NavisworksTransport/src/Core/PathPlanningManager.cs

5707 lines
242 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;
using NavisworksTransport.PathPlanning;
using NavisworksTransport.Utils;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Config;
using NavisworksTransport.Commands;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport
{
/// <summary>
/// 路径规划管理器
/// 负责路径规划的核心业务逻辑与UI完全解耦
/// </summary>
public class PathPlanningManager : IPathPlanningManager, IDisposable
{
#region
private readonly string _managerId;
private readonly CategoryAttributeManager _categoryManager;
private readonly UIStateManager _uiStateManager;
private CoordinateConverter _coordinateConverter;
private PathPointRenderPlugin _renderPlugin;
// 路径分析相关
private PathDatabase _pathDatabase;
private PathAnalysisService _analysisService;
private string _databaseDocumentPath;
private List<ModelItem> _walkableAreas;
private List<PathRoute> _routes;
private PathRoute _currentRoute;
private ChannelBounds _combinedChannelBounds;
// 路径编辑状态管理
private PathEditState _pathEditState = PathEditState.Viewing;
private PathPointType _currentPointType = PathPointType.WayPoint;
private PathRoute _editingRoute = null;
private PathHistoryManager _historyManager;
// ToolPlugin 集成
private bool _isToolPluginActive = false;
// 自动路径规划模式标志
private bool _isInAutoPathMode = false;
// 空轨吸附模式标志
private bool _enableRailSnapping = false;
// 吊装模式标志
private bool _enableHoistingMode = false;
// 吊装起点3D坐标
private Point3D _hoistingStartPoint = null;
// 吊装高度(模型单位)
private double _hoistingLiftHeight;
// 吊装中间点列表(用户点击的地面投影点)
private List<Point3D> _hoistingGroundIntermediatePoints = new List<Point3D>();
// 路径点3D标记管理
private List<PathPointMarker> _pathPointMarkers;
// 预览点管理
private PathPoint _previewPoint = null;
// 网格可视化设置
private bool _showWalkableGrid = false;
private bool _showObstacleGrid = false;
private bool _showUnknownGrid = false;
private bool _showDoorGrid = false;
private GridMap _currentGridMap = null; // 保存当前网格地图用于刷新
private double _currentObjectHeight = 2.0; // 保存当前物体高度(米)用于网格刷新
private bool _isPreviewMode = false;
private int _previewInsertIndex = -1; // 保存预览点应该插入的索引位置
// 修改路径点管理
private int _editingPointIndex = -1; // 正在修改的路径点索引
private PathPoint _originalPoint = null; // 修改前的原始路径点
private PathPoint _editingPreviewPoint = null; // 修改时的预览路径点
/// <summary>
/// ToolPlugin是否处于激活状态供InputMonitor使用
/// </summary>
public bool IsToolPluginActive => _isToolPluginActive;
/// <summary>
/// 获取当前是否在预览模式
/// </summary>
public bool IsPreviewMode => _isPreviewMode;
// 静态引用用于处理ToolPlugin事件
private static PathPlanningManager _activePathManager;
#endregion
#region
/// <summary>
/// 构造函数
/// </summary>
/// <param name="categoryManager">类别属性管理器</param>
/// <param name="uiStateManager">UI状态管理器可选</param>
public PathPlanningManager(CategoryAttributeManager categoryManager, UIStateManager uiStateManager = null)
{
_managerId = Guid.NewGuid().ToString("N").Substring(0, 8); // 前8位作为ID
_categoryManager = categoryManager ?? throw new ArgumentNullException(nameof(categoryManager));
_uiStateManager = uiStateManager ?? UIStateManager.Instance;
InitializeManager();
}
/// <summary>
/// 无参构造函数(为向后兼容而保留)
/// </summary>
public PathPlanningManager()
{
_managerId = Guid.NewGuid().ToString("N").Substring(0, 8);
_categoryManager = new CategoryAttributeManager();
_uiStateManager = UIStateManager.Instance;
InitializeManager();
}
private void InitializeManager()
{
// 设置静态引用,确保所有地方都使用同一个实例
_activePathManager = this;
LogManager.Debug($"[路径管理] 设置_activePathManager静态引用, ManagerId: {_managerId}");
// 获取已注册的圆形渲染插件实例
try
{
_renderPlugin = PathPointRenderPlugin.Instance;
if (_renderPlugin != null)
{
LogManager.Info("[路径管理] ✅ PathPointRenderPlugin实例获取成功");
LogManager.Debug($"[路径管理] 渲染插件状态 - 启用: {_renderPlugin.IsEnabled}, 标记数量: {_renderPlugin.MarkerCount}");
// 推送默认的网格大小和物体参数,确保渲染插件有合理的初始值
InitializeRenderPluginDefaults();
}
else
{
LogManager.Warning("[路径管理] ❌ PathPointRenderPlugin实例尚未就绪将在后续尝试获取");
}
}
catch (Exception ex)
{
LogManager.Error($"[路径管理] PathPointRenderPlugin实例获取失败: {ex.Message}");
LogManager.Error($"[路径管理] 异常堆栈: {ex.StackTrace}");
_renderPlugin = null;
}
// 初始化核心数据结构
_walkableAreas = new List<ModelItem>();
_routes = new List<PathRoute>();
_currentRoute = new PathRoute("默认路径");
_historyManager = new PathHistoryManager(50); // 最多保存50个历史记录
_coordinateConverter = null; // 将在需要时初始化
_pathPointMarkers = new List<PathPointMarker>();
LogManager.Info($"PathPlanningManager初始化完成ManagerId: {_managerId}");
// 注意:数据库采用按需初始化。
// 文档加载时只尝试连接已存在的数据库,不再为未使用插件的文档自动创建 .db 文件。
}
/// <summary>
/// 路径分析数据库初始化
/// 此方法应在文档加载完成后调用
/// </summary>
public void DatabaseInitialize(bool createIfMissing = true)
{
try
{
var documentPath = Application.ActiveDocument?.FileName;
if (string.IsNullOrEmpty(documentPath))
{
LogManager.Debug("文档路径为空,跳过数据库初始化");
return;
}
if (_pathDatabase != null &&
string.Equals(_databaseDocumentPath, documentPath, StringComparison.OrdinalIgnoreCase))
{
return;
}
string dbPath = Path.ChangeExtension(documentPath, ".db");
if (!createIfMissing && !File.Exists(dbPath))
{
LogManager.Info($"文档对应数据库不存在,跳过自动连接: {dbPath}");
return;
}
if (_pathDatabase != null)
{
try
{
_pathDatabase.Dispose();
}
catch (Exception disposeEx)
{
LogManager.Warning($"释放旧数据库连接时出现警告: {disposeEx.Message}");
}
_pathDatabase = null;
_analysisService = null;
_databaseDocumentPath = null;
}
_pathDatabase = new PathDatabase(documentPath);
_analysisService = new PathAnalysisService(_pathDatabase);
_databaseDocumentPath = documentPath;
LogManager.Info($"路径分析数据库初始化成功,文档路径: {documentPath}");
// 从数据库加载历史路径到内存
LoadHistoricalRoutesFromDatabase();
}
catch (Exception ex)
{
LogManager.Error($"初始化路径分析数据库失败: {ex.Message}", ex);
LogManager.Error($"数据库路径: {Application.ActiveDocument?.FileName}");
LogManager.Error($"异常类型: {ex.GetType().Name}");
throw; // 重新抛出异常,让调用者知道初始化失败
}
}
/// <summary>
/// 初始化渲染插件的默认值
/// 从配置文件读取网格大小和物体参数,确保渲染插件在任何情况下都有正确的可视化效果
/// </summary>
private void InitializeRenderPluginDefaults()
{
try
{
var config = ConfigManager.Instance.Current;
// 从配置读取网格大小
double gridSizeInMeters = config.PathEditing.CellSizeMeters;
_renderPlugin.SetGridSize(gridSizeInMeters);
LogManager.Info($"[渲染插件初始化] 已设置网格大小: {gridSizeInMeters}米(来自配置)");
// 通行空间参数将在UpdatePassageSpaceViewModel中根据路径类型动态设置
// 这里不设置默认值,因为不同路径类型的坐标系不同
LogManager.Info($"[渲染插件初始化] 通行空间参数将在使用时动态设置");
}
catch (Exception ex)
{
LogManager.Error($"[渲染插件初始化] 设置默认值失败: {ex.Message}", ex);
// 即使失败也不抛出异常避免影响PathPlanningManager的初始化
}
}
#endregion
#region IPathPlanningManager接口实现
public string ManagerId => _managerId;
public PathHistoryManager HistoryManager => _historyManager;
public PathEditState PathEditState
{
get { return _pathEditState; }
private set
{
var previousState = _pathEditState;
_pathEditState = value;
// 触发状态变更事件
RaiseEditStateChanged(previousState, value, _editingRoute);
LogManager.Info($"路径编辑状态变更: {previousState} -> {value}");
}
}
public PathRoute EditingRoute
{
get { return _editingRoute; }
private set { _editingRoute = value; }
}
public bool IsInEditableState
{
get { return _pathEditState == PathEditState.Creating || _pathEditState == PathEditState.Editing || _pathEditState == PathEditState.AddingPoints; }
}
public PathRoute CurrentRoute
{
get { return _currentRoute; }
private set
{
var previousRoute = _currentRoute;
_currentRoute = value;
// 只有真正的业务操作才触发事件UI同步不触发事件
RaiseCurrentRouteChanged(previousRoute, value, triggerEvent: true);
}
}
public IReadOnlyList<PathRoute> Routes => _routes.AsReadOnly();
/// <summary>
/// 获取可修改的路线集合(内部使用)
/// </summary>
internal List<PathRoute> ModifiableRoutes => _routes;
public IReadOnlyList<ModelItem> SelectedChannels => _walkableAreas.AsReadOnly();
public PathPointType CurrentPointType
{
get { return _currentPointType; }
private set { _currentPointType = value; }
}
/// <summary>
/// 是否在自动路径规划模式
/// </summary>
public bool IsInAutoPathMode
{
get { return _isInAutoPathMode; }
private set { _isInAutoPathMode = value; }
}
public ChannelBounds CombinedChannelBounds => _combinedChannelBounds;
#endregion
#region
public event EventHandler<PathPlanningStatusChangedEventArgs> StatusChanged;
public event EventHandler<PathPlanningErrorOccurredEventArgs> ErrorOccurred;
public event EventHandler<PathEditStateChangedEventArgs> EditStateChanged;
public event EventHandler<ChannelSelectionChangedEventArgs> ChannelSelectionChanged;
public event EventHandler<PathPointOperationEventArgs> PathPointOperation;
public event EventHandler<RouteGeneratedEventArgs> RouteGenerated;
public event EventHandler<CurrentRouteChangedEventArgs> CurrentRouteChanged;
public event EventHandler<PathPointsListUpdatedEventArgs> PathPointsListUpdated;
/// <summary>
/// 当前路径切换事件总是触发包括UI调用SetCurrentRoute时
/// 用于需要监听任何路径切换场景的功能(如剖面盒自动跟随)
/// </summary>
public event EventHandler<CurrentRouteChangedEventArgs> CurrentRouteSwitched;
/// <summary>
/// 路径可视化状态变更事件用于通知UI刷新可视化按钮状态
/// </summary>
public event EventHandler VisualizationStateChanged;
public event EventHandler<RoutesLoadedEventArgs> RoutesLoaded;
// 保留原有事件以保持向后兼容
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<List<ModelItem>> ChannelsSelected;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<PathRoute> CurrentRouteChanged_Legacy;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<PathRoute> RouteGenerated_Legacy;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<string> StatusChanged_Legacy;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<string> ErrorOccurred_Legacy;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<PathEditState> PathEditStateChanged;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<PathPoint> PathPointAddedIn3D;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<PathPoint> PathPointRemovedFrom3D;
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<PathRoute> PathPointsListUpdated_Legacy;
/// <summary>
#endregion
#region
private void RaiseStatusChanged(string statusMessage, PathPlanningStatusType statusType = PathPlanningStatusType.Info, object additionalData = null)
{
try
{
var eventArgs = new PathPlanningStatusChangedEventArgs(statusMessage, statusType, additionalData, _managerId);
// 使用UIStateManager安全地触发事件
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
StatusChanged?.Invoke(this, eventArgs);
// 向后兼容的事件
StatusChanged_Legacy?.Invoke(this, statusMessage);
});
}
else
{
StatusChanged?.Invoke(this, eventArgs);
StatusChanged_Legacy?.Invoke(this, statusMessage);
}
LogManager.Info($"[状态变更] {statusMessage}");
}
catch (Exception ex)
{
LogManager.Error($"[状态变更] 事件触发失败: {ex.Message}");
}
}
private void RaiseErrorOccurred(string errorMessage, Exception exception = null, PathPlanningErrorLevel errorLevel = PathPlanningErrorLevel.Error)
{
try
{
var eventArgs = new PathPlanningErrorOccurredEventArgs(errorMessage, exception, errorLevel, _managerId);
// 使用UIStateManager安全地触发事件
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
ErrorOccurred?.Invoke(this, eventArgs);
// 向后兼容的事件
ErrorOccurred_Legacy?.Invoke(this, errorMessage);
});
}
else
{
ErrorOccurred?.Invoke(this, eventArgs);
ErrorOccurred_Legacy?.Invoke(this, errorMessage);
}
LogManager.Error($"[错误发生] {errorMessage}");
if (exception != null)
{
LogManager.Error($"[错误详情] {exception}");
}
}
catch (Exception ex)
{
LogManager.Error($"[错误事件] 触发失败: {ex.Message}");
}
}
private void RaiseEditStateChanged(PathEditState previousState, PathEditState newState, PathRoute editingRoute)
{
try
{
var eventArgs = new PathEditStateChangedEventArgs(previousState, newState, editingRoute, _managerId);
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
EditStateChanged?.Invoke(this, eventArgs);
// 向后兼容的事件
PathEditStateChanged?.Invoke(this, newState);
});
}
else
{
EditStateChanged?.Invoke(this, eventArgs);
PathEditStateChanged?.Invoke(this, newState);
}
}
catch (Exception ex)
{
LogManager.Error($"[编辑状态变更] 事件触发失败: {ex.Message}");
}
}
private void RaiseChannelSelectionChanged(List<ModelItem> selectedChannels, string selectionMethod)
{
try
{
var eventArgs = new ChannelSelectionChangedEventArgs(selectedChannels, selectionMethod, _managerId);
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
ChannelSelectionChanged?.Invoke(this, eventArgs);
// 向后兼容的事件
ChannelsSelected?.Invoke(this, selectedChannels);
});
}
else
{
ChannelSelectionChanged?.Invoke(this, eventArgs);
ChannelsSelected?.Invoke(this, selectedChannels);
}
}
catch (Exception ex)
{
LogManager.Error($"[通道选择变更] 事件触发失败: {ex.Message}");
}
}
private void RaisePathPointOperation(PathPointOperationType operationType, PathPoint pathPoint, PathRoute route)
{
try
{
var eventArgs = new PathPointOperationEventArgs(operationType, pathPoint, route, _managerId);
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
PathPointOperation?.Invoke(this, eventArgs);
// 向后兼容的事件
if (operationType == PathPointOperationType.Added)
{
PathPointAddedIn3D?.Invoke(this, pathPoint);
}
else if (operationType == PathPointOperationType.Removed)
{
PathPointRemovedFrom3D?.Invoke(this, pathPoint);
}
});
}
else
{
PathPointOperation?.Invoke(this, eventArgs);
if (operationType == PathPointOperationType.Added)
{
PathPointAddedIn3D?.Invoke(this, pathPoint);
}
else if (operationType == PathPointOperationType.Removed)
{
PathPointRemovedFrom3D?.Invoke(this, pathPoint);
}
}
}
catch (Exception ex)
{
LogManager.Error($"[路径点操作] 事件触发失败: {ex.Message}");
}
}
private void RaiseRouteGenerated(PathRoute route, RouteGenerationMethod generationMethod, double gridSize = -1)
{
try
{
LogManager.Info($"*** RaiseRouteGenerated被调用: {route?.Name}, Method: {generationMethod}, GridSize: {gridSize} ***");
// 在触发事件前,进行曲线化计算和保存
// 注意Manual方法不需要重新计算因为PathPointsListUpdated已经计算过了
if (route != null && route.Points.Count >= 2 && generationMethod != RouteGenerationMethod.Manual)
{
// 使用统一的曲线化和保存方法
route.RecalculateAndSaveRoute($"RouteGenerated({generationMethod})");
}
else if (generationMethod == RouteGenerationMethod.Manual)
{
LogManager.Info($"*** Manual方法跳过重新计算PathPointsListUpdated已经计算过了 ***");
}
var eventArgs = new RouteGeneratedEventArgs(route, generationMethod, gridSize, _managerId);
// 检查是否有订阅者
if (RouteGenerated != null)
{
var invocationList = RouteGenerated.GetInvocationList();
LogManager.Info($"*** RouteGenerated事件有 {invocationList.Length} 个订阅者 ***");
for (int i = 0; i < invocationList.Length; i++)
{
var handler = invocationList[i];
LogManager.Info($"*** 订阅者 {i}: {handler.Target?.GetType().Name}.{handler.Method.Name} ***");
}
}
else
{
LogManager.Warning("*** RouteGenerated事件没有订阅者 ***");
}
// 测试强制同步处理使用高优先级QueueUIUpdate代替立即执行
if (_uiStateManager != null)
{
LogManager.Info("*** 使用高优先级强制同步处理RouteGenerated事件 ***");
_uiStateManager.QueueUIUpdateWithForcedSync(() =>
{
LogManager.Info($"*** 强制同步队列中执行RouteGenerated事件: {route?.Name} ***");
RouteGenerated?.Invoke(this, eventArgs);
// 向后兼容的事件
RouteGenerated_Legacy?.Invoke(this, route);
LogManager.Info($"*** RouteGenerated事件强制同步执行完成: {route?.Name} ***");
}, UIUpdatePriority.Critical, $"RouteGenerated强制同步事件({route?.Name})");
}
else
{
LogManager.Info("*** 直接触发RouteGenerated事件UIStateManager为null***");
RouteGenerated?.Invoke(this, eventArgs);
RouteGenerated_Legacy?.Invoke(this, route);
LogManager.Info($"*** RouteGenerated事件直接调用完成: {route?.Name} ***");
}
}
catch (Exception ex)
{
LogManager.Error($"*** [路径生成] 事件触发失败: {ex.Message} ***");
}
}
private void RaiseCurrentRouteChanged(PathRoute previousRoute, PathRoute newRoute, bool triggerEvent)
{
try
{
var eventArgs = new CurrentRouteChangedEventArgs(previousRoute, newRoute, _managerId);
// 只有需要触发事件时才执行事件触发逻辑
if (triggerEvent)
{
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
CurrentRouteChanged?.Invoke(this, eventArgs);
// 向后兼容的事件
CurrentRouteChanged_Legacy?.Invoke(this, newRoute);
});
}
else
{
CurrentRouteChanged?.Invoke(this, eventArgs);
CurrentRouteChanged_Legacy?.Invoke(this, newRoute);
}
}
}
catch (Exception ex)
{
LogManager.Error($"[当前路径变更] 事件触发失败: {ex.Message}");
}
}
/// <summary>
/// 触发可视化状态变更事件
/// </summary>
public void RaiseVisualizationStateChanged()
{
try
{
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
VisualizationStateChanged?.Invoke(this, EventArgs.Empty);
});
}
else
{
VisualizationStateChanged?.Invoke(this, EventArgs.Empty);
}
LogManager.Debug("[PathPlanningManager] 可视化状态变更事件已触发");
}
catch (Exception ex)
{
LogManager.Error($"[可视化状态变更] 事件触发失败: {ex.Message}");
}
}
public void RaisePathPointsListUpdated(PathRoute route, string updateReason)
{
try
{
// 在触发事件前,检查是否需要重新计算曲线化路径
// 根据 updateReason 判断:
// - "编辑完成":需要重新计算(刚完成编辑)
// - "路径点修改完成":需要重新计算(位置变化)
// - "添加路径点":需要重新计算(新增点)
// - "确认添加预览点":需要重新计算(新增点)
// - "删除路径点":需要重新计算(删除点)
bool needRecalculate = false;
if (route != null && route.Points.Count >= 2)
{
// 检查 Edges 是否为空
if (route.Edges == null || route.Edges.Count == 0)
{
needRecalculate = true;
LogManager.Info($"路径 Edges 为空,需要重新计算: {route.Name}");
}
// 根据更新原因判断
else if (updateReason == "编辑完成" ||
updateReason == "路径点修改完成" ||
updateReason == "添加路径点" ||
updateReason == "确认添加预览点" ||
updateReason == "删除路径点并重新分配类型")
{
needRecalculate = true;
LogManager.Info($"根据更新原因需要重新计算曲线化: {route.Name}, 原因: {updateReason}");
}
}
if (needRecalculate)
{
// 使用统一的曲线化和保存方法
route.RecalculateAndSaveRoute($"PathPointsListUpdated({updateReason})");
}
var eventArgs = new PathPointsListUpdatedEventArgs(route, updateReason, _managerId);
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
PathPointsListUpdated?.Invoke(this, eventArgs);
// 向后兼容的事件
PathPointsListUpdated_Legacy?.Invoke(this, route);
});
}
else
{
PathPointsListUpdated?.Invoke(this, eventArgs);
PathPointsListUpdated_Legacy?.Invoke(this, route);
}
}
catch (Exception ex)
{
LogManager.Error($"[路径点列表更新] 事件触发失败: {ex.Message}");
}
}
private void RaiseRoutesLoaded(IReadOnlyList<PathRoute> routes, string loadSource = "Database")
{
try
{
var eventArgs = new RoutesLoadedEventArgs(routes, loadSource, _managerId);
if (_uiStateManager != null)
{
_uiStateManager.QueueUIUpdate(() =>
{
RoutesLoaded?.Invoke(this, eventArgs);
});
}
else
{
RoutesLoaded?.Invoke(this, eventArgs);
}
LogManager.Info($"[路径加载完成] 从{loadSource}加载了 {routes.Count} 条路径");
}
catch (Exception ex)
{
LogManager.Error($"[路径加载完成] 事件触发失败: {ex.Message}");
}
}
#endregion
#region
/// <summary>
/// 切换到查看状态
/// </summary>
public void SwitchToViewingState()
{
PathEditState = PathEditState.Viewing;
EditingRoute = null;
// 智能管理ToolPlugin状态
ManageToolPluginForEditState();
RaiseStatusChanged("已切换到查看状态", PathPlanningStatusType.Info);
}
/// <summary>
/// 重置路径编辑状态
/// </summary>
public void ResetPathEditState()
{
try
{
PathEditState = PathEditState.None;
LogManager.Info("路径编辑状态已重置");
}
catch (Exception ex)
{
LogManager.Error("重置路径编辑状态失败", ex);
RaiseErrorOccurred(ex.Message, ex);
}
}
/// <summary>
/// 清理无效的对象引用
/// </summary>
public void ClearInvalidReferences()
{
try
{
LogManager.Info("[PathPlanningManager] 清理无效对象引用");
// 重置编辑状态
PathEditState = PathEditState.None;
// 清空当前路径
if (_currentRoute != null)
{
_currentRoute.Points.Clear();
}
// 清空通道引用
if (_walkableAreas != null)
{
_walkableAreas.Clear();
LogManager.Info("[PathPlanningManager] 已清空通道引用");
}
// 清空组合边界
_combinedChannelBounds = null;
LogManager.Info("[PathPlanningManager] 对象引用清理完成");
}
catch (Exception ex)
{
LogManager.Error($"[PathPlanningManager] 清理对象引用失败: {ex.Message}");
}
}
/// <summary>
/// 保存当前路线到历史记录
/// </summary>
/// <param name="description">保存描述</param>
public void SaveCurrentRouteToHistory(string description = "手动保存")
{
try
{
if (CurrentRoute != null)
{
// 创建历史记录条目
var entry = new PathHistoryEntry(CurrentRoute.Id, PathHistoryOperationType.ManualSave, CurrentRoute, description);
_historyManager?.AddHistoryEntry(entry);
LogManager.Info($"路线 '{CurrentRoute.Name}' 已保存到历史记录: {description}");
}
}
catch (Exception ex)
{
LogManager.Error("保存路线到历史记录失败", ex);
RaiseErrorOccurred(ex.Message, ex);
}
}
/// <summary>
/// 清除3D路径标记保留网格可视化
/// </summary>
public void Clear3DPathMarkers()
{
try
{
_pathPointMarkers?.Clear();
// 清除路径标记但保留网格可视化
_renderPlugin?.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door");
LogManager.Info("3D路径标记已清除保留网格可视化");
}
catch (Exception ex)
{
LogManager.Error("清除3D路径标记失败", ex);
RaiseErrorOccurred(ex.Message, ex);
}
}
/// <summary>
/// 删除路径点
/// </summary>
/// <param name="point">要删除的路径点</param>
public void RemovePathPoint(PathPoint point)
{
try
{
if (point != null)
{
if (_pathPointMarkers != null)
{
// 找到对应的PathPointMarker
var marker = _pathPointMarkers.FirstOrDefault(m => m.PathPoint?.Id == point.Id);
if (marker != null)
{
_pathPointMarkers.Remove(marker);
LogManager.Info($"已从3D中移除路径点标记: {point.Name}");
}
}
// 重新渲染当前路径以反映更改
if (_renderPlugin != null && CurrentRoute != null)
{
_renderPlugin.RenderPath(CurrentRoute);
LogManager.Info($"已更新路径可视化: {point.Name}");
}
// 触发路径点移除事件
RaisePathPointOperation(PathPointOperationType.Removed, point, CurrentRoute);
// 保存到数据库
if (CurrentRoute != null)
{
SavePathToDatabase(CurrentRoute);
}
}
}
catch (Exception ex)
{
LogManager.Error("从3D中移除路径点失败", ex);
RaiseErrorOccurred(ex.Message, ex);
}
}
/// <summary>
/// 启动点击工具
/// </summary>
/// <param name="pointType">点击类型</param>
public void StartClickTool(PathPointType pointType)
{
StartClickTool(pointType, enableRailSnapping: false, enableHoistingMode: false);
}
/// <summary>
/// 启动点击工具(支持空轨吸附)
/// </summary>
/// <param name="pointType">点击类型</param>
/// <param name="enableRailSnapping">是否启用空轨吸附</param>
public void StartClickTool(PathPointType pointType, bool enableRailSnapping)
{
StartClickTool(pointType, enableRailSnapping, enableHoistingMode: false);
}
/// <summary>
/// 启动点击工具(支持空轨吸附和吊装模式)
/// </summary>
/// <param name="pointType">点击类型</param>
/// <param name="enableRailSnapping">是否启用空轨吸附</param>
/// <param name="enableHoistingMode">是否启用吊装模式</param>
public void StartClickTool(PathPointType pointType, bool enableRailSnapping, bool enableHoistingMode)
{
try
{
CurrentPointType = pointType;
_enableRailSnapping = enableRailSnapping;
_enableHoistingMode = enableHoistingMode;
// 检查是否在自动路径模式 - 如果是则不订阅PathPlanningManager的事件
bool shouldSubscribeToEvents = !IsInAutoPathMode;
LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}, 空轨吸附: {enableRailSnapping}, 吊装模式: {enableHoistingMode}");
ActivateToolPlugin(shouldSubscribeToEvents);
PathEditState = PathEditState.AddingPoints;
LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents},空轨吸附: {enableRailSnapping},吊装模式: {enableHoistingMode}");
}
catch (Exception ex)
{
LogManager.Error("启动点击工具失败", ex);
RaiseErrorOccurred(ex.Message, ex);
}
}
/// <summary>
/// 停止点击工具
/// </summary>
public void StopClickTool()
{
try
{
LogManager.Debug("[事件清理] ===== 开始执行StopClickTool - 完整事件订阅清理 =====");
// 1. 停用ToolPlugin并清理事件订阅
DeactivateToolPlugin();
// 2. 简化的事件订阅清理 - 移除危险的反射操作
LogManager.Debug("[事件清理] 执行安全的事件订阅清理");
try
{
// 安全地移除事件订阅,多次取消订阅同一处理程序是安全的
PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked;
LogManager.Debug("[事件清理] 已安全移除PathPlanningManager事件订阅");
}
catch (Exception cleanupEx)
{
LogManager.Error($"[事件清理] 事件清理过程异常: {cleanupEx.Message}");
}
// 注意移除了PathEditState设置StopClickTool只管理工具插件状态不修改业务逻辑状态
LogManager.Debug("[事件清理] ===== StopClickTool执行完成所有事件订阅已清理 =====");
}
catch (Exception ex)
{
LogManager.Error("停止点击工具失败", ex);
RaiseErrorOccurred(ex.Message, ex);
}
}
/// <summary>
/// 禁用鼠标处理(用于自动路径规划模式)
/// </summary>
public void DisableMouseHandling()
{
try
{
LogManager.Info("禁用PathPlanningManager鼠标处理进入自动路径规划模式");
IsInAutoPathMode = true;
// 取消PathPlanningManager的鼠标事件订阅
PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked;
LogManager.Info("已取消PathPlanningManager的鼠标事件订阅");
}
catch (Exception ex)
{
LogManager.Error($"禁用鼠标处理失败: {ex.Message}", ex);
}
}
/// <summary>
/// 启用鼠标处理(用于手动路径编辑模式)
/// </summary>
public void EnableMouseHandling()
{
try
{
LogManager.Info("启用PathPlanningManager鼠标处理退出自动路径规划模式");
IsInAutoPathMode = false;
// 重新订阅PathPlanningManager的鼠标事件
PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked; // 先取消避免重复
PathClickToolPlugin.MouseClicked += OnToolPluginMouseClicked;
LogManager.Info("已重新订阅PathPlanningManager的鼠标事件");
}
catch (Exception ex)
{
LogManager.Error($"启用鼠标处理失败: {ex.Message}", ex);
}
}
/// <summary>
/// 自动路径规划(支持路径策略)
/// </summary>
/// <param name="startPoint">起点</param>
/// <param name="endPoint">终点</param>
/// <param name="objectRadius">物体尺寸(米)</param>
/// <param name="safetyMargin">安全间隙(米)</param>
/// <param name="gridSize">网格精度(米)</param>
/// <param name="objectHeight">物体高度(米)</param>
/// <param name="strategy">路径规划策略</param>
/// <returns>规划结果</returns>
public Task<PathRoute> AutoPlanPath(PathPoint startPoint, PathPoint endPoint, double objectRadius, double safetyMargin, double gridSize, double objectHeight, PathStrategy strategy)
{
try
{
if (startPoint == null || endPoint == null)
{
throw new ArgumentException("起点和终点不能为空");
}
LogManager.Info($"开始自动路径规划: {startPoint.Name} -> {endPoint.Name}");
LogManager.Info($"起点坐标: ({startPoint.Position.X:F2}, {startPoint.Position.Y:F2}, {startPoint.Position.Z:F2})");
LogManager.Info($"终点坐标: ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
LogManager.Info($"物体半径: {objectRadius}m, 安全间隙: {safetyMargin}m, 物体高度: {objectHeight}m");
RaiseStatusChanged("正在进行自动路径规划...", PathPlanningStatusType.Info);
// 1. 获取模型边界
var bounds = GetModelBounds() ?? throw new Exception("无法获取模型边界,请确保模型已加载");
var hostAdapter = new HostCoordinateAdapter(CoordinateSystemManager.Instance.ResolvedType);
var hostBoundsMin = new System.Numerics.Vector3((float)bounds.Min.X, (float)bounds.Min.Y, (float)bounds.Min.Z);
var hostBoundsMax = new System.Numerics.Vector3((float)bounds.Max.X, (float)bounds.Max.Y, (float)bounds.Max.Z);
var (boundsMinH1, boundsMaxH1, boundsMinH2, boundsMaxH2) =
HostPlanarGridHelper.GetHorizontalRange3(hostBoundsMin, hostBoundsMax, hostAdapter);
LogManager.Info($"模型边界: H1[{boundsMinH1:F2}, {boundsMaxH1:F2}], H2[{boundsMinH2:F2}, {boundsMaxH2:F2}]");
// 智能选择网格大小
if (gridSize <= 0)
{
gridSize = CalculateOptimalGridSize(bounds);
LogManager.Info($"自动选择网格大小: {gridSize}米");
}
else
{
LogManager.Info($"使用用户设置的网格大小: {gridSize}米");
}
// 2. 生成网格地图
RaiseStatusChanged("正在生成网格地图...", PathPlanningStatusType.Info);
var gridMapGenerator = new GridMapGenerator();
GridMap gridMap = null;
try
{
var performanceStart = System.Diagnostics.Stopwatch.StartNew();
// 获取当前文档
var document = Application.ActiveDocument;
LogManager.Info($"网格生成参数 - 边界: H1 {boundsMinH1:F2}->{boundsMaxH1:F2}, H2 {boundsMinH2:F2}->{boundsMaxH2:F2}");
LogManager.Info($"网格生成参数 - 网格大小: {gridSize}m, 物体半径: {objectRadius}m, 安全边距: {safetyMargin}m");
LogManager.Info($"网格生成参数 - 起点: ({startPoint.Position.X:F2}, {startPoint.Position.Y:F2}, {startPoint.Position.Z:F2})");
LogManager.Info($"网格生成参数 - 终点: ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
try
{
gridMap = gridMapGenerator.GenerateFromBIM(
bounds,
gridSize,
objectRadius,
safetyMargin,
startPoint.Position,
endPoint.Position,
objectHeight
);
LogManager.Info("✅ 网格地图生成成功");
}
catch (Exception modeEx)
{
LogManager.Warning($"网格地图生成失败,{modeEx.Message}");
}
performanceStart.Stop();
LogManager.Info($"GridMapGenerator.GenerateFromBIM 调用完成,耗时: {performanceStart.ElapsedMilliseconds}ms");
LogManager.Info($"生成的网格统计: {gridMap?.GetStatistics() ?? "NULL"}");
}
catch (Exception ex)
{
LogManager.Error($"网格地图生成失败: {ex.Message}");
throw; // 直接抛出原始异常,不包装
}
// 根据用户设置决定是否进行网格可视化
if (_showWalkableGrid || _showObstacleGrid || _showUnknownGrid || _showDoorGrid)
{
LogManager.Info("开始网格可视化,显示所有可通行网格单元");
VisualizeGridCells(gridMap, objectHeight);
}
// 3. 获取通道高度数据
var channelItems = GetChannelItemsForHeightCalculation(gridMap);
LogManager.Info($"获取通道数据完成: {channelItems?.Count() ?? 0} 个通道项");
// 4. 创建ChannelCoverage对象关键修复
ChannelCoverage channelCoverage = null;
if (channelItems != null && channelItems.Any())
{
LogManager.Info($"创建ChannelCoverage对象包含 {channelItems.Count()} 个通道项");
channelCoverage = new ChannelCoverage
{
GridMap = gridMap,
ChannelItems = channelItems.ToList(),
TotalBounds = bounds
};
LogManager.Info($"ChannelCoverage创建完成: {channelCoverage.GetStatistics()}");
}
else
{
LogManager.Error("未找到通道数据,无法进行路径规划");
throw new Exception("路径规划需要通道数据,请先进行通道检测");
}
// 5. 执行A*路径查找
RaiseStatusChanged("正在计算最优路径...", PathPlanningStatusType.Info);
LogManager.Info("=== 开始执行A*路径查找 ===");
AutoPathFinder pathFinder = null;
PathFindingResult pathResult = null;
try
{
pathFinder = new AutoPathFinder();
double objectHeightInModelUnits = objectHeight * UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
LogManager.Info($"使用2.5D模式进行路径查找,物体高度: {objectHeight}m ({objectHeightInModelUnits:F2}模型单位),策略: {strategy}");
pathResult = pathFinder.FindPath(startPoint.Position, endPoint.Position, gridMap, channelCoverage, objectHeightInModelUnits, strategy);
LogManager.Info("FindPath方法调用完成");
}
catch (Exception ex)
{
LogManager.Error($"路径查找失败: {ex.Message}");
throw; // 直接抛出原始异常,不包装
}
// 1. 检查路径查找是否完全失败
if (pathResult == null)
{
throw new Exception("路径查找失败");
}
// 2. 检查是否找到了可用的路径点至少2个点才能构成路径
if (pathResult.PathPoints.Count < 2)
{
throw new Exception("未找到可行路径(起点或终点不可达)");
}
// 3. 部分路径也是有效路径,不再抛出异常,继续正常处理
LogManager.Info($"A*算法找到路径,包含 {pathResult.PathPoints.Count} 个点,完成度: {pathResult.CompletionPercentage:F1}%");
// 6. 创建PathRoute对象并保存GridMap
var routeName = $"自动_{DateTime.Now:MMdd_HHmmss}";
// 🔥 关键修复在创建路径前设置_currentGridMap确保GetSpeedLimitAtPosition能正常工作
_currentGridMap = gridMap;
var autoRoute = CreateAutoPathRoute(pathResult, routeName);
// 保存GridMap和参数到PathRoute以便后续恢复网格可视化
autoRoute.AssociatedGridMap = gridMap;
autoRoute.GridSize = gridSize;
// 将objectRadius拆分为长宽暂时使用相同值后续可以传入更详细的参数
autoRoute.MaxObjectLength = objectRadius * 2; // 物体半径转换为长度
autoRoute.MaxObjectWidth = objectRadius * 2; // 物体半径转换为宽度
autoRoute.MaxObjectHeight = objectHeight;
autoRoute.SafetyMargin = safetyMargin;
LogManager.Info($"已保存GridMap到路径: {routeName}, 网格大小: {gridSize}米");
// 7. 添加到路径集合
if (!_routes.Contains(autoRoute))
{
_routes.Add(autoRoute);
}
SetCurrentRouteInternal(autoRoute, triggerEvent: true);
// 8. 确保路径可视化使用正确的网格大小(修复尺寸自适应问题)
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin != null && gridMap != null)
{
double gridSizeInMeters = UnitsConverter.ConvertToMeters(gridMap.CellSize);
renderPlugin.SetGridSize(gridSizeInMeters);
LogManager.Debug($"[路径可视化] 设置网格大小为 {gridSizeInMeters:F3}米,确保路径点尺寸正确");
}
// 9. 自动绘制路径可视化
DrawRouteVisualization(autoRoute, isAutoPath: true);
// 10. 强制设置路径编辑状态为查看状态(修复状态问题)
// 在所有事件触发之前设置状态,确保不会被后续操作重置
LogManager.Info("自动路径规划完成强制设置状态为Viewing");
PathEditState = PathEditState.Viewing;
EditingRoute = null; // 确保没有编辑中的路径
// 11. 触发事件
RaiseRouteGenerated(autoRoute, RouteGenerationMethod.AutoPlanning, gridSize);
var statusMessage = $"自动路径规划完成(2.5D模式): {routeName},使用网格大小 {gridSize:F2}米";
// 添加完成度信息
if (!autoRoute.IsComplete)
{
statusMessage += $" (完成度: {autoRoute.CompletionPercentage:F1}%)";
}
RaiseStatusChanged(statusMessage, PathPlanningStatusType.Success);
LogManager.Info($"自动路径规划成功完成: 路径长度 {autoRoute.TotalLength:F2}米,包含 {autoRoute.Points.Count} 个点,使用网格大小 {gridSize:F2}米");
return Task.FromResult(autoRoute);
}
catch (Exception ex)
{
LogManager.Error($"自动路径规划失败: {ex.Message}", ex);
RaiseErrorOccurred(ex.Message, ex); // 使用原始异常消息,不再包装
return Task.FromResult<PathRoute>(null);
}
}
/// <summary>
/// 设置当前路线UI调用版本不触发事件以避免循环
/// </summary>
/// <param name="route">要设置的路线</param>
public void SetCurrentRoute(PathRoute route)
{
SetCurrentRouteInternal(route, triggerEvent: false);
}
/// <summary>
/// 内部设置当前路线(可选择是否触发事件)
/// </summary>
/// <param name="route">要设置的路线</param>
/// <param name="triggerEvent">是否触发事件</param>
private void SetCurrentRouteInternal(PathRoute route, bool triggerEvent)
{
try
{
// 从数据库加载路径数据(如果有的话)
if (route != null && !string.IsNullOrEmpty(route.Id) && _pathDatabase != null)
{
try
{
// 从数据库加载完整路径数据(包括路径点)
var loadedRoute = _pathDatabase.GetPathRouteSync(route.Id);
if (loadedRoute != null && loadedRoute.Points.Count > 0)
{
// 清空现有路径点并从数据库加载
route.Points.Clear();
foreach (var point in loadedRoute.Points)
{
route.Points.Add(point);
}
// 更新其他属性不要覆盖TotalLength因为LoadPathEdges已经根据Edges重新计算了
route.LiftHeight = loadedRoute.LiftHeight;
route.RailMountMode = loadedRoute.RailMountMode;
route.RailPathDefinitionMode = loadedRoute.RailPathDefinitionMode;
route.RailNormalOffset = loadedRoute.RailNormalOffset;
route.RailPreferredNormal = loadedRoute.RailPreferredNormal != null
? new Point3D(
loadedRoute.RailPreferredNormal.X,
loadedRoute.RailPreferredNormal.Y,
loadedRoute.RailPreferredNormal.Z)
: null;
LogManager.Debug($"已从数据库加载路径数据: {route.Name}, 共 {loadedRoute.Points.Count} 个点");
}
}
catch (Exception ex)
{
LogManager.Error($"从数据库加载路径数据失败: {ex.Message}", ex);
}
}
// 直接设置字段,绕过事件触发
var previousRoute = _currentRoute;
_currentRoute = route;
// 总是触发 CurrentRouteSwitched 事件(用于剖面盒自动跟随等功能)
CurrentRouteSwitched?.Invoke(this, new CurrentRouteChangedEventArgs(previousRoute, route, _managerId));
// 如果需要触发事件(真正的业务操作)
if (triggerEvent)
{
RaiseCurrentRouteChanged(previousRoute, route, triggerEvent: true);
}
// 如果路径有关联的GridMap加载它用于网格可视化
if (route?.AssociatedGridMap != null)
{
_currentGridMap = route.AssociatedGridMap;
// GridSize是模型单位转换为米显示
double gridSizeMeters = UnitsConverter.ConvertToMeters(route.GridSize);
LogManager.Info($"已加载路径 '{route.Name}' 的GridMap网格大小: {gridSizeMeters:F3}米");
// 如果网格可视化开启,立即刷新显示
if (IsAnyGridVisualizationEnabled)
{
LogManager.Info("检测到网格可视化已启用,自动刷新网格显示");
RefreshGridVisualization();
}
}
else
{
// 如果路径没有GridMap清空当前缓存
_currentGridMap = null;
if (route != null)
{
LogManager.Info($"路径 '{route.Name}' 没有关联的GridMap");
if (IsAnyGridVisualizationEnabled)
{
LogManager.Info("检测到网格可视化已启用尝试按路径保存的自动规划参数重建GridMap");
RefreshGridVisualization();
}
}
}
LogManager.Info($"当前路线已设置: {route?.Name ?? "null"}");
}
catch (Exception ex)
{
LogManager.Error("设置当前路线失败", ex);
RaiseErrorOccurred(ex.Message, ex);
}
}
/// <summary>
/// 切换到编辑状态
/// </summary>
/// <param name="route">要编辑的路径</param>
public void SwitchToEditingState(PathRoute route)
{
if (route == null)
{
throw new ArgumentNullException(nameof(route));
}
PathEditState = PathEditState.Editing;
EditingRoute = route;
SetCurrentRouteInternal(route, triggerEvent: true);
// 智能管理ToolPlugin状态
ManageToolPluginForEditState();
RaiseStatusChanged($"已切换到编辑状态,正在编辑路径: {route.Name}", PathPlanningStatusType.Info);
}
/// <summary>
/// 选择通道模型
/// </summary>
/// <param name="useCurrentSelection">是否使用当前选择的模型</param>
/// <returns>选择的通道数量</returns>
public int SelectChannels(bool useCurrentSelection = true)
{
try
{
_walkableAreas.Clear();
if (useCurrentSelection)
{
// 使用当前选择的模型
var currentSelection = Application.ActiveDocument.CurrentSelection.SelectedItems;
if (currentSelection.Any())
{
_walkableAreas.AddRange(currentSelection);
RaiseStatusChanged($"已选择 {_walkableAreas.Count} 个模型作为通道", PathPlanningStatusType.Success);
}
else
{
RaiseStatusChanged("未选择任何模型,请先选择通道模型", PathPlanningStatusType.Warning);
return 0;
}
}
else
{
// 直接使用 CategoryAttributeManager 的方法筛选通道,无需额外的包装方法
var document = Application.ActiveDocument;
if (document != null)
{
var allLogisticsItems = CategoryAttributeManager.GetAllLogisticsItems();
var channelItems = CategoryAttributeManager.FilterByLogisticsType(allLogisticsItems, "通道");
_walkableAreas.AddRange(channelItems);
LogManager.Info($"[SelectChannels] 通过CategoryAttributeManager直接筛选到 {channelItems.Count} 个通道");
}
RaiseStatusChanged($"通过类别筛选到 {_walkableAreas.Count} 个通道", PathPlanningStatusType.Success);
}
if (_walkableAreas.Any())
{
// 计算组合边界
CalculateCombinedBounds();
// 触发通道选择变更事件
RaiseChannelSelectionChanged(_walkableAreas, useCurrentSelection ? "手动选择" : "类别筛选");
}
return _walkableAreas.Count;
}
catch (Exception ex)
{
RaiseErrorOccurred($"选择通道时发生错误: {ex.Message}", ex);
return 0;
}
}
/// <summary>
/// 添加路径到管理器
/// </summary>
/// <param name="route">要添加的路径</param>
/// <returns>是否成功添加</returns>
public bool AddRoute(PathRoute route)
{
try
{
if (route == null)
{
RaiseErrorOccurred("无法添加空路径");
return false;
}
// 检查是否已存在同名路径
var existingRoute = _routes.FirstOrDefault(r => r.Name == route.Name);
if (existingRoute != null)
{
// 如果存在同名路径,生成唯一名称
int counter = 1;
string originalName = route.Name;
while (_routes.Any(r => r.Name == route.Name))
{
route.Name = $"{originalName}_{counter}";
counter++;
}
}
_routes.Add(route);
RaiseStatusChanged($"已添加路径: {route.Name}", PathPlanningStatusType.Success);
// 保存到数据库
SavePathToDatabase(route);
// AddRoute只负责添加路径到数据集合不自动设置当前路径
// 路径选择应该通过专门的选择逻辑处理,而不是添加操作的副作用
// if (_currentRoute == null || _currentRoute.Points.Count == 0)
// {
// CurrentRoute = route;
// }
// 注释掉多余的路径生成事件调用Manual类型路径通过UI层的RefreshPathRoutes()统一刷新
// RaiseRouteGenerated(route, RouteGenerationMethod.Manual);
return true;
}
catch (Exception ex)
{
RaiseErrorOccurred($"添加路径失败: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 创建新路径
/// </summary>
/// <param name="routeName">路径名称</param>
/// <returns>新创建的路径</returns>
public PathRoute CreateNewRoute(string routeName = null)
{
if (string.IsNullOrEmpty(routeName))
{
routeName = $"路径_{_routes.Count + 1}";
}
var newRoute = new PathRoute(routeName);
_routes.Add(newRoute);
SetCurrentRouteInternal(newRoute, triggerEvent: true);
// 保存到数据库
SavePathToDatabase(newRoute);
RaiseStatusChanged($"已创建新路径: {routeName}", PathPlanningStatusType.Success);
return newRoute;
}
/// <summary>
/// 删除路径
/// </summary>
/// <param name="route">要删除的路径</param>
/// <returns>是否成功删除</returns>
public bool DeleteRoute(PathRoute route)
{
if (route == null) return false;
try
{
bool removed = _routes.Remove(route);
if (removed)
{
// 从数据库删除
if (_pathDatabase != null)
{
try
{
_pathDatabase.DeletePathRoute(route.Id);
LogManager.Info($"已从数据库删除路径: {route.Name}");
}
catch (Exception dbEx)
{
LogManager.Error($"从数据库删除路径失败: {dbEx.Message}", dbEx);
// 继续执行,不影响内存操作
}
}
if (_currentRoute == route)
{
var newRoute = _routes.FirstOrDefault() ?? new PathRoute("默认路径");
SetCurrentRouteInternal(newRoute, triggerEvent: true);
}
RaiseStatusChanged($"已删除路径: {route.Name}", PathPlanningStatusType.Success);
}
return removed;
}
catch (Exception ex)
{
RaiseErrorOccurred($"删除路径时发生错误: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 生成路径
/// </summary>
/// <param name="route">要生成的路径为null时使用当前路径</param>
/// <returns>是否成功生成</returns>
public bool GeneratePath(PathRoute route = null)
{
route = route ?? _currentRoute;
if (route == null) return false;
try
{
// 验证路径有效性
if (!route.IsValid())
{
RaiseErrorOccurred("路径无效:必须包含至少一个起点和一个终点");
return false;
}
// 更新路径关联的通道ID
route.AssociatedChannelIds.Clear();
foreach (var channel in _walkableAreas)
{
route.AssociatedChannelIds.Add(channel.InstanceGuid.ToString());
}
// 计算预估时间(简单实现)
CalculateEstimatedTime(route);
RaiseStatusChanged($"路径生成成功: {route.Name}, 长度: {route.TotalLength:F2}米, 预估时间: {route.EstimatedTime:F1}秒", PathPlanningStatusType.Success);
RaiseRouteGenerated(route, RouteGenerationMethod.Manual);
return true;
}
catch (Exception ex)
{
RaiseErrorOccurred($"生成路径时发生错误: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 开始新建路径
/// </summary>
/// <param name="routeName">路径名称</param>
/// <param name="isRailPath">是否为空轨路径</param>
/// <param name="pathType">路径类型(默认为地面路径)</param>
/// <returns>创建的新路径</returns>
public PathRoute StartCreatingNewRoute(string routeName = null, bool isRailPath = false, PathType pathType = PathType.Ground)
{
try
{
// 空轨路径不需要自动选择可通行物流模型
if (!isRailPath && pathType == PathType.Ground)
{
// 自动选择所有可通行的物流模型(先检查是否有可通行的模型)
AutoSelectLogisticsChannels();
// 检查是否有可通行的物流模型
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
RaiseErrorOccurred("没有找到任何可通行的物流模型,请先为模型设置可通行的物流属性");
// 不需要重置状态,因为还没有进入创建状态
return null;
}
}
// 设置为创建状态
PathEditState = PathEditState.Creating;
// 创建新路径,并设置路径类型
var newRoute = new PathRoute(routeName ?? $"人工_{DateTime.Now:MMdd_HHmmss}")
{
PathType = pathType
};
if (pathType == PathType.Rail)
{
newRoute.RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine;
}
_editingRoute = newRoute;
// 立即添加到路径集合确保UI能正确找到Core路径
if (!_routes.Contains(newRoute))
{
_routes.Add(newRoute);
}
SetCurrentRouteInternal(newRoute, triggerEvent: true);
// 如果是吊装路径,清理吊装模式相关状态
if (pathType == PathType.Hoisting)
{
_enableHoistingMode = false; // 将在StartClickTool中设置为true
_hoistingStartPoint = null;
_hoistingLiftHeight = 0;
_hoistingGroundIntermediatePoints.Clear();
LogManager.Debug($"[吊装模式] 已清理吊装模式状态,准备新建吊装路径");
}
// 智能管理ToolPlugin状态
ManageToolPluginForEditState();
string statusMessage;
if (pathType == PathType.Rail || pathType == PathType.Hoisting)
{
statusMessage = $"正在新建空中路径: {newRoute.Name} - 请在3D视图中设置路径点";
}
else
{
statusMessage = $"正在新建地面路径: {newRoute.Name} - 请在3D视图中可通行的物流模型上点击设置路径点";
}
RaiseStatusChanged(statusMessage, PathPlanningStatusType.Info);
return newRoute;
}
catch (Exception ex)
{
RaiseErrorOccurred($"开始新建路径失败: {ex.Message}", ex);
// 确保在出错时也重置状态
SwitchToViewingState();
return null;
}
}
/// <summary>
/// 完成当前编辑并保存
/// </summary>
/// <returns>是否成功完成编辑</returns>
/// <summary>
/// 完成路径编辑
/// </summary>
public bool FinishEditing()
{
if (!IsInEditableState)
{
RaiseErrorOccurred("当前不在编辑状态,无法完成编辑");
return false;
}
try
{
// 根据路径类型执行不同的完成逻辑
if (CurrentRoute != null && CurrentRoute.Points.Count > 1)
{
// 地面路径和空轨路径:自动设置最后一个点为终点
if (CurrentRoute.PathType == PathType.Ground ||
CurrentRoute.PathType == PathType.Rail)
{
var lastPoint = CurrentRoute.Points.Last();
if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点
{
lastPoint.Type = PathPointType.EndPoint;
lastPoint.Name = GeneratePointName(PathPointType.EndPoint);
// 更新3D路径可视化
// 重新渲染整个路径以反映类型变更
_renderPlugin?.RenderPath(CurrentRoute);
string pathTypeName = CurrentRoute.PathType == PathType.Ground ? "地面路径" : "空轨路径";
LogManager.Info($"[{pathTypeName}] 已自动设置最后一个点为终点: {lastPoint.Name}");
}
}
// 吊装路径:添加下降点和落地点
else if (CurrentRoute.PathType == PathType.Hoisting)
{
LogManager.Info($"[吊装路径] 正在完成吊装路径,处理终点和下降点");
// 确保至少有起吊点、提升点
if (CurrentRoute.Points.Count >= 2)
{
// 获取起吊点用于获取地面Z坐标
var startPoint = CurrentRoute.Points.First();
// 获取最后一个路径点用于获取当前的空中XY位置
var lastPoint = CurrentRoute.Points.Last();
LogManager.Info($"[吊装路径] 最后一个路径点: {lastPoint.Name}, 位置: ({lastPoint.Position.X:F2}, {lastPoint.Position.Y:F2}, {lastPoint.Position.Z:F2})");
var descendPoint = ReuseLastHoistingPointAsDescendPoint(CurrentRoute.Points);
LogManager.Info($"[吊装路径] 已将最后一个空中点复用为下降点(终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
Point3D finalGroundPoint = _hoistingGroundIntermediatePoints.Count > 0
? _hoistingGroundIntermediatePoints[_hoistingGroundIntermediatePoints.Count - 1]
: SetHoistingElevation(lastPoint.Position, GetHoistingElevation(startPoint.Position));
// 创建落地点(终点):使用用户最后一次点击的地面点,避免把终点高程错误吸回起点
var endPoint = new PathPoint(
finalGroundPoint,
"落地点",
PathPointType.EndPoint);
endPoint.Direction = HoistingPointDirection.Vertical;
endPoint.Index = CurrentRoute.Points.Count;
LogManager.Info($"[吊装路径] 已创建落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
// 添加落地点(终点)
CurrentRoute.Points.Add(endPoint);
LogManager.Info($"[吊装路径] 已复用最后一个空中点作为下降点,并添加落地点,当前共 {CurrentRoute.Points.Count} 个路径点");
// 注意:不需要再次调用 OrthogonalizePath因为添加的只是垂直线段
// 垂直线段不会形成斜线,所以不需要优化
// 注意TotalLength 现在是计算属性,自动从几何数据计算,无需手动更新
// 检测并移除同一高度层的矩形环路
LogManager.Info($"[吊装路径] 开始检测矩形环路,当前路径点数: {CurrentRoute?.Points?.Count ?? 0}");
bool loopsRemoved = RemoveRectangularLoops(CurrentRoute);
LogManager.Info($"[吊装路径] 矩形环路检测完成,是否移除环路: {loopsRemoved}");
// 重新渲染路径
_renderPlugin?.RenderPath(CurrentRoute);
LogManager.Info($"[吊装路径] 已完成吊装路径,共 {CurrentRoute.Points.Count} 个路径点");
// 清理吊装模式临时变量
_hoistingStartPoint = null;
_hoistingLiftHeight = 0;
_hoistingGroundIntermediatePoints.Clear();
_enableHoistingMode = false;
LogManager.Debug($"[吊装路径] 已清理吊装模式临时变量");
}
else
{
LogManager.Warning($"[吊装路径] 路径点不足或没有地面点击记录,无法完成吊装路径");
}
}
}
// 如果是创建模式或添加点模式,将当前路径添加到路径集合
if ((PathEditState == PathEditState.Creating || PathEditState == PathEditState.AddingPoints) && CurrentRoute != null)
{
if (!_routes.Contains(CurrentRoute))
{
_routes.Add(CurrentRoute);
// 添加历史记录
var historyEntry = new PathHistoryEntry(
CurrentRoute.Id,
PathHistoryOperationType.Created,
CurrentRoute,
$"创建新路径: {CurrentRoute.Name}");
_historyManager.AddHistoryEntry(historyEntry);
}
}
// 如果是编辑模式,保存编辑历史
if (_pathEditState == PathEditState.Editing && EditingRoute != null)
{
var historyEntry = new PathHistoryEntry(
EditingRoute.Id,
PathHistoryOperationType.Edited,
EditingRoute,
$"编辑路径: {EditingRoute.Name}");
_historyManager.AddHistoryEntry(historyEntry);
}
// 切换回查看状态
SwitchToViewingState();
// 触发路径点列表更新事件
RaisePathPointsListUpdated(CurrentRoute, "编辑完成");
// 结束编辑后主动再同步一次当前路径确保UI中的占位路径能回填完整点列表
SetCurrentRouteInternal(CurrentRoute, triggerEvent: true);
// 触发路径生成事件
RaiseRouteGenerated(CurrentRoute, RouteGenerationMethod.Manual);
RaiseStatusChanged("路径编辑已完成", PathPlanningStatusType.Success);
return true;
}
catch (Exception ex)
{
RaiseErrorOccurred($"完成编辑失败: {ex.Message}", ex);
return false;
}
}
#region
/// <summary>
/// 开始修改指定的路径点
/// </summary>
/// <param name="pointIndex">要修改的路径点索引</param>
/// <returns>是否成功开始修改</returns>
public bool StartEditingPoint(int pointIndex)
{
try
{
if (CurrentRoute == null || pointIndex < 0 || pointIndex >= CurrentRoute.Points.Count)
{
RaiseErrorOccurred("无效的路径点索引或当前没有路径");
return false;
}
// 保存原始点和索引
_editingPointIndex = pointIndex;
var originalPoint = CurrentRoute.Points[pointIndex];
_originalPoint = new PathPoint
{
Id = originalPoint.Id,
Name = originalPoint.Name,
Position = originalPoint.Position,
Type = originalPoint.Type
};
// 切换到修改路径点状态
PathEditState = PathEditState.EditingPoint;
// 激活ToolPlugin以接收3D点击
ActivateToolPlugin();
RaiseStatusChanged($"开始修改路径点: {_originalPoint.Name}", PathPlanningStatusType.Info);
LogManager.Info($"开始修改路径点,索引: {pointIndex}, 名称: {_originalPoint.Name}");
return true;
}
catch (Exception ex)
{
RaiseErrorOccurred($"开始修改路径点失败: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 设置修改路径点时的预览位置
/// </summary>
/// <param name="position">新的预览位置</param>
public void SetEditingPreviewPoint(Point3D position)
{
try
{
if (PathEditState != PathEditState.EditingPoint || _editingPointIndex == -1)
{
return;
}
// 创建预览路径点
_editingPreviewPoint = new PathPoint
{
Position = position,
Name = _originalPoint.Name + "_预览",
Type = _originalPoint.Type
};
// 更新预览可视化
UpdateEditingPreviewVisualization();
LogManager.Info($"设置修改预览点: ({position.X:F3}, {position.Y:F3}, {position.Z:F3})");
}
catch (Exception ex)
{
LogManager.Error($"设置修改预览点失败: {ex.Message}");
}
}
/// <summary>
/// 更新吊装路径的关联点(通用方法)
/// </summary>
/// <param name="route">路径</param>
/// <param name="modifiedPointIndex">修改的点的索引</param>
private void UpdateAerialPathRelatedPoints(PathRoute route, int modifiedPointIndex)
{
if (route == null || route.PathType != PathType.Hoisting)
{
return;
}
var points = route.Points;
if (points.Count == 0)
{
return;
}
// 修改起点索引0更新提升点索引1的X,Y坐标同步所有空中点Z
if (modifiedPointIndex == 0 && points.Count > 1)
{
var startPoint = points[0];
var liftPoint = points[1];
double newAerialElevation = GetHoistingElevation(startPoint.Position) + route.LiftHeight;
liftPoint.Position = SetHoistingElevation(startPoint.Position, newAerialElevation);
// 同步所有其他空中点Z坐标索引2到n-2
for (int i = 2; i <= points.Count - 2; i++)
{
points[i].Position = SetHoistingElevation(points[i].Position, newAerialElevation);
}
LogManager.Debug($"[吊装路径] 修改起点,更新提升点位置: ({liftPoint.Position.X:F2}, {liftPoint.Position.Y:F2}, {liftPoint.Position.Z:F2})同步所有空中点Z坐标");
}
// 修改终点最后一个点更新下降点倒数第二个点的X,Y坐标
else if (modifiedPointIndex == points.Count - 1 && points.Count > 1)
{
var endPoint = points[modifiedPointIndex];
var descendPoint = points[modifiedPointIndex - 1];
descendPoint.Position = SetHoistingElevation(endPoint.Position, GetHoistingElevation(descendPoint.Position));
LogManager.Debug($"[吊装路径] 修改终点,更新下降点位置: ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
}
}
/// <summary>
/// 检测两点之间是否为斜线,如果是则插入中间转折点或修正坐标
/// 处理三种斜线XY平面斜线、XZ平面斜线、YZ平面斜线
/// </summary>
/// <param name="points">路径点列表</param>
/// <param name="startIndex">开始检查的索引</param>
/// <returns>是否进行了修改(插入点或修正坐标)</returns>
private bool InsertIntermediatePointForDiagonal(IList<PathPoint> points, int startIndex)
{
if (points == null || startIndex < 0 || startIndex >= points.Count - 1)
{
return false;
}
var currentPoint = points[startIndex];
var nextPoint = points[startIndex + 1];
// 使用 Info 级别日志以便调试
LogManager.Debug($"[斜线处理] 检查索引 {startIndex} 和 {startIndex + 1} 之间的连线");
LogManager.Debug($"[斜线处理] 当前点 [{startIndex}]: {currentPoint.Name}, 位置: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
LogManager.Debug($"[斜线处理] 下一点 [{startIndex + 1}]: {nextPoint.Name}, 位置: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})");
Point3D canonicalCurrent = ToHoistingCanonicalPoint(currentPoint.Position);
Point3D canonicalNext = ToHoistingCanonicalPoint(nextPoint.Position);
double deltaX = Math.Abs(canonicalNext.X - canonicalCurrent.X);
double deltaY = Math.Abs(canonicalNext.Y - canonicalCurrent.Y);
double deltaZ = Math.Abs(canonicalNext.Z - canonicalCurrent.Z);
LogManager.Debug($"[斜线处理] deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2}");
// 检测三种斜线
bool isXYDiagonal = deltaX > 0.01 && deltaY > 0.01 && deltaZ < 0.001; // XY平面斜线
bool isXZDiagonal = deltaX > 0.01 && deltaZ > 0.001 && deltaY < 0.01; // XZ平面斜线
bool isYZDiagonal = deltaY > 0.01 && deltaZ > 0.001 && deltaX < 0.01; // YZ平面斜线
bool isXYZDiagonal = deltaX > 0.01 && deltaY > 0.01 && deltaZ > 0.001; // XYZ空间斜线
LogManager.Debug($"[斜线处理] 斜线检测结果: XY={isXYDiagonal}, XZ={isXZDiagonal}, YZ={isYZDiagonal}, XYZ={isXYZDiagonal}");
if (!isXYDiagonal && !isXZDiagonal && !isYZDiagonal && !isXYZDiagonal)
{
LogManager.Debug($"[斜线处理] 未检测到斜线,跳过处理");
return false;
}
// 处理XY平面斜线水平斜线
if (isXYDiagonal)
{
return HandleXYDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaY);
}
// 处理XZ平面斜线垂直斜线X和Z同时变化
if (isXZDiagonal)
{
return HandleXZDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaZ);
}
// 处理YZ平面斜线垂直斜线Y和Z同时变化
if (isYZDiagonal)
{
return HandleYZDiagonal(points, startIndex, currentPoint, nextPoint, deltaY, deltaZ);
}
// 处理XYZ空间斜线X、Y、Z都变化
if (isXYZDiagonal)
{
return HandleXYZDiagonal(points, startIndex, currentPoint, nextPoint, deltaX, deltaY, deltaZ);
}
return false;
}
/// <summary>
/// 处理XY平面斜线水平斜线
/// 策略:插入中间点将斜线拆分为直角转折,保持用户点击的坐标不变
/// </summary>
private bool HandleXYDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaY)
{
LogManager.Debug($"[斜线处理] 检测到XY平面斜线 (deltaX={deltaX:F2}, deltaY={deltaY:F2})");
// 策略:插入中间点,将斜线拆分为 X-only → Y-only 的两段直角路径
// 不修改用户点击的坐标,只插入转折点
// 转折点X用新点的XY用旧点的Y先X方向移动再Y方向移动
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
/// <summary>
/// 处理XZ平面斜线垂直斜线X和Z同时变化
/// 策略先水平移动X变化Z不变再垂直移动Z变化X不变
/// </summary>
private bool HandleXZDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaZ)
{
LogManager.Debug($"[斜线处理] 检测到XZ平面斜线 (deltaX={deltaX:F2}, deltaZ={deltaZ:F2})");
// 插入中间点先水平移动到新X旧Z目标点自然就是新X新Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
/// <summary>
/// 处理YZ平面斜线垂直斜线Y和Z同时变化
/// 策略先水平移动Y变化Z不变再垂直移动Z变化Y不变
/// </summary>
private bool HandleYZDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaY, double deltaZ)
{
LogManager.Debug($"[斜线处理] 检测到YZ平面斜线 (deltaY={deltaY:F2}, deltaZ={deltaZ:F2})");
// 插入中间点先水平移动到新Y旧Z目标点自然就是新Y新Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Lateral),
HoistingPointDirection.Lateral);
}
/// <summary>
/// 处理XYZ空间斜线X、Y、Z都变化
/// 策略先水平移动XY平面Z不变再垂直移动Z变化XY不变
/// 水平移动时如果XY也是斜线则进一步拆分
/// </summary>
private bool HandleXYZDiagonal(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint, double deltaX, double deltaY, double deltaZ)
{
LogManager.Debug($"[斜线处理] 检测到XYZ空间斜线 (deltaX={deltaX:F2}, deltaY={deltaY:F2}, deltaZ={deltaZ:F2})");
// 分两步:
// 1. 先水平移动到目标XY保持当前Z
// 2. 再垂直移动到目标Z保持XY不变
// 如果水平移动也是斜线X和Y都变化则先处理XY斜线
if (deltaX > 0.01 && deltaY > 0.01)
{
// 插入中间点先X方向移动保持Y到目标X后再处理Y和Z
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
else
{
// 水平方向只有一个轴变化,直接插入中间点处理垂直斜线
// 选择变化较大的水平轴作为先移动的方向
if (deltaX > deltaY)
{
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Longitudinal),
HoistingPointDirection.Longitudinal);
}
else
{
return InsertIntermediatePoint(points, startIndex, currentPoint, nextPoint,
CreateHorizontalTurnPoint(currentPoint.Position, nextPoint.Position, HoistingPointDirection.Lateral),
HoistingPointDirection.Lateral);
}
}
}
/// <summary>
/// 插入中间点的辅助方法
/// </summary>
private bool InsertIntermediatePoint(IList<PathPoint> points, int startIndex, PathPoint currentPoint, PathPoint nextPoint,
Point3D intermediatePosition, HoistingPointDirection direction)
{
int intermediateIndex = points.Count > 0 ? points.Count : 1;
var intermediatePoint = new PathPoint(intermediatePosition, $"路径点{intermediateIndex}", PathPointType.WayPoint);
intermediatePoint.Direction = direction;
int insertPosition = startIndex + 1;
points.Insert(insertPosition, intermediatePoint);
// 更新所有点的索引
for (int i = 0; i < points.Count; i++)
{
points[i].Index = i;
}
LogManager.Debug($"[斜线处理] 已插入中间点: {intermediatePoint.Name}, 位置: ({intermediatePoint.Position.X:F2}, {intermediatePoint.Position.Y:F2}, {intermediatePoint.Position.Z:F2}) - 方向: {direction}");
LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}");
return true;
}
/// <summary>
/// 正交化路径:将斜线路径转换为轴对齐的直角路径
/// 出现斜线时插入转折点使所有线段平行于X、Y或Z轴
/// </summary>
/// <param name="route">要正交化的路径</param>
/// <param name="startIndex">开始正交化的索引默认从0开始处理所有点。用于增量处理避免修改已处理的点。</param>
/// <returns>是否进行了正交化</returns>
public bool OrthogonalizePath(PathRoute route, int startIndex = 0)
{
if (route == null || route.Points.Count < 2)
{
return false;
}
// 确保startIndex有效
if (startIndex < 0) startIndex = 0;
if (startIndex >= route.Points.Count - 1) return false;
bool hasChanges = false;
var points = route.Points;
double tolerance = 0.001; // 容差,用于判断是否在同一直线上
double minDistance = 1.0; // 最小距离阈值,小于此距离不删除点
// 第一步:处理斜线,插入转折点或修正坐标
// 从startIndex开始只处理新添加的点
int i = startIndex;
while (i < points.Count - 1)
{
int originalCount = points.Count;
if (InsertIntermediatePointForDiagonal(points, i))
{
hasChanges = true;
// 如果插入了点points.Count会增加
// 新插入的点在位置i+1继续检查从i开始
if (points.Count > originalCount)
{
// 插入了点,继续检查当前索引(因为插入点可能需要进一步优化)
LogManager.Info($"[直角路径优化] 索引{i}处插入了点,继续检查");
}
else
{
// 只修改了坐标,继续下一个点
i++;
}
}
else
{
i++;
}
}
// 第二步:清除在同一直线上的多余点
// 只检查从startIndex开始的新插入点避免修改已处理的路径
int startForCollinear = Math.Max(1, startIndex);
i = startForCollinear;
while (i < points.Count - 1)
{
var prevPoint = points[i - 1];
var currentPoint = points[i];
var nextPoint = points[i + 1];
// 只删除普通的WayPoint不删除起点、终点、起吊点、提升点、下降点等特殊点
if (currentPoint.Type != PathPointType.WayPoint)
{
i++;
continue;
}
// 额外检查:不删除提升点和下降点(通过名称识别)
if (currentPoint.Name == "提升点" || currentPoint.Name == "下降点")
{
i++;
continue;
}
// 判断三个点是否在同一直线上(统一在 Canonical 水平面上判断)
Point3D canonicalPrevPoint = ToHoistingCanonicalPoint(prevPoint.Position);
Point3D canonicalCurrentPoint = ToHoistingCanonicalPoint(currentPoint.Position);
Point3D canonicalNextPoint = ToHoistingCanonicalPoint(nextPoint.Position);
double deltaZ1 = Math.Abs(canonicalCurrentPoint.Z - canonicalPrevPoint.Z);
double deltaZ2 = Math.Abs(canonicalNextPoint.Z - canonicalCurrentPoint.Z);
bool sameZ = deltaZ1 < tolerance && deltaZ2 < tolerance;
// 如果Z不同这三个点一定不共线一个是垂直过渡点
if (!sameZ)
{
i++;
continue;
}
// 在同一平面上检查XY方向是否共线
bool sameX = Math.Abs(canonicalPrevPoint.X - canonicalCurrentPoint.X) < tolerance &&
Math.Abs(canonicalCurrentPoint.X - canonicalNextPoint.X) < tolerance;
bool sameY = Math.Abs(canonicalPrevPoint.Y - canonicalCurrentPoint.Y) < tolerance &&
Math.Abs(canonicalCurrentPoint.Y - canonicalNextPoint.Y) < tolerance;
bool isCollinear = sameX || sameY;
// 额外检查:中间点到前后两点的距离都大于最小距离
if (isCollinear)
{
double distToPrev = GeometryHelper.CalculatePointDistance(currentPoint.Position, prevPoint.Position);
double distToNext = GeometryHelper.CalculatePointDistance(currentPoint.Position, nextPoint.Position);
if (distToPrev > minDistance || distToNext > minDistance)
{
// 删除中间点
points.RemoveAt(i);
hasChanges = true;
LogManager.Info($"[直角路径优化] 删除共线的多余点 {i}: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
// 不调整索引,因为删除了一个点,新位置的点会在下一次循环被检查
continue;
}
else
{
LogManager.Debug($"[直角路径优化] 跳过距离过近的共线点 {i}: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
}
}
i++;
}
if (hasChanges)
{
// 注意TotalLength 现在是计算属性,自动从几何数据计算,无需手动更新
LogManager.Info($"[直角路径优化] 路径点优化完成,当前点数: {points.Count}, 路径长度: {route.TotalLength:F2}米");
}
return hasChanges;
}
/// <summary>
/// 检测并移除各水平层内的矩形环路
/// 限制条件:
/// 1. 只处理同一水平层内的点Z坐标必须完全相同只考虑浮点数精度误差
/// 2. 不允许处理起点索引0和终点最后一个点
/// 3. 不允许跨层检测不同Z高度的线段不互相检测
/// 4. 支持多层吊装路径(多个不同高度的水平层)
/// 算法原理按Z坐标分组严格相等对每个水平层分别检测正交路径中的相交线段
/// </summary>
/// <param name="route">路径</param>
/// <returns>是否移除了环路</returns>
public bool RemoveRectangularLoops(PathRoute route)
{
if (route == null || route.Points.Count < 4)
{
return false;
}
bool hasChanges = false;
var points = route.Points;
// 浮点数精度容差用于判断Z坐标是否相等
const double epsilon = 1e-9;
LogManager.Debug($"[环路检测] 开始检测,路径点数: {points.Count}");
// 按Z坐标分组找出所有水平层排除起点和终点
// 使用严格相等比较(只考虑浮点数精度误差)
var layers = new Dictionary<double, List<int>>();
for (int i = 1; i < points.Count - 1; i++) // 跳过起点(0)和终点(Count-1)
{
double z = GetHoistingElevation(points[i].Position);
// 查找是否已有相同Z坐标的层考虑浮点数精度
bool found = false;
foreach (var key in layers.Keys)
{
if (Math.Abs(z - key) < epsilon)
{
layers[key].Add(i);
found = true;
break;
}
}
if (!found)
{
layers[z] = new List<int> { i };
}
}
LogManager.Debug($"[环路检测] 发现{layers.Count}个水平层");
// 按Z坐标从高到低排序处理
var sortedLayers = layers.OrderByDescending(l => l.Key).ToList();
int totalDeletedPoints = 0; // 记录已删除的点数,用于更新后续层的索引
// 对每个水平层分别进行环路检测
for (int layerIdx = 0; layerIdx < sortedLayers.Count; layerIdx++)
{
var layer = sortedLayers[layerIdx];
double layerZ = layer.Key;
var layerIndices = layer.Value.OrderBy(idx => idx).ToList();
// 更新索引:减去之前层删除的点数
for (int i = 0; i < layerIndices.Count; i++)
{
layerIndices[i] -= totalDeletedPoints;
}
// 过滤掉无效索引小于0或超出范围
layerIndices = layerIndices.Where(idx => idx >= 0 && idx < points.Count).ToList();
if (layerIndices.Count < 2)
{
LogManager.Debug($"[环路检测] 层 Z={layerZ:F2} 剩余点太少,跳过");
continue; // 该层点太少,无法形成环路
}
LogManager.Debug($"[环路检测] 处理水平层 Z={layerZ:F2},包含{layerIndices.Count}个,索引范围: {layerIndices.First()}-{layerIndices.Last()}");
// 对该层进行环路检测
int pointsBefore = points.Count;
bool layerHasChanges = RemoveLoopsInLayer(points, layerIndices, epsilon);
int pointsDeleted = pointsBefore - points.Count;
totalDeletedPoints += pointsDeleted;
if (layerHasChanges)
{
hasChanges = true;
}
}
if (hasChanges)
{
LogManager.Debug($"[环路检测] 完成,当前路径点数: {points.Count}");
}
return hasChanges;
}
/// <summary>
/// 对单个水平层进行环路检测和移除
/// </summary>
/// <param name="points">路径点列表</param>
/// <param name="layerIndices">该层的点索引列表(已排序)</param>
/// <param name="epsilon">浮点数精度容差</param>
/// <returns>是否移除了环路</returns>
private bool RemoveLoopsInLayer(List<PathPoint> points, List<int> layerIndices, double epsilon)
{
if (layerIndices.Count < 2)
{
return false;
}
bool hasChanges = false;
double layerZ = GetHoistingElevation(points[layerIndices[0]].Position);
// 在该层内进行环路检测
// n和m是layerIndices列表中的索引位置
int n = 0; // 第一个线段的起始索引在layerIndices中的位置
int m = 2; // 第二个线段相对于n的偏移在layerIndices中的位置
while (n + m + 1 < layerIndices.Count)
{
// 获取实际的路径点索引
int idx1 = layerIndices[n]; // 线段1起点
int idx2 = layerIndices[n + 1]; // 线段1终点
int idx3 = layerIndices[n + m]; // 线段2起点
int idx4 = layerIndices[n + m + 1]; // 线段2终点
// 获取线段坐标
var p1 = points[idx1].Position;
var p2 = points[idx2].Position;
var p3 = points[idx3].Position;
var p4 = points[idx4].Position;
// 确保所有点都在同一层水平线段Z坐标严格相同只考虑浮点数精度
if (Math.Abs(GetHoistingElevation(p1) - layerZ) > epsilon ||
Math.Abs(GetHoistingElevation(p2) - layerZ) > epsilon ||
Math.Abs(GetHoistingElevation(p3) - layerZ) > epsilon ||
Math.Abs(GetHoistingElevation(p4) - layerZ) > epsilon)
{
m++;
if (n + m + 1 >= layerIndices.Count)
{
m = 2;
n++;
}
continue;
}
// 检测两条正交线段是否相交
Point3D intersection;
if (GetOrthogonalSegmentIntersection(p1, p2, p3, p4, out intersection))
{
LogManager.Debug($"[环路检测] 层Z={layerZ:F2} 发现相交: 线段{idx1}({points[idx1].Name}-{points[idx2].Name}) 与 线段{idx3}({points[idx3].Name}-{points[idx4].Name})");
LogManager.Debug($"[环路检测] 交点: ({intersection.X:F4}, {intersection.Y:F4}, {intersection.Z:F4})");
// 检查中间点是否包含特殊点
bool containsSpecialPoint = false;
for (int k = idx1 + 1; k <= idx3; k++)
{
if (points[k].Type != PathPointType.WayPoint ||
points[k].Name == "提升点" ||
points[k].Name == "下降点" ||
points[k].Name == "起吊点" ||
points[k].Name == "落地点")
{
containsSpecialPoint = true;
break;
}
}
if (containsSpecialPoint)
{
LogManager.Debug("[环路检测] 环路包含特殊点,跳过");
m++;
if (n + m + 1 >= layerIndices.Count)
{
m = 2;
n++;
}
continue;
}
// 用交点替换 points[idx2]
points[idx2].Position = intersection;
points[idx2].Name = "路径点";
// 删除中间点 (idx2+1 到 idx3)
int d = idx3 - idx2; // 要删除的点数
for (int i = idx3 + 1; i < points.Count; i++)
{
points[i - d] = points[i];
}
// 移除末尾多余的点
points.RemoveRange(points.Count - d, d);
// 更新layerIndices因为删除了点
for (int i = 0; i < layerIndices.Count; i++)
{
if (layerIndices[i] > idx2 && layerIndices[i] <= idx3)
{
// 这些点被删除了,标记为-1
layerIndices[i] = -1;
}
else if (layerIndices[i] > idx3)
{
// 这些点的索引需要向前移动
layerIndices[i] -= d;
}
}
layerIndices.RemoveAll(idx => idx == -1);
// 从交点重新开始检测
m = 2;
hasChanges = true;
LogManager.Info($"[环路优化] 层Z={layerZ:F2} 已移除环路,删除{d}个点");
continue;
}
m++;
if (n + m + 1 >= layerIndices.Count)
{
m = 2;
n++;
}
}
return hasChanges;
}
/// <summary>
/// 计算两条正交线段的交点
/// </summary>
/// <returns>是否相交(线段内部相交,非端点)</returns>
private bool GetOrthogonalSegmentIntersection(Point3D a1, Point3D a2, Point3D b1, Point3D b2, out Point3D intersection)
{
intersection = new Point3D(0, 0, 0);
Point3D canonicalA1 = ToHoistingCanonicalPoint(a1);
Point3D canonicalA2 = ToHoistingCanonicalPoint(a2);
Point3D canonicalB1 = ToHoistingCanonicalPoint(b1);
Point3D canonicalB2 = ToHoistingCanonicalPoint(b2);
// 判断线段A是水平还是垂直在 Canonical 水平面 XY 中)
bool aIsHorizontal = Math.Abs(canonicalA1.Y - canonicalA2.Y) < 0.001;
bool aIsVertical = Math.Abs(canonicalA1.X - canonicalA2.X) < 0.001;
// 判断线段B是水平还是垂直
bool bIsHorizontal = Math.Abs(canonicalB1.Y - canonicalB2.Y) < 0.001;
bool bIsVertical = Math.Abs(canonicalB1.X - canonicalB2.X) < 0.001;
// 必须一条水平一条垂直才可能形成矩形环路
if ((aIsHorizontal && bIsHorizontal) || (aIsVertical && bIsVertical))
{
return false;
}
// 确保a是水平b是垂直
if (aIsVertical && bIsHorizontal)
{
// 交换
var temp1 = canonicalA1; var temp2 = canonicalA2;
canonicalA1 = canonicalB1; canonicalA2 = canonicalB2;
canonicalB1 = temp1; canonicalB2 = temp2;
aIsHorizontal = true; aIsVertical = false;
bIsHorizontal = false; bIsVertical = true;
}
if (!aIsHorizontal || !bIsVertical)
{
return false;
}
// 水平线段A: y = a1.Y, x范围 [min(a1.X,a2.X), max(a1.X,a2.X)]
// 垂直线段B: x = b1.X, y范围 [min(b1.Y,b2.Y), max(b1.Y,b2.Y)]
double aY = canonicalA1.Y;
double bX = canonicalB1.X;
double aMinX = Math.Min(canonicalA1.X, canonicalA2.X);
double aMaxX = Math.Max(canonicalA1.X, canonicalA2.X);
double bMinY = Math.Min(canonicalB1.Y, canonicalB2.Y);
double bMaxY = Math.Max(canonicalB1.Y, canonicalB2.Y);
// 检查垂直线段的X是否在水平线段的X范围内不包括端点
// 且水平线段的Y是否在垂直线段的Y范围内不包括端点
const double eps = 0.001;
bool xInRange = bX > aMinX + eps && bX < aMaxX - eps;
bool yInRange = aY > bMinY + eps && aY < bMaxY - eps;
if (xInRange && yInRange)
{
intersection = GetHoistingHostAdapter().FromCanonicalPoint(new Point3D(
bX,
aY,
canonicalA1.Z));
return true;
}
return false;
}
/// <summary>
/// 更新路径点位置(保存修改)
/// </summary>
/// <returns>是否成功更新</returns>
public bool UpdatePointPosition()
{
try
{
if (PathEditState != PathEditState.EditingPoint ||
_editingPointIndex == -1 ||
_editingPreviewPoint == null ||
CurrentRoute == null)
{
RaiseErrorOccurred("当前不在修改路径点状态或没有预览点");
return false;
}
// 吊装路径:调用专用方法处理
if (CurrentRoute.PathType == PathType.Hoisting)
{
return UpdateHoistingPathPointPosition();
}
// 更新路径点位置(非吊装路径)
var pointToUpdate = CurrentRoute.Points[_editingPointIndex];
pointToUpdate.Position = _editingPreviewPoint.Position;
// 核心修复:调用统一更新函数
CurrentRoute.RecalculateAndSaveRoute($"修改路径点 {pointToUpdate.Name} 位置");
// 添加历史记录
var historyEntry = new PathHistoryEntry(
CurrentRoute.Id,
PathHistoryOperationType.Edited,
CurrentRoute,
$"修改路径点 {pointToUpdate.Name} 位置");
_historyManager.AddHistoryEntry(historyEntry);
// 清理修改预览可视化(会自动清除预览并重新渲染正常路径)
ClearEditingPreviewVisualization();
// 清理修改状态
ClearEditingState();
// 切换回查看状态
PathEditState = PathEditState.Viewing;
// 停用ToolPlugin
DeactivateToolPlugin();
// 更新可视化
DrawRouteVisualization(CurrentRoute, isAutoPath: false);
// 立即保存到数据库
SavePathToDatabase(CurrentRoute);
// 触发路径点列表更新事件
RaisePathPointsListUpdated(CurrentRoute, "路径点修改完成");
RaiseStatusChanged($"路径点 {pointToUpdate.Name} 修改完成", PathPlanningStatusType.Success);
LogManager.Info($"路径点修改完成: {pointToUpdate.Name}");
return true;
}
catch (Exception ex)
{
RaiseErrorOccurred($"确认修改路径点失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 更新路径点位置(带约束的公共方法)
/// </summary>
/// <remarks>
/// 根据路径类型智能处理坐标更新:
/// - 吊装路径:
/// - 空中路径点提升点、下降点、中间路径点只更新XY坐标保留原有Z坐标吊装高度
/// - 地面路径点起吊点、落地点更新完整XYZ坐标
/// - 其他路径类型更新完整XYZ坐标
/// </remarks>
/// <param name="route">路径</param>
/// <param name="pointIndex">路径点索引</param>
/// <param name="newPosition">新位置</param>
/// <returns>是否成功更新</returns>
public bool UpdatePathPointWithConstraints(PathRoute route, int pointIndex, Point3D newPosition)
{
try
{
if (route == null || pointIndex < 0 || pointIndex >= route.Points.Count)
{
LogManager.Error($"UpdatePathPointWithConstraints: 无效的参数route={route}, pointIndex={pointIndex}");
return false;
}
var pointToUpdate = route.Points[pointIndex];
// 吊装路径智能处理Z坐标
if (route.PathType == PathType.Hoisting)
{
bool isLiftPoint = pointToUpdate.Name == "提升点";
bool isDescendPoint = pointToUpdate.Name == "下降点";
bool isStartPoint = pointToUpdate.Name == "起吊点";
bool isEndPoint = pointToUpdate.Name == "落地点";
// 🔥 新功能允许修改提升点和下降点的Z坐标来调整吊装高度
if (isLiftPoint || isDescendPoint)
{
// 修改提升点或下降点的Z坐标同步更新所有空中路径点的Z坐标
double newHoistingHeight = GetHoistingElevation(newPosition);
double originalZ = GetHoistingElevation(pointToUpdate.Position);
double heightDelta = newHoistingHeight - originalZ;
// 更新当前点的宿主水平位置与高程
pointToUpdate.Position = SetHoistingElevation(newPosition, newHoistingHeight);
// 同步更新所有空中路径点的Z坐标
foreach (var point in route.Points)
{
// 只更新空中路径点(提升点、下降点、中间路径点)
// 跳过当前正在修改的点,避免重复更新
if (point.Name != "起吊点" && point.Name != "落地点" && point != pointToUpdate)
{
point.Position = OffsetPointAlongHoistingUp(point.Position, heightDelta);
}
}
// 🔥 更新路径的提升高度属性(内部使用模型单位)
double hoistingHeightModelUnits = CalculateHoistingHeight(route);
route.LiftHeight = hoistingHeightModelUnits;
LogManager.Info($"[更新路径点] 吊装路径 {pointToUpdate.Name},提升高度已调整为: {UnitsConverter.ConvertToMeters(hoistingHeightModelUnits):F3}米");
// 使用路径的 LiftHeight 属性显示提升高度(内部是模型单位,转换为米显示)
RaiseStatusChanged($"吊装高度: {UnitsConverter.ConvertToMeters(route.LiftHeight):F3}米", PathPlanningStatusType.Info);
}
// 空中路径点非提升点、下降点只更新XY坐标保持原有吊装高度
else if (!isStartPoint && !isEndPoint)
{
double originalZ = GetHoistingElevation(pointToUpdate.Position);
pointToUpdate.Position = SetHoistingElevation(newPosition, originalZ);
LogManager.Info($"[更新路径点] 吊装路径空中点 {pointToUpdate.Name},保留原有高度: {originalZ:F3}");
}
// 起吊点/落地点:更新完整位置
else
{
pointToUpdate.Position = newPosition;
LogManager.Info($"[更新路径点] 吊装路径地面点 {pointToUpdate.Name},更新完整位置");
}
// 吊装路径:自动更新关联点(仅当修改起吊点或落地点时)
// 注意:修改提升点/下降点时,已经在上面同步更新了所有空中路径点,不需要再次调用
if (isStartPoint || isEndPoint)
{
UpdateAerialPathRelatedPoints(route, pointIndex);
}
// 吊装路径:正交化路径(处理斜线和清除多余点)
OrthogonalizePath(route);
// 吊装路径:检测并移除矩形环路
LogManager.Info($"[吊装路径] 修改路径点后检测矩形环路");
RemoveRectangularLoops(route);
}
else
{
// 其他路径类型:更新完整位置
pointToUpdate.Position = newPosition;
LogManager.Info($"[更新路径点] {route.PathType}路径点 {pointToUpdate.Name},更新完整位置");
}
// 调用统一更新函数
route.RecalculateAndSaveRoute($"更新路径点 {pointToUpdate.Name} 位置");
// 保存到数据库
SavePathToDatabase(route);
// 触发路径点列表更新事件
RaisePathPointsListUpdated(route, "路径点更新完成");
LogManager.Info($"路径点更新完成: {pointToUpdate.Name}");
return true;
}
catch (Exception ex)
{
LogManager.Error($"UpdatePathPointWithConstraints 失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 计算吊装路径的提升高度
/// </summary>
/// <param name="route">吊装路径</param>
/// <returns>提升高度(从起吊点到提升点的高度差)</returns>
private HostCoordinateAdapter GetHoistingHostAdapter()
{
return CoordinateSystemManager.Instance.CreateHostAdapter();
}
private Point3D ToHoistingCanonicalPoint(Point3D hostPoint)
{
return GetHoistingHostAdapter().ToCanonicalPoint(hostPoint);
}
private double GetHoistingElevation(Point3D point)
{
return HoistingCoordinateHelper.GetElevation(point, GetHoistingHostAdapter());
}
private Point3D SetHoistingElevation(Point3D point, double elevation)
{
return HoistingCoordinateHelper.SetElevation(point, elevation, GetHoistingHostAdapter());
}
private Point3D OffsetPointAlongHoistingUp(Point3D point, double offset)
{
return HoistingCoordinateHelper.OffsetAlongUp(point, offset, GetHoistingHostAdapter());
}
private Point3D CreateHorizontalTurnPoint(Point3D currentPoint, Point3D nextPoint, HoistingPointDirection direction)
{
bool keepCurrentFirstHorizontalAxis = direction == HoistingPointDirection.Lateral;
return HoistingCoordinateHelper.CreateHorizontalTurnPoint(
currentPoint,
nextPoint,
keepCurrentFirstHorizontalAxis,
GetHoistingHostAdapter());
}
private double CalculateHoistingHeight(PathRoute route)
{
if (route == null || route.PathType != PathType.Hoisting || route.Points.Count < 2)
{
return 0.0;
}
// 找到起吊点和提升点
var startPoint = route.Points.FirstOrDefault(p => p.Name == "起吊点");
var liftPoint = route.Points.FirstOrDefault(p => p.Name == "提升点");
if (startPoint == null || liftPoint == null)
{
return 0.0;
}
// 计算高度差(沿宿主 up
return GetHoistingElevation(liftPoint.Position) - GetHoistingElevation(startPoint.Position);
}
/// <summary>
/// 更新吊装路径点位置专用方法用于3D修改流程
/// </summary>
/// <remarks>
/// 智能处理吊装路径点的坐标更新:
/// - 空中路径点提升点、下降点、中间路径点只更新XY坐标保留原有Z坐标吊装高度
/// - 地面路径点起吊点、落地点更新完整XYZ坐标
/// </remarks>
/// <returns>是否成功更新</returns>
private bool UpdateHoistingPathPointPosition()
{
try
{
if (PathEditState != PathEditState.EditingPoint ||
_editingPointIndex == -1 ||
_editingPreviewPoint == null ||
CurrentRoute == null)
{
RaiseErrorOccurred("当前不在修改路径点状态或没有预览点");
return false;
}
// 调用公共方法处理
bool success = UpdatePathPointWithConstraints(CurrentRoute, _editingPointIndex, _editingPreviewPoint.Position);
if (success)
{
// 添加历史记录
var pointToUpdate = CurrentRoute.Points[_editingPointIndex];
var historyEntry = new PathHistoryEntry(
CurrentRoute.Id,
PathHistoryOperationType.Edited,
CurrentRoute,
$"修改吊装路径点 {pointToUpdate.Name} 位置");
_historyManager.AddHistoryEntry(historyEntry);
// 清理修改预览可视化(会自动清除预览并重新渲染正常路径)
ClearEditingPreviewVisualization();
// 清理修改状态
ClearEditingState();
// 切换回查看状态
PathEditState = PathEditState.Viewing;
// 停用ToolPlugin
DeactivateToolPlugin();
// 更新可视化
DrawRouteVisualization(CurrentRoute, isAutoPath: false);
RaiseStatusChanged($"吊装路径点 {pointToUpdate.Name} 修改完成", PathPlanningStatusType.Success);
}
return success;
}
catch (Exception ex)
{
RaiseErrorOccurred($"确认修改吊装路径点失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 取消修改路径点
/// </summary>
/// <returns>是否成功取消修改</returns>
public bool CancelEditPoint()
{
try
{
if (PathEditState != PathEditState.EditingPoint)
{
return true; // 已经不在修改状态,认为成功
}
// 清理修改状态
ClearEditingState();
// 切换回查看状态
PathEditState = PathEditState.Viewing;
// 停用ToolPlugin
DeactivateToolPlugin();
// 清理预览可视化
ClearEditingPreviewVisualization();
RaiseStatusChanged("已取消路径点修改", PathPlanningStatusType.Info);
LogManager.Info("已取消路径点修改");
return true;
}
catch (Exception ex)
{
RaiseErrorOccurred($"取消修改路径点失败: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 清理修改路径点的状态
/// </summary>
private void ClearEditingState()
{
_editingPointIndex = -1;
_originalPoint = null;
_editingPreviewPoint = null;
}
/// <summary>
/// 更新修改路径点时的预览可视化
/// </summary>
private void UpdateEditingPreviewVisualization()
{
try
{
if (_renderPlugin == null || CurrentRoute == null || _editingPreviewPoint == null)
{
return;
}
// 创建预览路径,替换修改中的路径点
var previewRoute = CurrentRoute.Clone() as PathRoute;
previewRoute.Points[_editingPointIndex] = _editingPreviewPoint;
// 渲染预览路径(使用特殊的预览样式)
_renderPlugin.RenderPreviewPath(previewRoute);
}
catch (Exception ex)
{
LogManager.Error($"更新修改预览可视化失败: {ex.Message}");
}
}
/// <summary>
/// 清理修改路径点时的预览可视化
/// </summary>
private void ClearEditingPreviewVisualization()
{
try
{
if (_renderPlugin == null)
{
return;
}
// 清理预览渲染,恢复原始路径渲染
_renderPlugin.ClearPreview();
if (CurrentRoute != null)
{
_renderPlugin.RenderPath(CurrentRoute);
}
}
catch (Exception ex)
{
LogManager.Error($"清理修改预览可视化失败: {ex.Message}");
}
}
#endregion
/// <summary>
/// 取消当前编辑
/// </summary>
/// <returns>是否成功取消编辑</returns>
public bool CancelEditing()
{
if (!IsInEditableState)
{
RaiseErrorOccurred("当前不在编辑状态,无法取消编辑");
return false;
}
try
{
// 如果是创建模式,清理当前路径
if (_pathEditState == PathEditState.Creating && CurrentRoute != null)
{
CurrentRoute.Points.Clear();
LogManager.Info("已清理新建路径的临时数据");
}
// 切换回查看状态
SwitchToViewingState();
RaiseStatusChanged("路径编辑已取消", PathPlanningStatusType.Info);
return true;
}
catch (Exception ex)
{
RaiseErrorOccurred($"取消编辑失败: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 在3D视图中添加路径点
/// </summary>
/// <param name="worldPoint">3D世界坐标</param>
/// <param name="pointType">路径点类型为null时使用当前类型</param>
/// <returns>添加的路径点失败时返回null</returns>
public PathPoint AddPathPointIn3D(Point3D worldPoint, PathPointType? pointType = null)
{
// 确保在编辑状态下才能添加点
if (!IsInEditableState)
{
RaiseErrorOccurred("不在编辑状态,无法添加路径点");
return null;
}
// 如果没有当前路径,则无法添加
if (CurrentRoute == null)
{
RaiseErrorOccurred("内部错误:当前路径丢失");
return null;
}
try
{
// 确定路径点类型
PathPointType finalPointType;
if (pointType.HasValue)
{
finalPointType = pointType.Value;
}
else
{
// 自动判断类型:第一个点为起点,其余为路径点
finalPointType = (CurrentRoute.Points.Count == 0)
? PathPointType.StartPoint
: PathPointType.WayPoint;
}
// 创建路径点
var pathPoint = new PathPoint
{
Name = GeneratePointName(finalPointType),
Position = worldPoint,
Type = finalPointType
};
CurrentRoute.AddPoint(pathPoint);
LogManager.Info($"路径点已添加: {pathPoint.Name}, 位置: ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2})");
// 绘制3D路径可视化使用与自动路径规划相同的方法
try
{
DrawRouteVisualization(CurrentRoute, isAutoPath: false);
LogManager.Info($"手工路径3D可视化已更新: {pathPoint.Name}");
}
catch (Exception renderEx)
{
LogManager.Error($"绘制手工路径3D可视化失败: {renderEx.Message}");
}
// 触发路径点添加事件
RaisePathPointOperation(PathPointOperationType.Added, pathPoint, CurrentRoute);
// 触发路径列表更新事件
RaisePathPointsListUpdated(CurrentRoute, "添加路径点");
return pathPoint;
}
catch (Exception ex)
{
RaiseErrorOccurred($"添加路径点失败: {ex.Message}", ex);
return null;
}
}
/// <summary>
/// 在3D视图中插入路径点在指定位置
/// </summary>
/// <param name="worldPoint">3D世界坐标</param>
/// <param name="pointType">点类型</param>
/// <returns>插入的路径点</returns>
public PathPoint InsertPathPointIn3D(Point3D worldPoint, PathPointType? pointType = null)
{
var insertInfo = FindNearestLineSegmentWithIndex(worldPoint, CurrentRoute.Points);
if (!insertInfo.HasValue)
{
throw new InvalidOperationException("无法确定插入位置");
}
PathPointType finalPointType = pointType ?? PathPointType.WayPoint;
var pathPoint = new PathPoint
{
Name = GeneratePointName(finalPointType),
Position = worldPoint,
Type = finalPointType
};
CurrentRoute.InsertPoint(pathPoint, insertInfo.Value.insertIndex);
DrawRouteVisualization(CurrentRoute, isAutoPath: false);
RaisePathPointOperation(PathPointOperationType.Inserted, pathPoint, CurrentRoute);
RaisePathPointsListUpdated(CurrentRoute, "插入路径点");
return pathPoint;
}
/// <summary>
/// 设置预览点位置(仅用于预览,不添加到路径中)
/// </summary>
/// <param name="worldPoint">3D世界坐标</param>
/// <param name="pointType">点类型</param>
/// <returns>预览点对象</returns>
public PathPoint SetPreviewPoint(Point3D worldPoint, PathPointType? pointType = null)
{
// 确保在编辑状态下才能设置预览点
if (!IsInEditableState)
{
RaiseErrorOccurred("不在编辑状态,无法设置预览点");
return null;
}
// 如果没有当前路径,则无法设置预览点
if (CurrentRoute == null)
{
RaiseErrorOccurred("内部错误:当前路径丢失");
return null;
}
try
{
// 确定路径点类型
PathPointType finalPointType;
if (pointType.HasValue)
{
finalPointType = pointType.Value;
}
else
{
// 自动判断类型:第一个点为起点,其余为路径点
finalPointType = (CurrentRoute.Points.Count == 0)
? PathPointType.StartPoint
: PathPointType.WayPoint;
}
// 计算预览点应该插入的位置
_previewInsertIndex = -1; // 重置插入索引
if (CurrentRoute.Points.Count >= 2)
{
// 如果路径中有至少2个点计算最佳插入位置
var nearestSegment = FindNearestLineSegmentWithIndex(worldPoint, CurrentRoute.Points.ToList());
if (nearestSegment.HasValue)
{
_previewInsertIndex = nearestSegment.Value.insertIndex;
LogManager.Info($"预览点计算出插入索引: {_previewInsertIndex},位于 {nearestSegment.Value.prevPoint.Name} 和 {nearestSegment.Value.nextPoint.Name} 之间");
}
}
// 创建预览点(不添加到路径中)
_previewPoint = new PathPoint
{
Name = $"预览-{GeneratePointName(finalPointType)}",
Position = worldPoint,
Type = finalPointType
};
_isPreviewMode = true;
LogManager.Info($"预览点已设置: {_previewPoint.Name}, 位置: ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2}), 插入索引: {_previewInsertIndex}");
// 绘制预览点可视化(灰色)
try
{
DrawPreviewPointVisualization(_previewPoint);
LogManager.Info($"预览点3D可视化已更新: {_previewPoint.Name}");
// 如果路径中有足够的点,绘制预览连线
if (CurrentRoute.Points.Count >= 2)
{
DrawPreviewLinesVisualization(_previewPoint, CurrentRoute.Points.ToList());
LogManager.Info($"预览连线已绘制: {_previewPoint.Name}");
}
}
catch (Exception renderEx)
{
LogManager.Error($"绘制预览点3D可视化失败: {renderEx.Message}");
}
return _previewPoint;
}
catch (Exception ex)
{
RaiseErrorOccurred($"设置预览点失败: {ex.Message}", ex);
return null;
}
}
/// <summary>
/// 将预览点添加为正式路径点
/// </summary>
/// <returns>添加的路径点如果失败返回null</returns>
public PathPoint ConvertPreviewToPathPoint()
{
if (!_isPreviewMode || _previewPoint == null)
{
RaiseErrorOccurred("没有预览点可确认");
return null;
}
if (CurrentRoute == null)
{
RaiseErrorOccurred("内部错误:当前路径丢失");
return null;
}
try
{
PathPoint confirmPoint;
// 吊装路径:使用专门的空中路径点生成逻辑
if (CurrentRoute.PathType == PathType.Hoisting)
{
LogManager.Info($"[预览点-吊装路径] 开始创建空中路径点");
// 获取上一个路径点
Point3D previousPoint;
int previousPointIndex;
if (_previewInsertIndex >= 0 && _previewInsertIndex < CurrentRoute.Points.Count)
{
// 插入模式:上一个点是插入位置前的一个点
previousPointIndex = _previewInsertIndex - 1;
previousPoint = CurrentRoute.Points[previousPointIndex].Position;
LogManager.Info($"[预览点-吊装路径] 插入模式,上一个点索引: {previousPointIndex}, 名称: {CurrentRoute.Points[previousPointIndex].Name}");
}
else if (CurrentRoute.Points.Count > 0)
{
// 追加模式:上一个点是最后一个点
previousPointIndex = CurrentRoute.Points.Count - 1;
previousPoint = CurrentRoute.Points[previousPointIndex].Position;
LogManager.Info($"[预览点-吊装路径] 追加模式,上一个点索引: {previousPointIndex}, 名称: {CurrentRoute.Points[previousPointIndex].Name}");
}
else
{
LogManager.Error("[预览点-吊装路径] 没有上一个路径点");
return null;
}
// 获取吊装高度LiftHeight内部是模型单位
double liftHeightModelUnits = CurrentRoute.LiftHeight > 0
? CurrentRoute.LiftHeight
: UnitsConverter.ConvertFromMeters(3.0);
double targetElevation = GetHoistingElevation(CurrentRoute.Points[0].Position) + liftHeightModelUnits;
// 使用 AerialPathGenerator 生成空中路径点
var generator = new AerialPathGenerator();
confirmPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
_previewPoint.Position, // 用户点击的地面位置
targetElevation,
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
LogManager.Info($"[预览点-吊装路径] 已生成空中路径点: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})");
// 插入到正确位置
int optimizeStartIndex;
if (_previewInsertIndex >= 0 && _previewInsertIndex <= CurrentRoute.Points.Count)
{
CurrentRoute.InsertPoint(confirmPoint, _previewInsertIndex);
LogManager.Info($"[预览点-吊装路径] 已插入到索引 {_previewInsertIndex}");
// 从插入位置的前一个点开始优化
optimizeStartIndex = Math.Max(0, _previewInsertIndex - 1);
}
else
{
// 追加模式:记录添加前的点数
optimizeStartIndex = Math.Max(0, CurrentRoute.Points.Count - 1);
CurrentRoute.AddPoint(confirmPoint);
LogManager.Info($"[预览点-吊装路径] 已追加到末尾");
}
// 调用正交化方法处理斜线、微小转折和共线点(增量处理)
OrthogonalizePath(CurrentRoute, optimizeStartIndex);
LogManager.Info($"[预览点-吊装路径] 路径优化完成,当前路径点数: {CurrentRoute.Points.Count}");
}
else
{
// 非吊装路径:创建普通路径点
confirmPoint = new PathPoint
{
Name = GeneratePointName(_previewPoint.Type),
Position = _previewPoint.Position,
Type = _previewPoint.Type
};
// 根据保存的插入索引决定添加方式
if (_previewInsertIndex >= 0 && _previewInsertIndex <= CurrentRoute.Points.Count)
{
CurrentRoute.InsertPoint(confirmPoint, _previewInsertIndex);
}
else
{
CurrentRoute.AddPoint(confirmPoint);
}
}
// 清除预览状态
ClearPreviewPoint();
// 绘制3D路径可视化
DrawRouteVisualization(CurrentRoute, isAutoPath: false);
// 触发路径点添加事件
RaisePathPointOperation(PathPointOperationType.Added, confirmPoint, CurrentRoute);
// 触发路径列表更新事件
RaisePathPointsListUpdated(CurrentRoute, "确认添加预览点");
return confirmPoint;
}
catch (Exception ex)
{
RaiseErrorOccurred($"确认预览点失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 清除预览点
/// </summary>
public void ClearPreviewPoint()
{
if (_isPreviewMode && _previewPoint != null)
{
LogManager.Info($"清除预览点: {_previewPoint.Name}");
// 清除预览点可视化
try
{
ClearPreviewPointVisualization();
}
catch (Exception ex)
{
LogManager.Error($"清除预览点可视化失败: {ex.Message}");
}
}
_previewPoint = null;
_isPreviewMode = false;
_previewInsertIndex = -1; // 清除保存的插入索引
}
/// <summary>
/// 绘制预览点可视化(灰色)
/// </summary>
/// <param name="previewPoint">预览点</param>
private void DrawPreviewPointVisualization(PathPoint previewPoint)
{
if (_renderPlugin != null && previewPoint != null)
{
// 先清除之前的预览点
_renderPlugin.ClearPreviewPoint();
// 绘制新的灰色预览点
_renderPlugin.RenderPreviewPoint(previewPoint);
LogManager.Info($"预览点可视化已绘制: {previewPoint.Name}");
}
}
/// <summary>
/// 绘制预览连线的3D可视化灰色
/// </summary>
/// <param name="previewPoint">预览点</param>
/// <param name="pathPoints">当前路径点列表</param>
private void DrawPreviewLinesVisualization(PathPoint previewPoint, List<PathPoint> pathPoints)
{
if (_renderPlugin != null && previewPoint != null && pathPoints != null)
{
// 绘制预览连线
_renderPlugin.RenderPreviewLines(previewPoint, pathPoints);
LogManager.Info($"预览连线可视化已绘制: {previewPoint.Name}");
}
}
/// <summary>
/// 清除预览点可视化
/// </summary>
private void ClearPreviewPointVisualization()
{
if (_renderPlugin != null)
{
_renderPlugin.ClearPreviewPoint();
LogManager.Info("预览点可视化已清除");
}
}
/// <summary>
/// 获取路径统计信息
/// </summary>
/// <returns>统计信息字符串</returns>
public string GetStatistics()
{
var stats = $"通道数量: {_walkableAreas.Count}\n";
stats += $"路径数量: {_routes.Count}\n";
if (_currentRoute != null)
{
stats += $"当前路径: {_currentRoute.Name}\n";
stats += $"路径点数: {_currentRoute.Points.Count}\n";
stats += $"路径长度: {_currentRoute.TotalLength:F2}米\n";
stats += $"预估时间: {_currentRoute.EstimatedTime:F1}秒\n";
}
if (_combinedChannelBounds != null)
{
double scaleX = 0, scaleY = 0;
_coordinateConverter?.GetMapScale(out scaleX, out scaleY);
stats += $"通道范围: {_combinedChannelBounds.MinPoint.X:F2},{_combinedChannelBounds.MinPoint.Y:F2} - {_combinedChannelBounds.MaxPoint.X:F2},{_combinedChannelBounds.MaxPoint.Y:F2}\n";
stats += $"地图缩放: {scaleX:F4}, {scaleY:F4}";
}
return stats;
}
/// <summary>
/// 验证路径管理器状态
/// </summary>
/// <returns>验证结果</returns>
public PathPlanningResult ValidateManagerState()
{
try
{
var errors = new List<string>();
// 检查基础组件
if (_categoryManager == null)
errors.Add("类别属性管理器未初始化");
if (_uiStateManager == null)
errors.Add("UI状态管理器未初始化");
// 检查Navisworks环境
var document = Application.ActiveDocument;
if (document == null)
errors.Add("当前没有活动的Navisworks文档");
else if (document.Models == null || !document.Models.Any())
errors.Add("当前文档中没有加载的模型");
// 检查通道选择状态
if (_walkableAreas.Count == 0)
errors.Add("没有选择任何通道模型");
if (errors.Count > 0)
{
return PathPlanningResult.ValidationFailure($"路径管理器状态验证失败: {string.Join("; ", errors)}");
}
return PathPlanningResult.Success("路径管理器状态验证通过");
}
catch (Exception ex)
{
LogManager.Error("验证路径管理器状态时发生异常", ex);
return PathPlanningResult.ValidationFailure($"状态验证异常: {ex.Message}");
}
}
/// <summary>
/// 验证路径有效性
/// </summary>
/// <param name="route">要验证的路径</param>
/// <returns>验证结果</returns>
public PathPlanningResult ValidateRoute(PathRoute route)
{
try
{
if (route == null)
return PathPlanningResult.ValidationFailure("路径不能为空");
var errors = new List<string>();
var warnings = new List<string>();
// 基本验证
if (string.IsNullOrWhiteSpace(route.Name))
errors.Add("路径名称不能为空");
if (route.Points.Count < 2)
errors.Add("路径至少需要包含2个点");
// 点类型验证
var startPoints = route.Points.Where(p => p.Type == PathPointType.StartPoint).ToList();
var endPoints = route.Points.Where(p => p.Type == PathPointType.EndPoint).ToList();
if (startPoints.Count == 0)
errors.Add("路径必须包含至少一个起点");
else if (startPoints.Count > 1)
warnings.Add($"路径包含多个起点({startPoints.Count}个)");
if (endPoints.Count == 0)
errors.Add("路径必须包含至少一个终点");
else if (endPoints.Count > 1)
warnings.Add($"路径包含多个终点({endPoints.Count}个)");
// 坐标验证
foreach (var point in route.Points)
{
if (point.Position == null)
errors.Add($"路径点 '{point.Name}' 缺少位置信息");
}
// 路径长度验证
if (route.TotalLength < 0.1)
warnings.Add("路径长度过短,可能存在重复点");
else if (route.TotalLength > 10000)
warnings.Add("路径长度过长,可能影响性能");
// 构建结果
if (errors.Count > 0)
{
return PathPlanningResult.ValidationFailure($"路径验证失败: {string.Join("; ", errors)}");
}
var message = "路径验证通过";
if (warnings.Count > 0)
{
message += $" (警告: {string.Join("; ", warnings)})";
}
return PathPlanningResult.Success(message);
}
catch (Exception ex)
{
LogManager.Error($"验证路径 '{route?.Name}' 时发生异常", ex);
return PathPlanningResult.ValidationFailure($"路径验证异常: {ex.Message}");
}
}
#endregion
#region
private void CalculateCombinedBounds()
{
// 保留原有实现但移除UI相关调用
if (!_walkableAreas.Any())
{
_combinedChannelBounds = null;
return;
}
try
{
var conversionFactor = GetUnitsToMetersConversionFactor();
var units = Application.ActiveDocument.Units;
var allBounds = new List<BoundingBox3D>();
foreach (var channel in _walkableAreas)
{
try
{
var originalBoundingBox = channel.BoundingBox();
if (originalBoundingBox != null)
{
var boundingBoxInMeters = ConvertBoundingBoxToMeters(originalBoundingBox, conversionFactor);
allBounds.Add(boundingBoxInMeters);
}
}
catch
{
continue;
}
}
if (allBounds.Any())
{
var minX = allBounds.Min(b => b.Min.X);
var minY = allBounds.Min(b => b.Min.Y);
var minZ = allBounds.Min(b => b.Min.Z);
var maxX = allBounds.Max(b => b.Max.X);
var maxY = allBounds.Max(b => b.Max.Y);
var maxZ = allBounds.Max(b => b.Max.Z);
var combinedBoundingBox = new BoundingBox3D(
new Point3D(minX, minY, minZ),
new Point3D(maxX, maxY, maxZ)
);
_combinedChannelBounds = new ChannelBounds(combinedBoundingBox);
RaiseStatusChanged($"已计算通道边界: {_combinedChannelBounds.MinPoint.X:F2},{_combinedChannelBounds.MinPoint.Y:F2} - {_combinedChannelBounds.MaxPoint.X:F2},{_combinedChannelBounds.MaxPoint.Y:F2}", PathPlanningStatusType.Info);
}
}
catch (Exception ex)
{
RaiseErrorOccurred($"计算通道边界时发生错误: {ex.Message}", ex);
}
}
/// <summary>
/// 计算预估时间
/// 使用配置中的速度限制(转换为模型单位)
/// </summary>
private void CalculateEstimatedTime(PathRoute route)
{
// 从配置获取速度限制(模型单位/秒)
double speedLimit = ConfigManager.Instance.Current.Logistics.SpeedLimit;
if (speedLimit <= 0)
{
speedLimit = 0.8; // 默认 0.8 模型单位/秒约0.8米/秒)
}
route.EstimatedTime = route.TotalLength / speedLimit;
}
/// <summary>
/// 生成路径点名称
/// </summary>
private string GeneratePointName(PathPointType pointType)
{
var currentPoints = _currentRoute?.Points ?? new List<PathPoint>();
var typeCount = currentPoints.Count(p => p.Type == pointType) + 1;
switch (pointType)
{
case PathPointType.StartPoint:
// 起点固定显示为"起点",不带编号
return "起点";
case PathPointType.EndPoint:
// 终点固定显示为"终点",不带编号
return "终点";
case PathPointType.WayPoint:
return $"路径点{typeCount}";
default:
return $"点{typeCount}";
}
}
/// <summary>
/// 自动选择所有可通行的物流模型
/// </summary>
private void AutoSelectLogisticsChannels()
{
try
{
var document = Application.ActiveDocument;
if (document?.Models == null) return;
_walkableAreas.Clear();
LogManager.Info("[通道自动选择] 开始搜索可通行的物流模型");
// 使用CategoryAttributeManager统一接口获取物流项目
var allLogisticsItemsCollection = CategoryAttributeManager.GetAllLogisticsItems();
LogManager.Info($"[通道自动选择] CategoryAttributeManager找到 {allLogisticsItemsCollection.Count} 个具有物流属性的模型项");
// 筛选出可通行的物流模型
var traversableItems = CategoryAttributeManager.FilterTraversableItems(allLogisticsItemsCollection);
LogManager.Info($"[通道自动选择] 筛选出 {traversableItems.Count} 个可通行的物流模型");
// 将可通行的物流模型添加到_selectedChannels
foreach (ModelItem item in traversableItems)
{
_walkableAreas.Add(item);
LogManager.Info($"[通道自动选择] 添加可通行模型: '{item.DisplayName}'");
}
if (_walkableAreas.Count > 0)
{
// 计算组合边界
CalculateCombinedBounds();
// 触发通道选择变更事件
RaiseChannelSelectionChanged(_walkableAreas, "自动选择");
LogManager.Info($"[通道自动选择] ✅ 自动选择了 {_walkableAreas.Count} 个可通行的物流模型");
}
else
{
LogManager.Warning("[通道自动选择] ❌ 未找到任何可通行的物流模型");
}
}
catch (Exception ex)
{
LogManager.Error($"[通道自动选择] 自动选择可通行物流模型失败: {ex.Message}");
RaiseErrorOccurred($"自动选择可通行物流模型失败: {ex.Message}", ex);
}
}
/// <summary>
/// 智能ToolPlugin管理根据编辑状态自动激活或停用
/// </summary>
private void ManageToolPluginForEditState()
{
try
{
if (IsInEditableState)
{
// 编辑状态确保ToolPlugin已激活
if (!_isToolPluginActive)
{
ActivateToolPlugin();
}
}
else
{
// 查看状态确保ToolPlugin已停用
if (_isToolPluginActive)
{
DeactivateToolPlugin();
}
}
}
catch (Exception ex)
{
LogManager.Error($"管理ToolPlugin状态时发生错误: {ex.Message}");
RaiseErrorOccurred($"管理ToolPlugin状态失败: {ex.Message}", ex);
}
}
/// <summary>
/// 激活自定义ToolPlugin进行精确点击检测
/// </summary>
/// <param name="subscribeToEvents">是否订阅鼠标事件</param>
private bool ActivateToolPlugin(bool subscribeToEvents = true)
{
// 如果已经激活,检查是否需要订阅事件
if (_isToolPluginActive)
{
LogManager.Debug("[ToolPlugin] ToolPlugin已激活检查事件订阅状态");
if (subscribeToEvents)
{
// 确保事件订阅(避免重复订阅)
PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked;
PathClickToolPlugin.MouseClicked += OnToolPluginMouseClicked;
LogManager.Debug("[ToolPlugin] 已确保PathPlanningManager事件订阅");
}
return true;
}
try
{
LogManager.Debug("[ToolPlugin] ===== 开始激活ToolPlugin =====");
LogManager.Debug($"[ToolPlugin] 订阅事件: {subscribeToEvents}");
LogManager.Debug($"[ToolPlugin] 当前应用程序状态: {Application.IsAutomated}");
LogManager.Debug($"[ToolPlugin] 当前文档状态: {Application.ActiveDocument?.Title ?? "NULL"}");
// 1. 加载插件程序集
LogManager.Debug("[ToolPlugin] 步骤1: 加载插件程序集");
var assemblyPath = PathClickToolPlugin.AssemblyPath;
LogManager.Debug($"[ToolPlugin] 程序集路径: {assemblyPath}");
LogManager.Debug($"[ToolPlugin] 程序集文件是否存在: {System.IO.File.Exists(assemblyPath)}");
Application.Plugins.AddPluginAssembly(assemblyPath);
LogManager.Debug("[ToolPlugin] ✓ 程序集加载完成");
// 2. 查找插件
LogManager.Debug("[ToolPlugin] 步骤2: 查找插件");
ToolPluginRecord toolPluginRecord = (ToolPluginRecord)Application.Plugins.FindPlugin("PathClickTool.NavisworksTransport");
if (toolPluginRecord == null)
{
LogManager.Error("[ToolPlugin] ✗ 错误: 无法找到PathClickTool插件");
return false;
}
LogManager.Debug("[ToolPlugin] ✓ 插件查找成功");
// 3. 加载插件
LogManager.Debug("[ToolPlugin] 步骤3: 加载插件");
var loadedPlugin = toolPluginRecord.LoadPlugin();
if (loadedPlugin == null)
{
LogManager.Error("[ToolPlugin] ✗ 错误: 插件加载失败");
return false;
}
LogManager.Debug("[ToolPlugin] ✓ 插件加载成功");
// 4. 设置为活动工具
LogManager.Debug("[ToolPlugin] 步骤4: 设置为活动工具");
// 先重置工具状态,清除可能的导航工具
// 这一步很重要确保从导航工具如Pan、Orbit切换回自定义工具
Application.MainDocument.Tool.Value = Tool.None;
LogManager.Debug("[ToolPlugin] 已重置工具状态为None");
// 然后设置自定义工具插件
Application.MainDocument.Tool.SetCustomToolPlugin(loadedPlugin);
LogManager.Debug("[ToolPlugin] ✓ 工具设置成功");
// 5. 根据参数决定是否订阅点击事件
if (subscribeToEvents)
{
LogManager.Debug("[ToolPlugin] 步骤5: 订阅点击事件");
PathClickToolPlugin.MouseClicked += OnToolPluginMouseClicked;
LogManager.Debug("[ToolPlugin] ✓ PathPlanningManager事件订阅成功");
}
else
{
LogManager.Debug("[ToolPlugin] 步骤5: 跳过事件订阅(由外部组件管理)");
}
_isToolPluginActive = true;
LogManager.Info("[ToolPlugin] ===== ToolPlugin激活完成 =====");
return true;
}
catch (Exception ex)
{
LogManager.Error($"[ToolPlugin] 激活异常: {ex.Message}");
LogManager.Error($"[ToolPlugin] 堆栈: {ex.StackTrace}");
return false;
}
}
/// <summary>
/// 重新激活ToolPlugin供InputMonitor调用
/// 用于在工具失活后快速恢复,不重复订阅事件
/// </summary>
public void ReactivateToolPlugin()
{
try
{
// 检查当前状态和实际工具值
if ((PathEditState == PathEditState.Creating ||
PathEditState == PathEditState.AddingPoints ||
PathEditState == PathEditState.EditingPoint))
{
// 检查当前工具是否为自定义工具,如果不是则需要重新激活
var currentTool = Application.MainDocument.Tool.Value;
if (currentTool != Tool.CustomToolPlugin)
{
LogManager.Debug($"[ReactivateToolPlugin] 当前工具为{currentTool}开始重新激活ToolPlugin");
// 重要先将激活标志设置为false强制执行完整的激活流程
_isToolPluginActive = false;
// 调用现有的激活方法,但不重复订阅事件
if (ActivateToolPlugin(false))
{
LogManager.Info("[ReactivateToolPlugin] ToolPlugin重新激活成功");
RaiseStatusChanged("工具已重新激活", PathPlanningStatusType.Info);
}
else
{
LogManager.Warning("[ReactivateToolPlugin] ToolPlugin重新激活失败");
RaiseErrorOccurred("无法重新激活编辑工具");
}
}
else
{
LogManager.Debug("[ReactivateToolPlugin] 当前已是CustomToolPlugin无需重新激活");
}
}
else
{
LogManager.Debug($"[ReactivateToolPlugin] 跳过重新激活 - PathEditState: {PathEditState}");
}
}
catch (Exception ex)
{
LogManager.Error($"[ReactivateToolPlugin] 重新激活失败: {ex.Message}");
RaiseErrorOccurred($"重新激活工具失败: {ex.Message}", ex);
}
}
/// <summary>
/// 处理ToolPlugin的鼠标点击事件
/// </summary>
private void OnToolPluginMouseClicked(object sender, PickItemResult pickResult)
{
try
{
// 如果在自动路径规划模式则跳过处理应该由PathEditingViewModel处理
if (IsInAutoPathMode)
{
LogManager.Debug("[ToolPlugin事件] 当前在自动路径规划模式跳过PathPlanningManager处理");
return;
}
// 手动路径编辑处理
ProcessManualPathEditing(pickResult);
}
catch (Exception ex)
{
LogManager.Error($"[ToolPlugin事件] 处理异常: {ex.Message}");
LogManager.Error($"[ToolPlugin事件] 堆栈: {ex.StackTrace}");
}
}
/// <summary>
/// 处理手动路径编辑的鼠标点击
/// </summary>
private void ProcessManualPathEditing(PickItemResult pickResult)
{
try
{
Point3D clickedPoint = pickResult.Point;
// 吊装模式处理
if (_enableHoistingMode)
{
if (_hoistingStartPoint == null)
{
// 第一次点击:记录起点
_hoistingStartPoint = clickedPoint;
LogManager.Info($"[吊装模式] 已设置起点: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2}),弹出吊装高度设置对话框");
// 渲染起点标记
var startPoint = new PathPoint(clickedPoint, "吊装起点", PathPointType.StartPoint);
PathPointRenderPlugin.Instance.RenderPreviewPoint(startPoint);
// 在UI线程中弹出吊装高度设置对话框
_uiStateManager.QueueUIUpdate(() =>
{
try
{
var heightDialog = new NavisworksTransport.UI.WPF.Views.AerialHeightDialog(defaultHeight: 3.0);
bool? dialogResult = heightDialog.ShowDialog();
// 如果用户取消,则清除起点并退出吊装模式
if (dialogResult != true)
{
LogManager.Info("用户取消了吊装高度设置,清除起点并退出吊装模式");
_hoistingStartPoint = null;
_enableHoistingMode = false;
_hoistingGroundIntermediatePoints.Clear();
PathPointRenderPlugin.Instance.ClearPreviewPoint();
StopClickTool();
RaiseStatusChanged("已取消创建吊装路径", PathPlanningStatusType.Info);
return;
}
// 获取用户设置的吊装高度(转换为模型单位存储)
double liftHeightMetersInput = heightDialog.LiftHeightMeters;
double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMetersInput);
_hoistingLiftHeight = liftHeightModelUnits;
LogManager.Info($"用户设置的吊装高度: {liftHeightMetersInput:F2}米");
// 保存吊装高度到当前路径(内部使用模型单位)
if (CurrentRoute != null)
{
CurrentRoute.LiftHeight = liftHeightModelUnits;
}
// 立即生成起吊点和提升点
try
{
var hoistingStartPoint = new PathPoint(_hoistingStartPoint, "起吊点", PathPointType.StartPoint);
hoistingStartPoint.Direction = HoistingPointDirection.Vertical;
CurrentRoute.AddPoint(hoistingStartPoint);
LogManager.Debug($"[吊装模式] 已添加起吊点: ({hoistingStartPoint.Position.X:F2}, {hoistingStartPoint.Position.Y:F2}, {hoistingStartPoint.Position.Z:F2})");
// 使用已转换的模型单位高度
var liftPoint = new PathPoint(
OffsetPointAlongHoistingUp(_hoistingStartPoint, liftHeightModelUnits),
"提升点",
PathPointType.WayPoint);
liftPoint.Direction = HoistingPointDirection.Vertical;
CurrentRoute.AddPoint(liftPoint);
LogManager.Debug($"[吊装模式] 已添加提升点: ({liftPoint.Position.X:F2}, {liftPoint.Position.Y:F2}, {liftPoint.Position.Z:F2})");
// 渲染路径
if (_renderPlugin != null)
{
_renderPlugin.RenderPath(CurrentRoute);
}
}
catch (Exception ex)
{
LogManager.Error($"[吊装模式] 生成起吊点和提升点失败: {ex.Message}", ex);
}
// 提示用户点击添加转向点
RaiseStatusChanged($"吊装高度已设置为 {liftHeightMetersInput:F2}米,点击添加转向点(点击地面位置),完成后点击完成按钮", PathPlanningStatusType.Info);
}
catch (Exception ex)
{
LogManager.Error($"[吊装模式] 弹出吊装高度对话框失败: {ex.Message}", ex);
_hoistingStartPoint = null;
_enableHoistingMode = false;
_hoistingGroundIntermediatePoints.Clear();
PathPointRenderPlugin.Instance.ClearPreviewPoint();
StopClickTool();
RaiseStatusChanged("设置吊装高度失败,已取消创建吊装路径", PathPlanningStatusType.Warning);
}
}, UIUpdatePriority.High);
return;
}
else
{
// 后续点击:添加新的层级(每次都需要输入高度)
LogManager.Debug($"[吊装模式] 用户点击,准备添加新层级: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})");
// 记录地面点击位置后续在UI线程中处理
Point3D groundPoint = clickedPoint;
// 在UI线程中弹出高度对话框并生成路径点
_uiStateManager.QueueUIUpdate(() =>
{
try
{
// 计算默认高度上一次的高度如果没有则使用3米
double defaultHeightMeters = _hoistingLiftHeight > 0
? UnitsConverter.ConvertToMeters(_hoistingLiftHeight)
: 3.0;
var heightDialog = new NavisworksTransport.UI.WPF.Views.AerialHeightDialog(defaultHeight: defaultHeightMeters);
bool? dialogResult = heightDialog.ShowDialog();
// 如果用户取消,则只是跳过这次点击,继续等待下一次点击
if (dialogResult != true)
{
LogManager.Info("[吊装模式] 用户取消了高度设置,继续等待点击");
RaiseStatusChanged("已取消,继续点击添加层级或点击完成按钮", PathPlanningStatusType.Info);
return;
}
// 检查用户是否选择"设为终点"(完成路径)
bool isSetAsEndPoint = heightDialog.IsSetAsEndPoint;
// 获取用户设置的高度
double liftHeightMetersInput = heightDialog.LiftHeightMeters;
double liftHeightModelUnits = UnitsConverter.ConvertFromMeters(liftHeightMetersInput);
double previousHeight = _hoistingLiftHeight;
_hoistingLiftHeight = liftHeightModelUnits;
// 更新路径的吊装高度(使用最新的高度)
if (CurrentRoute != null)
{
CurrentRoute.LiftHeight = liftHeightModelUnits;
}
LogManager.Info($"[吊装模式] 用户设置高度: {liftHeightMetersInput:F2}米,上一高度: {UnitsConverter.ConvertToMeters(previousHeight):F2}米");
// 获取上一个路径点
Point3D previousPoint;
if (CurrentRoute.Points.Count > 0)
{
previousPoint = CurrentRoute.Points.Last().Position;
var lastPoint = CurrentRoute.Points.Last();
LogManager.Info($"[吊装模式] 上一路径点: {lastPoint.Name}, Elevation={GetHoistingElevation(lastPoint.Position):F2}");
}
else
{
previousPoint = OffsetPointAlongHoistingUp(_hoistingStartPoint, liftHeightModelUnits);
LogManager.Info($"[吊装模式] 无上一路径点,使用起点+高度: Elevation={GetHoistingElevation(previousPoint):F2}");
}
// 记录原始地面点击位置
_hoistingGroundIntermediatePoints.Add(groundPoint);
LogManager.Debug($"[吊装模式] 已记录地面点击点: ({groundPoint.X:F2}, {groundPoint.Y:F2}, {groundPoint.Z:F2})");
// 检查高度是否变化(比较沿宿主 up 的绝对高程)
double newAbsoluteHeight = GetHoistingElevation(_hoistingStartPoint) + liftHeightModelUnits;
double heightDiff = Math.Abs(newAbsoluteHeight - GetHoistingElevation(previousPoint));
bool heightChanged = heightDiff > 0.001;
LogManager.Info($"[吊装模式] 起点=({_hoistingStartPoint.X:F2}, {_hoistingStartPoint.Y:F2}, {_hoistingStartPoint.Z:F2})");
LogManager.Info($"[吊装模式] 相对高度(米)={liftHeightMetersInput:F2}, 相对高度(模型)={liftHeightModelUnits:F2}");
LogManager.Info($"[吊装模式] 新绝对高度={newAbsoluteHeight:F2}, 上一位置Elevation={GetHoistingElevation(previousPoint):F2}");
// 步骤1水平移动点在旧高度上移动到新XY
// 使用 AerialPathGenerator 生成水平路径点
LogManager.Info($"[吊装模式] 生成水平路径点: previousPoint=({previousPoint.X:F2}, {previousPoint.Y:F2}, {previousPoint.Z:F2}), groundPoint=({groundPoint.X:F2}, {groundPoint.Y:F2}, {groundPoint.Z:F2})");
// 记录添加新点前的点数,用于增量优化
int pointCountBefore = CurrentRoute.Points.Count;
var generator = new AerialPathGenerator();
var aerialPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
groundPoint,
GetHoistingElevation(previousPoint),
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
if (aerialPoint != null)
{
// 添加水平移动路径点
CurrentRoute.AddPoint(aerialPoint);
LogManager.Info($"[吊装模式] 已添加水平路径点: {aerialPoint.Name}, 位置: ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}, {aerialPoint.Position.Z:F2})");
// 更新上一个点为水平路径点
previousPoint = aerialPoint.Position;
}
// 步骤2在添加垂直过渡点前先正交化水平路径此时所有点在同一高度可以处理斜线
// 使用增量处理,只处理新添加的点(从添加前最后一个点的索引开始)
int orthogonalizeStartIndex = Math.Max(0, pointCountBefore - 1);
OrthogonalizePath(CurrentRoute, orthogonalizeStartIndex);
// 步骤3如果需要添加垂直过渡点
if (heightChanged)
{
// 高度变化:垂直上升到新高度
LogManager.Info($"[吊装模式] 高度变化,插入垂直过渡点: Elevation={GetHoistingElevation(previousPoint):F2} -> Elevation={newAbsoluteHeight:F2}");
var verticalTransitionPoint = SetHoistingElevation(previousPoint, newAbsoluteHeight);
var verticalPoint = new PathPoint(
verticalTransitionPoint,
$"高度过渡{CurrentRoute.Points.Count}",
PathPointType.WayPoint);
verticalPoint.Direction = HoistingPointDirection.Vertical;
CurrentRoute.AddPoint(verticalPoint);
LogManager.Info($"[吊装模式] 已添加垂直过渡点: ({verticalTransitionPoint.X:F2}, {verticalTransitionPoint.Y:F2}, {verticalTransitionPoint.Z:F2})");
}
else
{
LogManager.Info($"[吊装模式] 高度未变化,无需垂直过渡");
}
// 渲染路径
if (_renderPlugin != null)
{
_renderPlugin.RenderPath(CurrentRoute);
}
// 如果用户选择"设为终点",调用 FinishEditing 完成路径
if (isSetAsEndPoint)
{
LogManager.Info("[吊装模式] 用户选择设为终点,调用 FinishEditing 完成路径");
bool finished = FinishEditing();
if (finished && CurrentRoute != null)
{
// 触发路径列表更新事件通知UI刷新
RaisePathPointsListUpdated(CurrentRoute, "设为终点完成路径");
}
}
else
{
string transitionText = heightChanged ? "(含高度过渡)" : "";
RaiseStatusChanged($"已添加层级路径点{transitionText}(高度: {liftHeightMetersInput:F2}米),继续点击添加新层级或点击完成按钮", PathPlanningStatusType.Info);
}
}
catch (Exception ex)
{
LogManager.Error($"[吊装模式] 添加层级失败: {ex.Message}", ex);
}
}, UIUpdatePriority.High);
return;
}
}
// 如果启用了空轨吸附,先吸附到基准路径
if (_enableRailSnapping)
{
clickedPoint = SnapToRailBaseline(clickedPoint);
}
// 检查当前选中的通道状态
LogManager.Debug($"[手动编辑] 当前_selectedChannels状态: {(_walkableAreas == null ? "NULL" : $"{_walkableAreas.Count}")}");
// 如果没有选中的通道,尝试实时搜索
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
LogManager.Debug("[手动编辑] 没有预选通道,开始实时搜索可通行的物流模型");
SearchAndSetTraversableChannels();
}
// 空轨路径直接添加路径点,不检查可通行物流模型
if (_enableRailSnapping)
{
LogManager.Debug("[手动编辑] 空轨路径模式,直接添加路径点");
var pathPoint = AddPathPointIn3D(clickedPoint);
if (pathPoint != null)
{
LogManager.Debug($"[手动编辑] ✓ 空轨路径点添加成功: {pathPoint.Name}");
}
else
{
LogManager.Warning("[手动编辑] ✗ 空轨路径点添加失败");
}
}
// 地面路径直接处理点击
else
{
// 手动路径编辑 - 根据当前模式处理点击
if (PathEditState == PathEditState.AddingPoints)
{
// 添加路径点模式 - 使用预览点
LogManager.Debug("[手动编辑] 设置预览点位置");
var previewPoint = SetPreviewPoint(clickedPoint);
if (previewPoint != null)
{
LogManager.Debug($"[手动编辑] ✓ 预览点已设置: {previewPoint.Name}");
}
else
{
LogManager.Warning("[手动编辑] ✗ 预览点设置失败");
}
}
else if (PathEditState == PathEditState.EditingPoint)
{
// 修改路径点模式 - 设置预览位置
LogManager.Debug("[手动编辑] 设置修改路径点预览位置");
SetEditingPreviewPoint(clickedPoint);
LogManager.Debug($"[手动编辑] ✓ 修改路径点预览位置已设置: ({clickedPoint.X:F3}, {clickedPoint.Y:F3}, {clickedPoint.Z:F3})");
}
else
{
// 其他编辑模式 - 保持原有逻辑
LogManager.Debug("[手动编辑] 调用AddPathPointIn3D添加路径点");
var pathPoint = AddPathPointIn3D(clickedPoint);
if (pathPoint != null)
{
LogManager.Debug($"[手动编辑] ✓ 路径点添加成功: {pathPoint.Name}");
}
else
{
LogManager.Warning("[手动编辑] ✗ 路径点添加失败");
}
}
}
}
catch (Exception ex)
{
LogManager.Error($"[手动编辑] 处理异常: {ex.Message}");
LogManager.Error($"[手动编辑] 堆栈: {ex.StackTrace}");
}
}
/// <summary>
/// 将点击点吸附到空轨基准路径
/// </summary>
private Point3D SnapToRailBaseline(Point3D clickedPoint)
{
try
{
// 获取所有空轨基准路径
var baselinePaths = PathPointRenderPlugin.Instance?.GetAllRailBaselinePaths();
if (baselinePaths == null || baselinePaths.Count == 0)
{
LogManager.Warning("[空轨吸附] 没有找到空轨基准路径,不进行吸附");
return clickedPoint;
}
// 找到最近的基准路径投影点
Point3D nearestPoint = null;
double minDistance = double.MaxValue;
double maxSnapDistance = 2.0; // 最大吸附距离(模型单位)
foreach (var pathPoints in baselinePaths.Values)
{
var snappedPoint = FindNearestPointOnPolyline(pathPoints, clickedPoint);
if (snappedPoint == null)
{
continue;
}
double distance = CalculateDistance3D(clickedPoint, snappedPoint);
if (distance < minDistance)
{
minDistance = distance;
nearestPoint = snappedPoint;
}
}
if (nearestPoint != null && minDistance <= maxSnapDistance)
{
LogManager.Info($"[空轨吸附] 点击点 ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2}) 吸附到基准路径投影点 ({nearestPoint.X:F2}, {nearestPoint.Y:F2}, {nearestPoint.Z:F2}),距离 {minDistance:F2}");
return nearestPoint;
}
else
{
LogManager.Warning($"[空轨吸附] 未找到合适的基准路径点(最近距离: {minDistance:F2},最大吸附距离: {maxSnapDistance}");
return clickedPoint;
}
}
catch (Exception ex)
{
LogManager.Error($"[空轨吸附] 吸附失败: {ex.Message}");
return clickedPoint;
}
}
/// <summary>
/// 计算3D距离
/// </summary>
private double CalculateDistance3D(Point3D a, Point3D b)
{
double dx = a.X - b.X;
double dy = a.Y - b.Y;
double dz = a.Z - b.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
/// <summary>
/// 查找折线上距离目标点最近的投影点
/// </summary>
private Point3D FindNearestPointOnPolyline(List<Point3D> pathPoints, Point3D targetPoint)
{
if (pathPoints == null || pathPoints.Count == 0)
{
return null;
}
if (pathPoints.Count == 1)
{
return pathPoints[0];
}
Point3D nearestPoint = null;
double minDistance = double.MaxValue;
for (int i = 0; i < pathPoints.Count - 1; i++)
{
var projectedPoint = ProjectPointToSegment(targetPoint, pathPoints[i], pathPoints[i + 1]);
double distance = CalculateDistance3D(targetPoint, projectedPoint);
if (distance < minDistance)
{
minDistance = distance;
nearestPoint = projectedPoint;
}
}
return nearestPoint ?? pathPoints[0];
}
/// <summary>
/// 将点投影到线段上
/// </summary>
private Point3D ProjectPointToSegment(Point3D point, Point3D segmentStart, Point3D segmentEnd)
{
double segmentX = segmentEnd.X - segmentStart.X;
double segmentY = segmentEnd.Y - segmentStart.Y;
double segmentZ = segmentEnd.Z - segmentStart.Z;
double segmentLengthSquared = segmentX * segmentX + segmentY * segmentY + segmentZ * segmentZ;
if (segmentLengthSquared < 1e-9)
{
return segmentStart;
}
double pointX = point.X - segmentStart.X;
double pointY = point.Y - segmentStart.Y;
double pointZ = point.Z - segmentStart.Z;
double projection = (pointX * segmentX + pointY * segmentY + pointZ * segmentZ) / segmentLengthSquared;
projection = Math.Max(0.0, Math.Min(1.0, projection));
return new Point3D(
segmentStart.X + projection * segmentX,
segmentStart.Y + projection * segmentY,
segmentStart.Z + projection * segmentZ);
}
/// <summary>
/// 搜索并设置可通行的通道
/// </summary>
private void SearchAndSetTraversableChannels()
{
try
{
var document = Application.ActiveDocument;
if (document?.Models != null)
{
// 使用CategoryAttributeManager统一接口获取物流项目
var logisticsItemsCollection = CategoryAttributeManager.GetAllLogisticsItems();
LogManager.Debug($"[搜索通道] CategoryAttributeManager找到 {logisticsItemsCollection.Count} 个物流模型");
if (logisticsItemsCollection.Count > 0)
{
// 筛选出可通行的物流模型
var traversableItems = CategoryAttributeManager.FilterTraversableItems(logisticsItemsCollection);
LogManager.Debug($"[搜索通道] 筛选出 {traversableItems.Count} 个可通行的物流模型");
// 临时设置为选中通道
if (_walkableAreas == null) _walkableAreas = new List<ModelItem>();
_walkableAreas.Clear();
foreach (ModelItem item in traversableItems)
{
_walkableAreas.Add(item);
LogManager.Debug($"[搜索通道] 添加可通行模型: '{item.DisplayName}'");
}
}
}
}
catch (Exception searchEx)
{
LogManager.Error($"[搜索通道] 实时搜索失败: {searchEx.Message}");
}
}
/// <summary>
/// 获取Navisworks文档单位并转换为米的系数
/// </summary>
private double GetUnitsToMetersConversionFactor()
{
try
{
var units = Application.ActiveDocument.Units;
switch (units)
{
case Units.Millimeters:
return 0.001;
case Units.Centimeters:
return 0.01;
case Units.Meters:
return 1.0;
case Units.Inches:
return 0.0254;
case Units.Feet:
return 0.3048;
case Units.Kilometers:
return 1000.0;
case Units.Micrometers:
return 0.000001;
case Units.Microinches:
return 0.0000000254;
case Units.Mils:
return 0.0000254;
case Units.Yards:
return 0.9144;
case Units.Miles:
return 1609.43;
default:
return 1.0;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"获取文档单位失败: {ex.Message},默认为米");
return 1.0;
}
}
/// <summary>
/// 转换包围盒到米单位
/// </summary>
private BoundingBox3D ConvertBoundingBoxToMeters(BoundingBox3D boundingBox, double conversionFactor)
{
var minPoint = new Point3D(
boundingBox.Min.X * conversionFactor,
boundingBox.Min.Y * conversionFactor,
boundingBox.Min.Z * conversionFactor
);
var maxPoint = new Point3D(
boundingBox.Max.X * conversionFactor,
boundingBox.Max.Y * conversionFactor,
boundingBox.Max.Z * conversionFactor
);
return new BoundingBox3D(minPoint, maxPoint);
}
#endregion
#region IDisposable实现
public void Dispose()
{
try
{
// 清理资源
_renderPlugin?.ClearAllPaths();
_renderPlugin = null;
// 停用ToolPlugin
if (_isToolPluginActive)
{
DeactivateToolPlugin();
}
_pathDatabase?.Dispose();
_pathDatabase = null;
_analysisService = null;
_databaseDocumentPath = null;
}
catch (Exception ex)
{
LogManager.Error($"PathPlanningManager释放时发生错误: {ex.Message}");
}
}
private bool DeactivateToolPlugin()
{
if (!_isToolPluginActive)
{
LogManager.Debug("[ToolPlugin] ToolPlugin未激活无需停用");
return true;
}
try
{
// 1. 取消事件订阅
PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked;
// 2. 重置为无活动工具状态
try
{
// 使用Tool.Select切换到选择工具确保完全退出ToolPlugin模式
// Tool.None可能不足以释放ToolPlugin对键盘输入的拦截
Application.MainDocument.Tool.Value = Tool.Select;
LogManager.Debug("[ToolPlugin] 已重置工具状态为Select");
}
catch (Exception ex)
{
LogManager.Warning($"[ToolPlugin] 重置工具状态失败: {ex.Message}");
}
_isToolPluginActive = false;
LogManager.Debug("[ToolPlugin] ===== ToolPlugin停用完成 =====");
return true;
}
catch (Exception ex)
{
LogManager.Error($"[ToolPlugin] 停用异常: {ex.Message}");
LogManager.Error($"[ToolPlugin] 堆栈: {ex.StackTrace}");
return false;
}
}
/// <summary>
/// 强制重新初始化ToolPlugin公共接口避免反射调用
/// </summary>
/// <param name="subscribeToEvents">是否订阅事件</param>
/// <returns>初始化是否成功</returns>
public bool ForceReinitializeToolPlugin(bool subscribeToEvents = false)
{
try
{
LogManager.Debug($"[工具插件重初始化] 开始强制重新初始化ToolPlugin订阅事件: {subscribeToEvents}");
// 1. 停用当前ToolPlugin
bool deactivated = DeactivateToolPlugin();
LogManager.Debug($"[工具插件重初始化] 停用结果: {deactivated}");
// 2. 强制重置激活状态
_isToolPluginActive = false;
LogManager.Debug("[工具插件重初始化] 已重置激活状态标志");
// 3. 重新激活ToolPlugin
bool activated = ActivateToolPlugin(subscribeToEvents);
LogManager.Debug($"[工具插件重初始化] 激活结果(事件订阅: {subscribeToEvents}: {activated}");
if (activated)
{
LogManager.Info("[工具插件重初始化] ToolPlugin重新初始化成功已获得鼠标焦点");
}
else
{
LogManager.Error("[工具插件重初始化] ToolPlugin激活失败");
}
return activated;
}
catch (Exception ex)
{
LogManager.Error($"[工具插件重初始化] 重新初始化ToolPlugin失败: {ex.Message}", ex);
return false;
}
}
/// <summary>
/// 根据当前业务上下文恢复 ToolPlugin 焦点。
/// 普通手动建/编辑路径应恢复 PathPlanningManager 主链;
/// 自动路径、装配拾取、多层吊装等外部自管拾取模式则保持外部事件处理。
/// </summary>
public bool RestoreToolPluginFocusForCurrentContext()
{
try
{
bool shouldSubscribeToManagerEvents = !IsInAutoPathMode;
string contextLabel = shouldSubscribeToManagerEvents
? "普通手动取点"
: "外部自管拾取";
LogManager.Info(
$"[工具插件恢复] 按当前上下文恢复焦点: 模式={contextLabel}, " +
$"PathEditState={PathEditState}, 订阅PathPlanningManager事件={shouldSubscribeToManagerEvents}");
return ForceReinitializeToolPlugin(shouldSubscribeToManagerEvents);
}
catch (Exception ex)
{
LogManager.Error($"[工具插件恢复] 按当前上下文恢复焦点失败: {ex.Message}", ex);
return false;
}
}
#endregion
#region
/// <summary>
/// 重新计算曲线化路径并保存到数据库
/// </summary>
#endregion
#region 访
/// <summary>
/// 获取PathDatabase实例
/// </summary>
public PathDatabase GetPathDatabase()
{
return _pathDatabase;
}
/// <summary>
/// 更新路径并保存到数据库
/// </summary>
public void UpdateRoute(PathRoute route)
{
if (route == null) return;
// 使用统一的曲线化和保存方法
route.RecalculateAndSaveRoute("UpdateRoute");
}
#endregion
#region
/// <summary>
/// 获取当前模型的边界框
/// </summary>
/// <returns>模型边界框</returns>
private BoundingBox3D GetModelBounds()
{
try
{
// 如果没有选中的可通行区域,自动搜索
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
LogManager.Info("未找到预定义的可通行区域,尝试自动搜索...");
AutoSelectLogisticsChannels();
}
// 检查是否找到可通行区域
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
LogManager.Error("没有找到任何可通行区域(通道、门、电梯、楼梯等)");
throw new InvalidOperationException("无法进行路径规划:模型中没有定义可通行的物流区域。请先设置物流属性。");
}
// 使用可通行区域的边界作为路径规划范围
return CalculateChannelsBounds(_walkableAreas);
}
catch (Exception ex)
{
LogManager.Error($"获取模型边界失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 计算选中通道的组合边界
/// </summary>
private BoundingBox3D CalculateChannelsBounds(List<ModelItem> channels)
{
var allBounds = new List<BoundingBox3D>();
foreach (var channel in channels)
{
var bounds = channel.BoundingBox();
if (bounds != null)
{
allBounds.Add(bounds);
}
}
if (!allBounds.Any()) return null;
var minX = allBounds.Min(b => b.Min.X);
var minY = allBounds.Min(b => b.Min.Y);
var minZ = allBounds.Min(b => b.Min.Z);
var maxX = allBounds.Max(b => b.Max.X);
var maxY = allBounds.Max(b => b.Max.Y);
var maxZ = allBounds.Max(b => b.Max.Z);
return new BoundingBox3D(
new Point3D(minX, minY, minZ),
new Point3D(maxX, maxY, maxZ)
);
}
/// <summary>
/// 计算最优网格大小
/// </summary>
private double CalculateOptimalGridSize(BoundingBox3D bounds)
{
try
{
return CalculateOptimalGridSizeFromBounds(
bounds.Min.X, bounds.Min.Y, bounds.Min.Z,
bounds.Max.X, bounds.Max.Y, bounds.Max.Z,
CoordinateSystemManager.Instance.ResolvedType);
}
catch
{
return 0.5; // 默认值改为0.5米与UI默认设置保持一致
}
}
/// <summary>
/// 根据宿主包围盒和当前宿主坐标系语义,计算自动路径规划的最优网格大小。
/// 关键约束:
/// - 仅修正“水平平面”的解释,不改变历史阈值与业务策略。
/// - ZUp: 使用 Host XY 平面跨度。
/// - YUp: 使用 Host XZ 平面跨度。
/// </summary>
private static double CalculateOptimalGridSizeFromBounds(
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
CoordinateSystemType coordinateSystemType)
{
var adapter = new HostCoordinateAdapter(coordinateSystemType);
var hostMin = new System.Numerics.Vector3((float)minX, (float)minY, (float)minZ);
var hostMax = new System.Numerics.Vector3((float)maxX, (float)maxY, (float)maxZ);
var (min1, max1, min2, max2) = HostPlanarGridHelper.GetHorizontalRange3(hostMin, hostMax, adapter);
var width = max1 - min1;
var height = max2 - min2;
var maxDimension = Math.Max(width, height);
if (maxDimension > 1000) return 5.0;
if (maxDimension > 500) return 2.0;
return 0.5;
}
/// <summary>
/// 获取通道模型项用于高度计算
/// </summary>
private IEnumerable<ModelItem> GetChannelItemsForHeightCalculation(GridMap gridMap = null)
{
var channelItems = new List<ModelItem>();
try
{
// 🔥 关键修复:优先使用网格生成时已确定的通道数据
if (gridMap?.ChannelItems != null && gridMap.ChannelItems.Any())
{
LogManager.Info($"[通道数据] 使用网格生成时的通道数据,数量: {gridMap.ChannelItems.Count()}");
channelItems.AddRange(gridMap.ChannelItems);
return channelItems;
}
// 如果用户已选择通道,优先使用用户选择的通道
if (_walkableAreas != null && _walkableAreas.Count > 0)
{
LogManager.Info($"[通道数据] 使用用户选择的通道,数量: {_walkableAreas.Count}");
channelItems.AddRange(_walkableAreas);
return channelItems;
}
// 否则使用CategoryAttributeManager搜索
LogManager.Info("[通道数据] 用户未选择通道且网格无通道数据使用CategoryAttributeManager搜索");
var document = Application.ActiveDocument;
if (document != null)
{
var foundChannelsCollection = CategoryAttributeManager.GetAllLogisticsItems();
foreach (ModelItem item in foundChannelsCollection)
{
channelItems.Add(item);
}
}
LogManager.Info($"[通道数据] 找到 {channelItems.Count} 个通道模型项");
return channelItems;
}
catch (Exception ex)
{
LogManager.Error($"[通道数据] 获取通道项失败: {ex.Message}");
return channelItems;
}
}
/// <summary>
/// 从A*算法结果创建PathRoute对象
/// </summary>
private PathRoute CreateAutoPathRoute(PathFindingResult pathResult, string routeName)
{
try
{
var route = new PathRoute(routeName);
var pathPoints = pathResult.PathPoints;
// 设置路径完成状态信息
route.IsComplete = pathResult.IsComplete;
route.OriginalEndPoint = pathResult.OriginalEndPoint;
route.ActualEndPoint = pathResult.ActualEndPoint;
route.CompletionPercentage = pathResult.CompletionPercentage;
for (int i = 0; i < pathPoints.Count; i++)
{
var point = pathPoints[i];
var pathPoint = new PathPoint
{
Name = GenerateAutoPathPointName(i, pathPoints.Count),
Position = point,
Type = DeterminePointType(i, pathPoints.Count),
SpeedLimit = GetSpeedLimitAtPosition(point)
};
route.AddPoint(pathPoint);
}
// 注意AddPoint已经调用RecalculateAndSaveRoute计算了曲线化长度
// 不需要再调用RecalculateLength(),否则会覆盖正确的曲线长度
LogManager.Info($"创建自动路径: {routeName}, 点数: {pathPoints.Count}, 长度: {route.TotalLength:F2}米");
return route;
}
catch (Exception ex)
{
LogManager.Error($"创建自动路径失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 根据世界坐标获取该位置的限速
/// </summary>
/// <param name="worldPosition">世界坐标</param>
/// <returns>限速值(米/秒0表示未设置限速</returns>
private double GetSpeedLimitAtPosition(Point3D worldPosition)
{
try
{
// 如果没有当前网格地图返回0
if (_currentGridMap == null)
{
return 0;
}
// 将世界坐标转换为网格坐标
var gridPos = _currentGridMap.WorldToGrid(worldPosition);
// 检查网格坐标是否有效
if (!_currentGridMap.IsValidGridPosition(gridPos))
{
return 0;
}
// 获取网格单元格
var cell = _currentGridMap.GetCell(gridPos);
return cell?.SpeedLimit ?? 0;
}
catch (Exception ex)
{
LogManager.Error($"获取位置限速失败: {ex.Message}");
return 0;
}
}
/// <summary>
/// 生成自动路径点名称
/// </summary>
private string GenerateAutoPathPointName(int index, int totalCount)
{
if (index == 0) return "自动起点";
if (index == totalCount - 1) return "自动终点";
return $"自动点{index}";
}
/// <summary>
/// 确定路径点类型
/// </summary>
private PathPointType DeterminePointType(int index, int totalCount)
{
if (index == 0) return PathPointType.StartPoint;
if (index == totalCount - 1) return PathPointType.EndPoint;
return PathPointType.WayPoint;
}
/// <summary>
/// 绘制路径可视化
/// </summary>
public void DrawRouteVisualization(PathRoute route, bool isAutoPath = false)
{
if (route == null) return;
try
{
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin == null)
{
LogManager.Warning("PathPointRenderPlugin实例为空无法绘制路径");
return;
}
// 使用新的统一路径渲染API
renderPlugin.RenderPath(route);
LogManager.Info($"已渲染路径: {route.Name},包含 {route.Points.Count} 个路径点");
}
catch (Exception ex)
{
LogManager.Warning($"路径可视化失败: {ex.Message}");
}
}
#endregion
#region
/// <summary>
/// 检查项目是否在选定通道中
/// </summary>
/// <param name="item">要检查的项目</param>
/// <returns>是否在选定通道中</returns>
private bool IsItemInSelectedChannels(ModelItem item)
{
return _walkableAreas.Contains(item) || IsItemChildOfSelectedChannels(item);
}
/// <summary>
/// 检查项目是否为选定通道的子项
/// </summary>
/// <param name="item">要检查的项目</param>
/// <returns>是否为子项</returns>
private bool IsItemChildOfSelectedChannels(ModelItem item)
{
foreach (var channel in _walkableAreas)
{
if (IsChildOf(item, channel))
{
return true;
}
}
return false;
}
/// <summary>
/// 递归检查是否为子项
/// </summary>
/// <param name="child">子项</param>
/// <param name="parent">父项</param>
/// <returns>是否为子项</returns>
private bool IsChildOf(ModelItem child, ModelItem parent)
{
var currentParent = child.Parent;
while (currentParent != null)
{
if (currentParent == parent)
{
return true;
}
currentParent = currentParent.Parent;
}
return false;
}
/// <summary>
/// 状态变更通知(向后兼容方法)
/// </summary>
/// <param name="status">状态消息</param>
private void OnStatusChanged(string status)
{
try
{
// 使用新的事件触发机制
RaiseStatusChanged(status, PathPlanningStatusType.Info);
}
catch (Exception ex)
{
LogManager.Error($"[PathManager] OnStatusChanged方法异常: {ex.Message}");
LogManager.Error($"[PathManager] 状态消息: {status}");
}
}
#endregion
#region
/// <summary>
/// 查找预览点应该插入的最近线段和插入索引
/// </summary>
/// <param name="previewPosition">预览点位置</param>
/// <param name="pathPoints">路径点列表</param>
/// <returns>最近线段的前后两点以及插入索引未找到时返回null</returns>
private (PathPoint prevPoint, PathPoint nextPoint, int insertIndex)? FindNearestLineSegmentWithIndex(Point3D previewPosition, List<PathPoint> pathPoints)
{
if (pathPoints == null || pathPoints.Count < 2)
{
return null;
}
// 对路径点进行排序(按添加顺序,即列表中的索引)
var sortedPoints = pathPoints.ToList();
double minDistance = double.MaxValue;
(PathPoint prevPoint, PathPoint nextPoint, int insertIndex)? nearestSegment = null;
// 遍历相邻的路径点对,找到距离预览点最近的线段
for (int i = 0; i < sortedPoints.Count - 1; i++)
{
var currentPoint = sortedPoints[i];
var nextPoint = sortedPoints[i + 1];
// 计算预览点到线段的距离
var distance = CalculatePointToLineSegmentDistance(previewPosition, currentPoint.Position, nextPoint.Position);
if (distance < minDistance)
{
minDistance = distance;
// 插入索引应该是nextPoint的索引位置这样新点会插入到currentPoint和nextPoint之间
nearestSegment = (currentPoint, nextPoint, i + 1);
}
}
return nearestSegment;
}
/// <summary>
/// 计算点到线段的最短距离
/// </summary>
/// <param name="point">目标点</param>
/// <param name="lineStart">线段起点</param>
/// <param name="lineEnd">线段终点</param>
/// <returns>最短距离</returns>
private double CalculatePointToLineSegmentDistance(Point3D point, Point3D lineStart, Point3D lineEnd)
{
// 线段向量
var lineVector = new Point3D(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y, lineEnd.Z - lineStart.Z);
// 点到线段起点的向量
var pointVector = new Point3D(point.X - lineStart.X, point.Y - lineStart.Y, point.Z - lineStart.Z);
// 计算线段长度的平方
var lineLengthSquared = lineVector.X * lineVector.X + lineVector.Y * lineVector.Y + lineVector.Z * lineVector.Z;
if (lineLengthSquared == 0)
{
// 线段退化为点,返回点到点的距离
return CalculateDistance(point, lineStart);
}
// 计算投影参数t
var t = (pointVector.X * lineVector.X + pointVector.Y * lineVector.Y + pointVector.Z * lineVector.Z) / lineLengthSquared;
// 将t限制在[0,1]范围内
t = Math.Max(0, Math.Min(1, t));
// 计算线段上最近点
var closestPoint = new Point3D(
lineStart.X + t * lineVector.X,
lineStart.Y + t * lineVector.Y,
lineStart.Z + t * lineVector.Z
);
// 返回点到最近点的距离
return CalculateDistance(point, closestPoint);
}
/// <summary>
/// 计算两点间距离
/// </summary>
/// <param name="point1">第一个点</param>
/// <param name="point2">第二个点</param>
/// <returns>两点间距离</returns>
private double CalculateDistance(Point3D point1, Point3D point2)
{
var dx = point1.X - point2.X;
var dy = point1.Y - point2.Y;
var dz = point1.Z - point2.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
#endregion
#region
/// <summary>
/// 更新网格可视化设置
/// </summary>
/// <param name="showWalkable">是否显示可通行网格点</param>
/// <param name="showObstacle">是否显示障碍物网格点</param>
/// <param name="showUnknown">是否显示未知区域网格点</param>
/// <param name="showDoor">是否显示门网格点</param>
public void UpdateGridVisualizationSettings(bool showWalkable, bool showObstacle, bool showUnknown, bool showDoor = false)
{
try
{
LogManager.Info($"[网格可视化设置] 更新设置: 通行={showWalkable}, 障碍物={showObstacle}, 未知={showUnknown}, 门={showDoor}");
_showWalkableGrid = showWalkable;
_showObstacleGrid = showObstacle;
_showUnknownGrid = showUnknown;
_showDoorGrid = showDoor;
// 如果当前有网格正在显示,重新应用可视化
RefreshGridVisualization();
LogManager.Info("[网格可视化设置] 设置更新完成");
}
catch (Exception ex)
{
LogManager.Error($"[网格可视化设置] 更新设置失败: {ex.Message}", ex);
}
}
/// <summary>
/// 检查是否有任何网格可视化已启用
/// </summary>
public bool IsAnyGridVisualizationEnabled
{
get { return _showWalkableGrid || _showObstacleGrid || _showUnknownGrid || _showDoorGrid; }
}
/// <summary>
/// 按自动路径保存的规划参数重建GridMap用于数据库重载后恢复网格可视化和诊断。
/// </summary>
public bool TryRestoreGridMapForRoute(PathRoute route, out GridMap gridMap, out string message)
{
gridMap = null;
message = null;
try
{
if (route == null)
{
message = "路径为空无法重建GridMap";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
if (route.AssociatedGridMap != null)
{
gridMap = route.AssociatedGridMap;
message = $"路径 '{route.Name}' 已有关联GridMap";
return true;
}
List<PathPoint> orderedPoints = route.Points?.OrderBy(point => point.Index).ToList() ?? new List<PathPoint>();
if (orderedPoints.Count < 2)
{
message = $"路径 '{route.Name}' 点数不足无法重建GridMap";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
if (route.GridSize <= 0)
{
message = $"路径 '{route.Name}' 缺少有效网格大小无法重建GridMap";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
double objectRadius = Math.Max(route.MaxObjectLength, route.MaxObjectWidth) / 2.0;
if (objectRadius <= 0)
{
message = $"路径 '{route.Name}' 缺少有效物体长宽无法重建GridMap";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
if (route.SafetyMargin < 0)
{
message = $"路径 '{route.Name}' 安全间隙为负数无法重建GridMap: {route.SafetyMargin}";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
if (route.MaxObjectHeight <= 0)
{
message = $"路径 '{route.Name}' 缺少有效物体高度无法重建GridMap";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
BoundingBox3D bounds = GetModelBounds();
if (bounds == null)
{
message = "无法获取模型边界无法重建GridMap";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
PathPoint startPoint = orderedPoints.First();
PathPoint endPoint = orderedPoints.Last();
LogManager.Info(
$"[网格可视化] 开始按路径参数重建GridMap: 路径={route.Name}, 网格={route.GridSize}m, " +
$"物体半径={objectRadius}m, 安全间隙={route.SafetyMargin}m, 高度={route.MaxObjectHeight}m");
var gridMapGenerator = new GridMapGenerator();
gridMap = gridMapGenerator.GenerateFromBIM(
bounds,
route.GridSize,
objectRadius,
route.SafetyMargin,
startPoint.Position,
endPoint.Position,
route.MaxObjectHeight);
if (gridMap == null)
{
message = $"路径 '{route.Name}' 的GridMap重建失败";
LogManager.Warning($"[网格可视化] {message}");
return false;
}
route.AssociatedGridMap = gridMap;
if (_currentRoute == route || (_currentRoute != null && _currentRoute.Id == route.Id))
{
_currentGridMap = gridMap;
_currentObjectHeight = route.MaxObjectHeight;
}
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin != null)
{
renderPlugin.SetGridSize(UnitsConverter.ConvertToMeters(gridMap.CellSize));
}
message = $"路径 '{route.Name}' 的GridMap已重建: {gridMap.GetStatistics()}";
LogManager.Info($"[网格可视化] {message}");
return true;
}
catch (Exception ex)
{
message = $"重建GridMap失败: {ex.Message}";
LogManager.Error($"[网格可视化] {message}", ex);
gridMap = null;
return false;
}
}
/// <summary>
/// 刷新网格可视化(根据当前设置重新显示)
/// </summary>
private void RefreshGridVisualization()
{
try
{
// 清除当前网格可视化
ClearGridVisualization();
if (!IsAnyGridVisualizationEnabled)
{
LogManager.Info("[网格可视化] 网格可视化设置已关闭,跳过渲染");
return;
}
if (_currentGridMap == null && _currentRoute != null)
{
if (!TryRestoreGridMapForRoute(_currentRoute, out GridMap restoredGridMap, out string restoreMessage))
{
LogManager.Info($"[网格可视化] {restoreMessage}");
}
else
{
_currentGridMap = restoredGridMap;
}
}
// 如果存在缓存的网格地图且有网格可视化设置启用,重新进行可视化
if (_currentGridMap != null)
{
LogManager.Info("[网格可视化] 使用缓存的网格地图重新渲染");
VisualizeGridCells(_currentGridMap, _currentObjectHeight);
}
else
{
LogManager.Info("[网格可视化] 无缓存网格地图,且无法通过当前路径参数重建");
}
}
catch (Exception ex)
{
LogManager.Error($"[网格可视化] 刷新失败: {ex.Message}", ex);
}
}
/// <summary>
/// 在3D视图中可视化网格中的可通行单元格
/// 在每个可通行网格的中心绘制一个绿色小球
/// </summary>
/// <param name="gridMap">要可视化的网格地图</param>
/// <param name="objectHeight">物体高度(米),用于判断层高是否足够</param>
public void VisualizeGridCells(GridMap gridMap, double objectHeight = 2.0)
{
if (gridMap == null)
{
LogManager.Warning("[网格可视化] 网格地图为null无法进行可视化");
return;
}
try
{
// 保存当前网格地图和物体高度用于后续刷新
_currentGridMap = gridMap;
_currentObjectHeight = objectHeight;
LogManager.Info($"[网格可视化] 开始可视化网格:{gridMap.Width}x{gridMap.Height}, 物体高度:{objectHeight}m");
// 获取渲染插件
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin == null)
{
LogManager.Warning("[网格可视化] PathPointRenderPlugin实例为空无法绘制网格");
return;
}
// 设置网格大小以实现自适应点大小
// gridMap.CellSize是模型单位需要转换为米
double gridSizeInMeters = UnitsConverter.ConvertToMeters(gridMap.CellSize);
renderPlugin.SetGridSize(gridSizeInMeters);
// 清除之前的网格可视化
ClearGridVisualization();
// 创建网格可视化集合
var gridVis = new GridVisualization("grid_visualization");
// 统计变量
int channelCells = 0;
int unknownCells = 0;
int obstacleCells = 0;
int doorCells = 0;
int totalVisualized = 0;
int multiLayerCells = 0;
// 遍历所有网格单元格,根据类型分别收集
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
var cell = gridMap.Cells[x, y];
// 当前 GridMap 的索引语义是采样节点GridToWorld2D(x,y)
// 必须直接作为可视化平面位置,不能再按左下角单元取 (x+1,y+1) 平均。
var gridPos = new NavisworksTransport.PathPlanning.GridPoint2D(x, y);
var gridNode2D = gridMap.GridToWorld2D(gridPos);
// 所有网格都基于高度层,统一处理
if (cell.HeightLayers != null && cell.HeightLayers.Count > 0)
{
multiLayerCells++;
foreach (var layer in cell.HeightLayers)
{
double layerZ = layer.Z;
GridVisualizationType? cellType = null;
// 仅根据 layer.IsWalkable 决定渲染
if (layer.IsWalkable)
{
// 可通行层 - 门特殊处理,其他统一样式
if (cell.CellType == "门")
{
if (_showDoorGrid)
{
cellType = GridVisualizationType.Door;
doorCells++;
}
}
else
{
// 其他可通行类型统一为通道样式(绿色)
if (_showWalkableGrid)
{
cellType = GridVisualizationType.Walkable;
channelCells++;
}
}
}
else
{
// 不可通行层 - 渲染为障碍物(灰色)
if (_showObstacleGrid)
{
cellType = GridVisualizationType.Obstacle;
obstacleCells++;
}
}
// 创建该层的可视化标记
if (cellType.HasValue)
{
var gridNode = gridMap.SetWorldElevation(gridNode2D, layerZ);
var marker = new GridMarker(gridNode, cellType.Value, x, y, layerZ);
// 根据类型添加到对应列表
switch (cellType.Value)
{
case GridVisualizationType.Walkable:
gridVis.Walkable.Add(marker);
break;
case GridVisualizationType.Obstacle:
gridVis.Obstacle.Add(marker);
break;
case GridVisualizationType.Unknown:
gridVis.Unknown.Add(marker);
break;
case GridVisualizationType.Door:
gridVis.Door.Add(marker);
break;
}
totalVisualized++;
}
}
}
else if (cell.CellType == "空洞")
{
// Unknown网格没有高度层用于调试
if (_showUnknownGrid)
{
double unknownElevation = gridMap.GetWorldElevation(gridNode2D);
var gridNode = gridMap.SetWorldElevation(gridNode2D, unknownElevation);
var marker = new GridMarker(gridNode, GridVisualizationType.Unknown, x, y, unknownElevation);
gridVis.Unknown.Add(marker);
unknownCells++;
totalVisualized++;
}
}
}
}
// 使用新的专门渲染方法(类型安全)
renderPlugin.RenderGridVisualization(gridVis);
// 输出统计信息
LogManager.Info($"[网格可视化] 可视化完成:");
LogManager.Info($" - 总网格单元格数:{gridMap.Width * gridMap.Height}");
LogManager.Info($" - 多层网格数:{multiLayerCells}");
LogManager.Info($" - 可通行层数:{channelCells}");
LogManager.Info($" - 门层数:{doorCells}");
LogManager.Info($" - Unknown单元格数{unknownCells}");
LogManager.Info($" - 障碍物层数:{obstacleCells}");
LogManager.Info($" - 已可视化点数:{totalVisualized}");
RaiseStatusChanged($"网格可视化完成:显示了 {totalVisualized} 个网格层 (多层网格:{multiLayerCells}, 可通行:{channelCells}, 门:{doorCells}, Unknown:{unknownCells}, 障碍:{obstacleCells})", PathPlanningStatusType.Success);
}
catch (Exception ex)
{
LogManager.Error($"[网格可视化] 可视化失败: {ex.Message}");
RaiseErrorOccurred($"网格可视化失败: {ex.Message}", ex);
}
}
/// <summary>
/// 清除网格可视化
/// </summary>
public void ClearGridVisualization()
{
try
{
LogManager.Info("[网格可视化] 开始清除网格可视化");
var renderPlugin = PathPointRenderPlugin.Instance;
if (renderPlugin != null)
{
// 清除所有网格可视化(使用新的 ID 命名)
renderPlugin.RemovePath("grid_visualization_walkable"); // 可通行的网格
renderPlugin.RemovePath("grid_visualization_obstacle"); // 障碍物网格
renderPlugin.RemovePath("grid_visualization_unknown"); // 未知区域网格
renderPlugin.RemovePath("grid_visualization_door"); // 门网格
// 清除旧的网格可视化 ID向后兼容
renderPlugin.RemovePath("grid_visualization_all");
renderPlugin.RemovePath("grid_visualization_channel");
LogManager.Info("[网格可视化] 网格可视化已清除");
}
else
{
LogManager.Warning("[网格可视化] PathPointRenderPlugin实例为空无法清除可视化");
}
}
catch (Exception ex)
{
LogManager.Error($"[网格可视化] 清除可视化失败: {ex.Message}");
}
}
#endregion
#region
/// <summary>
/// 获取所有路径(供分析使用)
/// </summary>
public List<PathRoute> GetAllRoutes()
{
try
{
// 直接返回内存中的路径列表
// 因为所有路径操作都已同步到数据库,内存列表就是当前的完整列表
return _routes ?? new List<PathRoute>();
}
catch (Exception ex)
{
LogManager.Error($"获取所有路径失败: {ex.Message}", ex);
return new List<PathRoute>();
}
}
/// <summary>
/// 分析多条路径并进行对比
/// </summary>
/// <param name="routeIds">要分析的路径ID列表</param>
/// <param name="strategy">分析策略</param>
/// <returns>对比结果</returns>
public PathComparisonResult AnalyzePathsAndCompare(List<string> routeIds, string strategy)
{
try
{
if (_analysisService == null)
{
LogManager.Error("路径分析服务未初始化");
return null;
}
// 获取要分析的路径
var routesToAnalyze = _routes?.Where(r => routeIds.Contains(r.Id)).ToList();
if (routesToAnalyze == null || routesToAnalyze.Count == 0)
{
LogManager.Warning("未找到要分析的路径");
return null;
}
// 执行分析和对比
var result = _analysisService.CompareRoutes(routesToAnalyze, strategy);
return result;
}
catch (Exception ex)
{
LogManager.Error($"路径分析失败: {ex.Message}", ex);
return null;
}
}
/// <summary>
/// 从数据库加载历史路径到内存
/// </summary>
private void LoadHistoricalRoutesFromDatabase()
{
try
{
var historicalRoutes = _pathDatabase.GetAllPathRoutes();
if (historicalRoutes != null && historicalRoutes.Count > 0)
{
// 使用HashSet记录已存在的路径ID避免重复
var existingIds = new HashSet<string>(_routes.Select(r => r.Id));
int loadedCount = 0;
// 加载历史路径(跳过已存在的)
foreach (var route in historicalRoutes)
{
if (!existingIds.Contains(route.Id))
{
_routes.Add(route);
loadedCount++;
//LogManager.Info($"加载历史路径: {route.Name}, ID={route.Id}, 长度={route.TotalLength:F2}米");
}
}
if (loadedCount > 0)
{
LogManager.Info($"成功从数据库加载 {loadedCount} 条历史路径(总计 {historicalRoutes.Count} 条)");
}
else
{
LogManager.Info($"数据库中的 {historicalRoutes.Count} 条路径已全部在内存中");
}
// 如果当前没有选中的路径,且路径列表不为空,选择第一条
if ((_currentRoute == null || string.IsNullOrEmpty(_currentRoute.Name) || _currentRoute.Name == "默认路径")
&& _routes.Count > 0)
{
_currentRoute = _routes[0];
LogManager.Info($"设置当前路径为: {_currentRoute.Name}");
}
// 历史路径加载完成后触发RoutesLoaded事件通知UI刷新
RaiseRoutesLoaded(_routes.AsReadOnly(), "Database");
LogManager.Info($"已触发RoutesLoaded事件通知UI刷新历史路径列表");
}
else
{
LogManager.Info("数据库中没有历史路径记录");
}
}
catch (Exception ex)
{
LogManager.Error($"加载历史路径失败: {ex.Message}", ex);
}
}
/// <summary>
/// 保存路径时同时保存到数据库
/// </summary>
public void SavePathToDatabase(PathRoute route)
{
try
{
if (route == null)
{
return;
}
if (_pathDatabase == null)
{
DatabaseInitialize(createIfMissing: true);
}
if (_pathDatabase != null)
{
_pathDatabase.SavePathRoute(route);
LogManager.Info($"路径已保存到数据库: {route.Name}");
}
}
catch (Exception ex)
{
LogManager.Error($"保存路径到数据库失败: {ex.Message}", ex);
}
}
/// <summary>
/// 获取Instance单例为兼容现有代码
/// </summary>
public static PathPlanningManager Instance
{
get
{
if (_activePathManager == null)
{
LogManager.Warning("PathPlanningManager未初始化创建新实例");
_activePathManager = new PathPlanningManager();
}
return _activePathManager;
}
}
#endregion
#region
/// <summary>
/// 静态方法:获取当前活动的路径管理器
/// </summary>
/// <returns>当前活动的路径管理器</returns>
public static PathPlanningManager GetActivePathManager()
{
return _activePathManager;
}
internal static PathPoint ReuseLastHoistingPointAsDescendPoint(IList<PathPoint> points)
{
if (points == null)
{
throw new ArgumentNullException(nameof(points));
}
if (points.Count == 0)
{
throw new ArgumentException("吊装路径至少需要一个空中点才能复用为下降点。", nameof(points));
}
PathPoint descendPoint = points[points.Count - 1];
descendPoint.Name = "下降点";
descendPoint.Type = PathPointType.WayPoint;
descendPoint.Direction = HoistingPointDirection.Vertical;
descendPoint.Index = points.Count - 1;
return descendPoint;
}
#endregion
}
}