NavisworksTransport/src/Core/PathPlanningManager.cs
2026-02-18 09:46:10 +08:00

4925 lines
206 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.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;
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 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 _hoistingLiftHeightMeters = 0;
// 吊装中间点列表(用户点击的地面投影点)
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}");
// 注意:数据库初始化延迟到文档加载完成后
// 在 MainPlugin.OnModelsCollectionChanged 中会调用 DatabaseInitialize()
}
/// <summary>
/// 路径分析数据库初始化
/// 此方法应在文档加载完成后调用
/// </summary>
public void DatabaseInitialize()
{
try
{
var documentPath = Application.ActiveDocument?.FileName;
if (!string.IsNullOrEmpty(documentPath))
{
_pathDatabase = new PathDatabase(documentPath);
_analysisService = new PathAnalysisService(_pathDatabase);
LogManager.Info($"路径分析数据库初始化成功,文档路径: {documentPath}");
// 从数据库加载历史路径到内存
LoadHistoricalRoutesFromDatabase();
}
else
{
// 文档路径为空时,静默跳过(这种情况不应该发生,因为调用者应该确保文档已加载)
LogManager.Debug("文档路径为空,跳过数据库初始化");
}
}
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刷新可视化按钮状态
/// </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("无法获取模型边界,请确保模型已加载");
LogManager.Info($"模型边界: Min({bounds.Min.X:F2}, {bounds.Min.Y:F2}), Max({bounds.Max.X:F2}, {bounds.Max.Y: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($"网格生成参数 - 边界: {bounds.Min.X:F2},{bounds.Min.Y:F2} -> {bounds.Max.X:F2},{bounds.Max.Y: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.LiftHeightMeters = loadedRoute.LiftHeightMeters;
LogManager.Debug($"已从数据库加载路径数据: {route.Name}, 共 {loadedRoute.Points.Count} 个点");
}
}
catch (Exception ex)
{
LogManager.Error($"从数据库加载路径数据失败: {ex.Message}", ex);
}
}
// 直接设置字段,绕过事件触发
var previousRoute = _currentRoute;
_currentRoute = route;
// 如果需要触发事件(真正的业务操作)
if (triggerEvent)
{
RaiseCurrentRouteChanged(previousRoute, route, triggerEvent: true);
}
// 如果路径有关联的GridMap加载它用于网格可视化
if (route?.AssociatedGridMap != null)
{
_currentGridMap = route.AssociatedGridMap;
LogManager.Info($"已加载路径 '{route.Name}' 的GridMap网格大小: {route.GridSize}米");
// 如果网格可视化开启,立即刷新显示
if (IsAnyGridVisualizationEnabled)
{
LogManager.Info("检测到网格可视化已启用,自动刷新网格显示");
RefreshGridVisualization();
}
}
else
{
// 如果路径没有GridMap清空当前缓存
_currentGridMap = null;
if (route != null)
{
LogManager.Info($"路径 '{route.Name}' 没有关联的GridMap");
}
}
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, CategoryAttributeManager.LogisticsElementType.);
_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
};
_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;
_hoistingLiftHeightMeters = 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 >= 3 && _hoistingGroundIntermediatePoints.Count > 0)
{
// 获取起吊点用于获取地面Z坐标
var startPoint = CurrentRoute.Points.First();
// 获取吊装高度内部使用模型单位如果未设置则默认3米
double liftHeightModelUnits = CurrentRoute.LiftHeightMeters > 0 ? CurrentRoute.LiftHeightMeters : UnitsConverter.ConvertFromMeters(3.0);
// 获取最后一个地面点击点(原始坐标,未被调整过)
var lastGroundClick = _hoistingGroundIntermediatePoints.Last();
LogManager.Debug($"[吊装路径] 最后一个地面点击点: ({lastGroundClick.X:F2}, {lastGroundClick.Y:F2}, {lastGroundClick.Z:F2})");
// 删除最后一个空中路径点(它的坐标可能被调整过,不使用任何信息)
CurrentRoute.Points.RemoveAt(CurrentRoute.Points.Count - 1);
// 创建落地点终点使用最后一个地面点击点的完整原始坐标X、Y、Z
var endPoint = new PathPoint(
new Point3D(lastGroundClick.X, lastGroundClick.Y, lastGroundClick.Z),
"落地点",
PathPointType.EndPoint);
endPoint.Direction = HoistingPointDirection.Vertical;
LogManager.Debug($"[吊装路径] 已创建落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
// 向上生成下降点在落地点正上方Z坐标为吊装高度
var descendPoint = new PathPoint(
new Point3D(endPoint.Position.X, endPoint.Position.Y, startPoint.Position.Z + liftHeightModelUnits),
"下降点",
PathPointType.WayPoint);
descendPoint.Direction = HoistingPointDirection.Vertical;
LogManager.Debug($"[吊装路径] 已生成下降点(在终点正上方): ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
// 添加下降点
CurrentRoute.Points.Add(descendPoint);
// 添加落地点(终点)
CurrentRoute.Points.Add(endPoint);
LogManager.Debug($"[吊装路径] 已添加落地点(终点): ({endPoint.Position.X:F2}, {endPoint.Position.Y:F2}, {endPoint.Position.Z:F2})");
// 优化路径点:处理斜线和清除多余点
OptimizeRightAnglePathPoints(CurrentRoute);
// 注意TotalLength 现在是计算属性,自动从几何数据计算,无需手动更新
// 重新渲染路径
_renderPlugin?.RenderPath(CurrentRoute);
LogManager.Info($"[吊装路径] 已完成吊装路径,共 {CurrentRoute.Points.Count} 个路径点");
// 清理吊装模式临时变量
_hoistingStartPoint = null;
_hoistingLiftHeightMeters = 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, "编辑完成");
// 触发路径生成事件
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 newAerialZ = startPoint.Position.Z + route.LiftHeightMeters;
liftPoint.Position = new Point3D(startPoint.Position.X, startPoint.Position.Y, newAerialZ);
// 同步所有其他空中点Z坐标索引2到n-2
for (int i = 2; i <= points.Count - 2; i++)
{
points[i].Position = new Point3D(points[i].Position.X, points[i].Position.Y, newAerialZ);
}
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 = new Point3D(endPoint.Position.X, endPoint.Position.Y, descendPoint.Position.Z);
LogManager.Debug($"[吊装路径] 修改终点,更新下降点位置: ({descendPoint.Position.X:F2}, {descendPoint.Position.Y:F2}, {descendPoint.Position.Z:F2})");
}
}
/// <summary>
/// 检测两点之间是否为斜线,如果是则插入中间转折点或修正坐标
/// </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];
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})");
double deltaX = Math.Abs(nextPoint.Position.X - currentPoint.Position.X);
double deltaY = Math.Abs(nextPoint.Position.Y - currentPoint.Position.Y);
// 检测斜线X和Y都变化
bool isDiagonal = deltaX > 0.01 && deltaY > 0.01;
if (!isDiagonal)
{
return false;
}
// 区分两种情况:
// 1. 真正的斜线deltaX和deltaY都比较大>2.0)→ 插入中间点
// 2. 微小偏差:其中一个值很小(<0.5)→ 修改下一点坐标,使其与上一点对齐
if (deltaX > 2.0 && deltaY > 2.0)
{
// 真正的斜线:插入中间点
LogManager.Debug($"[斜线处理] 检测到斜线,需要插入中间点 (deltaX={deltaX:F2}, deltaY={deltaY:F2})");
int intermediateIndex = points.Count > 0 ? points.Count : 1;
var intermediatePoint = new PathPoint(
new Point3D(
nextPoint.Position.X, // 使用新点的X
currentPoint.Position.Y, // 保持上一个点的Y
currentPoint.Position.Z // 保持吊装高度
),
$"路径点{intermediateIndex}",
PathPointType.WayPoint);
intermediatePoint.Direction = HoistingPointDirection.Longitudinal; // 纵向移动
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}) - 方向: 纵向");
LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}");
return true;
}
else if (deltaX < 0.5 && deltaY > 0.5)
{
// X方向微小偏差Y方向移动修改下一点坐标保持X与上一点对齐垂直线
LogManager.Debug($"[斜线处理] 检测到X方向微小偏差 (deltaX={deltaX:F2}, deltaY={deltaY:F2}),修正下一点坐标使其垂直");
nextPoint.Position = new Point3D(
currentPoint.Position.X, // 保持上一个点的X
nextPoint.Position.Y, // 保持新点的Y
nextPoint.Position.Z); // 保持新点的Z
LogManager.Debug($"[斜线处理] 已修正下一点 {nextPoint.Name} 坐标: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})");
return true;
}
else if (deltaY < 0.5 && deltaX > 0.5)
{
// Y方向微小偏差X方向移动修改下一点坐标保持Y与上一点对齐水平线
LogManager.Debug($"[斜线处理] 检测到Y方向微小偏差 (deltaX={deltaX:F2}, deltaY={deltaY:F2}),修正下一点坐标使其水平");
nextPoint.Position = new Point3D(
nextPoint.Position.X, // 保持新点的X
currentPoint.Position.Y, // 保持上一个点的Y
nextPoint.Position.Z); // 保持新点的Z
LogManager.Debug($"[斜线处理] 已修正下一点 {nextPoint.Name} 坐标: ({nextPoint.Position.X:F2}, {nextPoint.Position.Y:F2}, {nextPoint.Position.Z:F2})");
return true;
}
else
{
// 中等程度的偏差,插入中间点
LogManager.Debug($"[斜线处理] 检测到中等程度斜线,需要插入中间点 (deltaX={deltaX:F2}, deltaY={deltaY:F2})");
int intermediateIndex = points.Count > 0 ? points.Count : 1;
var intermediatePoint = new PathPoint(
new Point3D(
nextPoint.Position.X, // 使用新点的X
currentPoint.Position.Y, // 保持上一个点的Y
currentPoint.Position.Z // 保持吊装高度
),
$"路径点{intermediateIndex}",
PathPointType.WayPoint);
intermediatePoint.Direction = HoistingPointDirection.Longitudinal; // 纵向移动
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}) - 方向: 纵向");
LogManager.Debug($"[斜线处理] 插入位置: 索引 {insertPosition}, 当前路径点总数: {points.Count}");
return true;
}
}
/// <summary>
/// 优化直角转折路径点:出现斜线时插入点,在同一直线上时清除多余点
/// </summary>
/// <param name="route">要优化的路径</param>
/// <returns>是否进行了优化</returns>
public bool OptimizeRightAnglePathPoints(PathRoute route)
{
if (route == null || route.Points.Count < 2)
{
return false;
}
bool hasChanges = false;
var points = route.Points;
double tolerance = 0.001; // 容差,用于判断是否在同一直线上
double smallDeviation = 0.5; // 微小偏差阈值,用于修正坐标
double minDistance = 1.0; // 最小距离阈值,小于此距离不删除点
// 第一步:处理斜线,插入转折点或修正坐标
// 注意由于可能插入点需要从后往前遍历或使用while循环
int i = 0;
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.Debug($"[直角路径优化] 索引{i}处插入了点,继续检查");
}
else
{
// 只修改了坐标,继续下一个点
i++;
}
}
else
{
i++;
}
}
// 第二步处理微小转折X或Y方向微小偏差
for (i = 1; i < points.Count - 1; i++)
{
var prevPoint = points[i - 1];
var currentPoint = points[i];
var nextPoint = points[i + 1];
// 计算两个方向的偏差
double deltaX1 = Math.Abs(currentPoint.Position.X - prevPoint.Position.X);
double deltaY1 = Math.Abs(currentPoint.Position.Y - prevPoint.Position.Y);
double deltaX2 = Math.Abs(nextPoint.Position.X - currentPoint.Position.X);
double deltaY2 = Math.Abs(nextPoint.Position.Y - currentPoint.Position.Y);
// 检测微小转折:前一段和后一段都有微小偏差,但方向不同
// 例如前一段X变化很小Y变化较大后一段X变化较大Y变化很小
bool hasSmallTurn = false;
// 前一段垂直,后一段水平
if (deltaX1 < smallDeviation && deltaY1 > smallDeviation &&
deltaY2 < smallDeviation && deltaX2 > smallDeviation)
{
hasSmallTurn = true;
}
// 前一段水平,后一段垂直
else if (deltaY1 < smallDeviation && deltaX1 > smallDeviation &&
deltaX2 < smallDeviation && deltaY2 > smallDeviation)
{
hasSmallTurn = true;
}
if (hasSmallTurn && currentPoint.Type == PathPointType.WayPoint)
{
// 修正转折点坐标,使其完全垂直
if (deltaX1 < smallDeviation)
{
// 前一段垂直调整转折点X与上一点对齐
currentPoint.Position = new Point3D(
prevPoint.Position.X,
currentPoint.Position.Y,
currentPoint.Position.Z);
LogManager.Debug($"[直角路径优化] 修正微小转折点 {i} X坐标: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
hasChanges = true;
}
else
{
// 前一段水平调整转折点Y与上一点对齐
currentPoint.Position = new Point3D(
currentPoint.Position.X,
prevPoint.Position.Y,
currentPoint.Position.Z);
LogManager.Debug($"[直角路径优化] 修正微小转折点 {i} Y坐标: ({currentPoint.Position.X:F2}, {currentPoint.Position.Y:F2}, {currentPoint.Position.Z:F2})");
hasChanges = true;
}
}
}
// 第三步:清除在同一直线上的多余点
i = 1;
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;
}
// 判断三个点是否在同一直线上只检查X或Y方向相同
bool sameX = Math.Abs(prevPoint.Position.X - currentPoint.Position.X) < tolerance &&
Math.Abs(currentPoint.Position.X - nextPoint.Position.X) < tolerance;
bool sameY = Math.Abs(prevPoint.Position.Y - currentPoint.Position.Y) < tolerance &&
Math.Abs(currentPoint.Position.Y - nextPoint.Position.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.Debug($"[直角路径优化] 删除共线的多余点 {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>
/// 更新路径点位置(保存修改)
/// </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 = newPosition.Z;
double originalZ = pointToUpdate.Position.Z;
double heightDelta = newHoistingHeight - originalZ;
// 更新当前点的XY和Z坐标
pointToUpdate.Position = new Point3D(
newPosition.X,
newPosition.Y,
newHoistingHeight
);
// 同步更新所有空中路径点的Z坐标
foreach (var point in route.Points)
{
// 只更新空中路径点(提升点、下降点、中间路径点)
// 跳过当前正在修改的点,避免重复更新
if (point.Name != "起吊点" && point.Name != "落地点" && point != pointToUpdate)
{
point.Position = new Point3D(
point.Position.X,
point.Position.Y,
point.Position.Z + heightDelta
);
}
}
// 🔥 更新路径的提升高度属性(内部使用模型单位)
double hoistingHeightModelUnits = CalculateHoistingHeight(route);
route.LiftHeightMeters = hoistingHeightModelUnits;
LogManager.Info($"[更新路径点] 吊装路径 {pointToUpdate.Name},提升高度已调整为: {UnitsConverter.ConvertToMeters(hoistingHeightModelUnits):F3}米");
// 使用路径的 LiftHeightMeters 属性显示提升高度(内部是模型单位,转换为米显示)
RaiseStatusChanged($"吊装高度: {UnitsConverter.ConvertToMeters(route.LiftHeightMeters):F3}米", PathPlanningStatusType.Info);
}
// 空中路径点非提升点、下降点只更新XY坐标保持原有吊装高度
else if (!isStartPoint && !isEndPoint)
{
double originalZ = pointToUpdate.Position.Z;
pointToUpdate.Position = new Point3D(
newPosition.X,
newPosition.Y,
originalZ // 保留原有Z坐标吊装高度
);
LogManager.Info($"[更新路径点] 吊装路径空中点 {pointToUpdate.Name},保留原有高度: {originalZ:F3}");
}
// 起吊点/落地点:更新完整位置
else
{
pointToUpdate.Position = newPosition;
LogManager.Info($"[更新路径点] 吊装路径地面点 {pointToUpdate.Name},更新完整位置");
}
// 吊装路径:自动更新关联点(仅当修改起吊点或落地点时)
// 注意:修改提升点/下降点时,已经在上面同步更新了所有空中路径点,不需要再次调用
if (isStartPoint || isEndPoint)
{
UpdateAerialPathRelatedPoints(route, pointIndex);
}
// 吊装路径:优化路径点(处理斜线和清除多余点)
OptimizeRightAnglePathPoints(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 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;
}
// 计算高度差提升点Z - 起吊点Z
return liftPoint.Position.Z - startPoint.Position.Z;
}
/// <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;
}
// 获取吊装高度LiftHeightMeters内部是模型单位转换为米传给方法
double liftHeightMeters = CurrentRoute.LiftHeightMeters > 0
? UnitsConverter.ConvertToMeters(CurrentRoute.LiftHeightMeters)
: 3.0;
// 使用 AerialPathGenerator 生成空中路径点
var generator = new AerialPathGenerator();
confirmPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
_previewPoint.Position, // 用户点击的地面位置
liftHeightMeters,
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
LogManager.Info($"[预览点-吊装路径] 已生成空中路径点: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})");
// 插入到正确位置
if (_previewInsertIndex >= 0 && _previewInsertIndex <= CurrentRoute.Points.Count)
{
CurrentRoute.InsertPoint(confirmPoint, _previewInsertIndex);
LogManager.Info($"[预览点-吊装路径] 已插入到索引 {_previewInsertIndex}");
}
else
{
CurrentRoute.AddPoint(confirmPoint);
LogManager.Info($"[预览点-吊装路径] 已追加到末尾");
}
// 调用路径优化方法处理斜线、微小转折和共线点
OptimizeRightAnglePathPoints(CurrentRoute);
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)
{
// 简单的时间估算假设平均速度1米/秒
const double averageSpeed = 1.0; // 米/秒
route.EstimatedTime = route.TotalLength / averageSpeed;
}
/// <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);
_hoistingLiftHeightMeters = liftHeightModelUnits;
LogManager.Info($"用户设置的吊装高度: {liftHeightMetersInput:F2}米");
// 保存吊装高度到当前路径(内部使用模型单位)
if (CurrentRoute != null)
{
CurrentRoute.LiftHeightMeters = 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(
new Point3D(_hoistingStartPoint.X, _hoistingStartPoint.Y, _hoistingStartPoint.Z + 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})");
// 获取吊装高度
double liftHeightMeters = _hoistingLiftHeightMeters;
if (liftHeightMeters <= 0)
{
liftHeightMeters = 3.0;
LogManager.Warning($"[吊装模式] 吊装高度无效,使用默认值: {liftHeightMeters}米");
}
// 使用 AerialPathGenerator 生成单个空中路径点(智能方向判断)
try
{
var generator = new AerialPathGenerator();
// 获取上一个路径点(用于方向判断)
Point3D previousPoint;
if (CurrentRoute.Points.Count > 0)
{
// 如果已有路径点,使用最后一个路径点
previousPoint = CurrentRoute.Points.Last().Position;
}
else
{
// 如果还没有路径点(第一次添加中间点),使用提升点
previousPoint = new Point3D(
_hoistingStartPoint.X,
_hoistingStartPoint.Y,
_hoistingStartPoint.Z + UnitsConverter.ConvertFromMeters(liftHeightMeters));
}
// 生成空中路径点
var aerialPoint = generator.GenerateSmartHoistingPoint(
previousPoint,
clickedPoint,
liftHeightMeters,
CurrentRoute.Points.Count > 0 ? CurrentRoute.Points.Count : 1);
if (aerialPoint != null)
{
// 保存上一个路径点的索引(用于插入中间点)
int previousPointIndex = CurrentRoute.Points.Count - 1;
var previousPathPoint = CurrentRoute.Points[previousPointIndex];
LogManager.Debug($"[吊装模式] 上一个路径点索引: {previousPointIndex}, 名称: {previousPathPoint.Name}, 位置: ({previousPathPoint.Position.X:F2}, {previousPathPoint.Position.Y:F2})");
// 记录原始地面点击位置
_hoistingGroundIntermediatePoints.Add(clickedPoint);
LogManager.Debug($"[吊装模式] 已记录地面点击点: ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2})");
// 添加空中路径点(临时)
CurrentRoute.AddPoint(aerialPoint);
LogManager.Debug($"[吊装模式] 已添加空中路径点: {aerialPoint.Name}, 位置: ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}), 当前路径点数: {CurrentRoute.Points.Count}");
// 调用路径优化方法处理斜线、微小转折和共线点
OptimizeRightAnglePathPoints(CurrentRoute);
LogManager.Debug($"[吊装模式] 路径优化完成,当前路径点数: {CurrentRoute.Points.Count}");
for (int i = 0; i < CurrentRoute.Points.Count; i++)
{
var p = CurrentRoute.Points[i];
LogManager.Debug($" [{i}] {p.Name}: ({p.Position.X:F2}, {p.Position.Y:F2}, {p.Position.Z:F2})");
}
LogManager.Debug($"[吊装模式] 已添加空中路径点: {aerialPoint.Name} ({aerialPoint.Position.X:F2}, {aerialPoint.Position.Y:F2}, {aerialPoint.Position.Z:F2}) - 方向: {aerialPoint.Direction}");
// 渲染路径
if (_renderPlugin != null)
{
_renderPlugin.RenderPath(CurrentRoute);
}
// 提示用户可以继续添加或点击完成
string directionText = aerialPoint.Direction == HoistingPointDirection.Longitudinal ? "纵向" : "横向";
RaiseStatusChanged($"已添加{directionText}路径点,继续点击或点击完成按钮", PathPlanningStatusType.Info);
}
}
catch (Exception ex)
{
LogManager.Error($"[吊装模式] 生成空中路径点失败: {ex.Message}", ex);
}
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)
{
foreach (var point in pathPoints)
{
double distance = CalculateDistance3D(clickedPoint, point);
if (distance < minDistance)
{
minDistance = distance;
nearestPoint = point;
}
}
}
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 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();
}
}
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.None重置到无活动工具状态让Navisworks处理默认导航
Application.MainDocument.Tool.Value = Tool.None;
}
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;
}
}
#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
{
var width = bounds.Max.X - bounds.Min.X;
var height = bounds.Max.Y - bounds.Min.Y;
var maxDimension = Math.Max(width, height);
// 基于模型大小智能选择网格大小
if (maxDimension > 1000) return 5.0; // 大型模型5米
if (maxDimension > 500) return 2.0; // 中型模型2米
return 0.5; // 小型模型0.5米
}
catch
{
return 0.5; // 默认值改为0.5米与UI默认设置保持一致
}
}
/// <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>
/// 刷新网格可视化(根据当前设置重新显示)
/// </summary>
private void RefreshGridVisualization()
{
try
{
// 清除当前网格可视化
ClearGridVisualization();
// 如果存在缓存的网格地图且有网格可视化设置启用,重新进行可视化
if (_currentGridMap != null && IsAnyGridVisualizationEnabled)
{
LogManager.Info("[网格可视化] 使用缓存的网格地图重新渲染");
VisualizeGridCells(_currentGridMap, _currentObjectHeight);
}
else if (_currentGridMap == null)
{
LogManager.Info("[网格可视化] 无缓存网格地图,需要重新进行路径规划以查看网格");
}
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];
// 计算网格单元格的XY中心位置Z坐标在多层逻辑中单独处理
var gridPos = new NavisworksTransport.PathPlanning.GridPoint2D(x, y);
var gridCorner = gridMap.GridToWorld3D(gridPos);
double centerX = gridCorner.X + gridMap.CellSize / 2;
double centerY = gridCorner.Y + gridMap.CellSize / 2;
// 所有网格都基于高度层,统一处理
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 == CategoryAttributeManager.LogisticsElementType.)
{
if (_showDoorGrid)
{
cellType = GridVisualizationType.Door;
doorCells++;
}
}
else
{
// 其他可通行类型统一为通道样式(绿色)
if (_showWalkableGrid)
{
cellType = GridVisualizationType.Walkable;
channelCells++;
}
}
}
else
{
// 不可通行层 - 渲染为障碍物(灰色)
if (_showObstacleGrid)
{
cellType = GridVisualizationType.Obstacle;
obstacleCells++;
}
}
// 创建该层的可视化标记
if (cellType.HasValue)
{
var gridCenter = new Point3D(centerX, centerY, layerZ);
var marker = new GridMarker(gridCenter, 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 == CategoryAttributeManager.LogisticsElementType.Unknown)
{
// Unknown网格没有高度层用于调试
if (_showUnknownGrid)
{
var gridCenter = new Point3D(centerX, centerY, gridCorner.Z);
var marker = new GridMarker(gridCenter, GridVisualizationType.Unknown, x, y, gridCorner.Z);
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 (_pathDatabase != null && route != 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;
}
#endregion
}
}