3811 lines
150 KiB
C#
3811 lines
150 KiB
C#
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;
|
||
|
||
// 路径点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 _currentVehicleHeight = 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化渲染插件的默认值
|
||
/// 从配置文件读取网格大小和车辆参数,确保渲染插件在任何情况下都有正确的可视化效果
|
||
/// </summary>
|
||
private void InitializeRenderPluginDefaults()
|
||
{
|
||
try
|
||
{
|
||
var config = ConfigManager.Instance.Current;
|
||
|
||
// 从配置读取网格大小
|
||
double gridSizeInMeters = config.PathEditing.CellSizeMeters;
|
||
_renderPlugin.SetGridSize(gridSizeInMeters);
|
||
LogManager.Info($"[渲染插件初始化] 已设置网格大小: {gridSizeInMeters}米(来自配置)");
|
||
|
||
// 从配置读取车辆参数
|
||
double vehicleLength = config.PathEditing.VehicleLengthMeters;
|
||
double vehicleWidth = config.PathEditing.VehicleWidthMeters;
|
||
double vehicleHeight = config.PathEditing.VehicleHeightMeters;
|
||
double safetyMargin = config.PathEditing.SafetyMarginMeters;
|
||
|
||
_renderPlugin.SetVehicleParameters(
|
||
vehicleLength,
|
||
vehicleWidth,
|
||
vehicleHeight,
|
||
safetyMargin
|
||
);
|
||
|
||
LogManager.Info($"[渲染插件初始化] 已设置车辆参数: 长={vehicleLength:F1}m, 宽={vehicleWidth:F1}m, 高={vehicleHeight:F1}m, 安全间隙={safetyMargin:F2}m(来自配置)");
|
||
}
|
||
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;
|
||
|
||
// 保留原有事件以保持向后兼容
|
||
[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;
|
||
|
||
#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} ***");
|
||
|
||
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}");
|
||
}
|
||
}
|
||
|
||
public void RaisePathPointsListUpdated(PathRoute route, string updateReason)
|
||
{
|
||
try
|
||
{
|
||
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}");
|
||
}
|
||
}
|
||
|
||
#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)
|
||
{
|
||
try
|
||
{
|
||
CurrentPointType = pointType;
|
||
|
||
// 检查是否在自动路径模式 - 如果是,则不订阅PathPlanningManager的事件
|
||
bool shouldSubscribeToEvents = !IsInAutoPathMode;
|
||
LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}");
|
||
|
||
ActivateToolPlugin(shouldSubscribeToEvents);
|
||
PathEditState = PathEditState.AddingPoints;
|
||
LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents}");
|
||
}
|
||
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.OnToolPluginMouseClicked订阅");
|
||
}
|
||
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="vehicleRadius">车辆尺寸(米)</param>
|
||
/// <param name="safetyMargin">安全间隙(米)</param>
|
||
/// <param name="gridSize">网格精度(米)</param>
|
||
/// <param name="vehicleHeight">车辆高度(米)</param>
|
||
/// <param name="strategy">路径规划策略</param>
|
||
/// <returns>规划结果</returns>
|
||
public Task<PathRoute> AutoPlanPath(PathPoint startPoint, PathPoint endPoint, double vehicleRadius, double safetyMargin, double gridSize, double vehicleHeight, 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($"车辆半径: {vehicleRadius}m, 安全间隙: {safetyMargin}m, 车辆高度: {vehicleHeight}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, 车辆半径: {vehicleRadius}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,
|
||
vehicleRadius,
|
||
safetyMargin,
|
||
startPoint.Position,
|
||
endPoint.Position,
|
||
vehicleHeight
|
||
);
|
||
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, vehicleHeight);
|
||
}
|
||
|
||
// 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 vehicleHeightInModelUnits = vehicleHeight * UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
|
||
LogManager.Info($"使用2.5D模式进行路径查找,车辆高度: {vehicleHeight}m ({vehicleHeightInModelUnits:F2}模型单位),策略: {strategy}");
|
||
pathResult = pathFinder.FindPath(startPoint.Position, endPoint.Position, gridMap, channelCoverage, vehicleHeightInModelUnits, 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:HHmmss}";
|
||
|
||
// 🔥 关键修复:在创建路径前设置_currentGridMap,确保GetSpeedLimitAtPosition能正常工作
|
||
_currentGridMap = gridMap;
|
||
|
||
var autoRoute = CreateAutoPathRoute(pathResult, routeName);
|
||
|
||
// 保存GridMap和参数到PathRoute以便后续恢复网格可视化
|
||
autoRoute.AssociatedGridMap = gridMap;
|
||
autoRoute.GridSize = gridSize;
|
||
// 将vehicleRadius拆分为长宽(暂时使用相同值,后续可以传入更详细的参数)
|
||
autoRoute.MaxVehicleLength = vehicleRadius * 2; // 车辆半径转换为长度
|
||
autoRoute.MaxVehicleWidth = vehicleRadius * 2; // 车辆半径转换为宽度
|
||
autoRoute.MaxVehicleHeight = vehicleHeight;
|
||
autoRoute.SafetyMargin = safetyMargin;
|
||
LogManager.Info($"已保存GridMap到路径: {routeName}, 网格大小: {gridSize}米");
|
||
|
||
// 7. 添加到路径集合
|
||
if (!_routes.Contains(autoRoute))
|
||
{
|
||
_routes.Add(autoRoute);
|
||
// 保存自动生成的路径到数据库
|
||
SavePathToDatabase(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
|
||
{
|
||
// 直接设置字段,绕过事件触发
|
||
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)
|
||
{
|
||
PathEditState = PathEditState.Editing;
|
||
EditingRoute = route ?? throw new ArgumentNullException(nameof(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>
|
||
/// <returns>创建的新路径</returns>
|
||
public PathRoute StartCreatingNewRoute(string routeName = null)
|
||
{
|
||
try
|
||
{
|
||
// 设置为创建状态
|
||
PathEditState = PathEditState.Creating;
|
||
|
||
// 创建新路径
|
||
var newRoute = new PathRoute(routeName ?? $"路径{DateTime.Now:yyyyMMdd_HHmmss}");
|
||
_editingRoute = newRoute;
|
||
SetCurrentRouteInternal(newRoute, triggerEvent: true);
|
||
|
||
// 自动选择所有可通行的物流模型
|
||
AutoSelectLogisticsChannels();
|
||
|
||
// 检查是否有可通行的物流模型
|
||
if (_walkableAreas == null || _walkableAreas.Count == 0)
|
||
{
|
||
RaiseErrorOccurred("没有找到任何可通行的物流模型,请先为模型设置可通行的物流属性");
|
||
// 重置状态
|
||
SwitchToViewingState();
|
||
return null;
|
||
}
|
||
|
||
// 智能管理ToolPlugin状态
|
||
ManageToolPluginForEditState();
|
||
|
||
RaiseStatusChanged($"正在新建路径: {newRoute.Name} - 请在3D视图中可通行的物流模型上点击设置路径点", PathPlanningStatusType.Info);
|
||
|
||
return newRoute;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
RaiseErrorOccurred($"开始新建路径失败: {ex.Message}", ex);
|
||
// 确保在出错时也重置状态
|
||
SwitchToViewingState();
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 完成当前编辑并保存
|
||
/// </summary>
|
||
/// <returns>是否成功完成编辑</returns>
|
||
public bool FinishEditing()
|
||
{
|
||
if (!IsInEditableState)
|
||
{
|
||
RaiseErrorOccurred("当前不在编辑状态,无法完成编辑");
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 自动设置最后一个点为终点
|
||
if (CurrentRoute != null && CurrentRoute.Points.Count > 1)
|
||
{
|
||
var lastPoint = CurrentRoute.Points.Last();
|
||
if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点
|
||
{
|
||
lastPoint.Type = PathPointType.EndPoint;
|
||
lastPoint.Name = GeneratePointName(PathPointType.EndPoint);
|
||
|
||
// 更新3D路径可视化
|
||
// 重新渲染整个路径以反映类型变更
|
||
_renderPlugin?.RenderPath(CurrentRoute);
|
||
|
||
LogManager.Info($"已自动设置最后一个点为终点: {lastPoint.Name}");
|
||
}
|
||
}
|
||
|
||
// === 应用曲线化 ===
|
||
if (CurrentRoute != null)
|
||
{
|
||
double samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep;
|
||
|
||
// 使用配置文件中的默认转弯半径
|
||
CurrentRoute.TurnRadius = ConfigManager.Instance.Current.PathEditing.DefaultPathTurnRadius;
|
||
|
||
PathCurveEngine.ApplyCurvatureToRoute(CurrentRoute, samplingStep);
|
||
LogManager.Info($"路径曲线化完成: {CurrentRoute.Name}, 转弯半径: {CurrentRoute.TurnRadius:F2}m, 边数: {CurrentRoute.Edges.Count}");
|
||
}
|
||
|
||
// 如果是创建模式,将当前路径添加到路径集合
|
||
if (_pathEditState == PathEditState.Creating && CurrentRoute != null)
|
||
{
|
||
if (!_routes.Contains(CurrentRoute))
|
||
{
|
||
_routes.Add(CurrentRoute);
|
||
|
||
// 保存到数据库
|
||
SavePathToDatabase(CurrentRoute);
|
||
|
||
// 添加历史记录
|
||
var historyEntry = new PathHistoryEntry(
|
||
CurrentRoute.Id,
|
||
PathHistoryOperationType.Created,
|
||
CurrentRoute,
|
||
$"创建新路径: {CurrentRoute.Name}");
|
||
_historyManager.AddHistoryEntry(historyEntry);
|
||
}
|
||
}
|
||
|
||
// 如果是编辑模式,保存编辑历史
|
||
if (_pathEditState == PathEditState.Editing && EditingRoute != null)
|
||
{
|
||
// 更新数据库
|
||
SavePathToDatabase(EditingRoute);
|
||
|
||
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>
|
||
/// <returns>是否成功更新</returns>
|
||
public bool UpdatePointPosition()
|
||
{
|
||
try
|
||
{
|
||
if (PathEditState != PathEditState.EditingPoint ||
|
||
_editingPointIndex == -1 ||
|
||
_editingPreviewPoint == null ||
|
||
CurrentRoute == null)
|
||
{
|
||
RaiseErrorOccurred("当前不在修改路径点状态或没有预览点");
|
||
return false;
|
||
}
|
||
|
||
// 更新路径点位置
|
||
var pointToUpdate = CurrentRoute.Points[_editingPointIndex];
|
||
pointToUpdate.Position = _editingPreviewPoint.Position;
|
||
|
||
// 添加历史记录
|
||
var historyEntry = new PathHistoryEntry(
|
||
CurrentRoute.Id,
|
||
PathHistoryOperationType.Edited,
|
||
CurrentRoute,
|
||
$"修改路径点 {pointToUpdate.Name} 位置");
|
||
_historyManager.AddHistoryEntry(historyEntry);
|
||
|
||
// 清理修改预览可视化(会自动清除预览并重新渲染正常路径)
|
||
ClearEditingPreviewVisualization();
|
||
|
||
// 清理修改状态
|
||
ClearEditingState();
|
||
|
||
// 切换回查看状态
|
||
PathEditState = PathEditState.Viewing;
|
||
|
||
// 停用ToolPlugin
|
||
DeactivateToolPlugin();
|
||
|
||
// 触发路径点列表更新事件
|
||
RaisePathPointsListUpdated(CurrentRoute, "路径点修改完成");
|
||
|
||
// 保存到数据库
|
||
SavePathToDatabase(CurrentRoute);
|
||
|
||
RaiseStatusChanged($"路径点 {pointToUpdate.Name} 修改完成", PathPlanningStatusType.Success);
|
||
LogManager.Info($"路径点修改完成: {pointToUpdate.Name}");
|
||
|
||
return true;
|
||
}
|
||
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.Points.Add(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>
|
||
/// 设置预览点位置(仅用于预览,不添加到路径中)
|
||
/// </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
|
||
{
|
||
// 创建正式的路径点
|
||
var confirmPoint = new PathPoint
|
||
{
|
||
Name = GeneratePointName(_previewPoint.Type),
|
||
Position = _previewPoint.Position,
|
||
Type = _previewPoint.Type
|
||
};
|
||
|
||
// 根据保存的插入索引决定添加方式
|
||
if (_previewInsertIndex >= 0 && _previewInsertIndex <= CurrentRoute.Points.Count)
|
||
{
|
||
// 在指定位置插入路径点
|
||
CurrentRoute.Points.Insert(_previewInsertIndex, confirmPoint);
|
||
LogManager.Info($"路径点已插入到索引 {_previewInsertIndex}: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})");
|
||
}
|
||
else
|
||
{
|
||
// 如果没有有效的插入索引,添加到末尾(原来的行为)
|
||
CurrentRoute.Points.Add(confirmPoint);
|
||
LogManager.Info($"路径点已添加到末尾: {confirmPoint.Name}, 位置: ({confirmPoint.Position.X:F2}, {confirmPoint.Position.Y:F2}, {confirmPoint.Position.Z:F2})");
|
||
}
|
||
|
||
// 清除预览状态
|
||
ClearPreviewPoint();
|
||
|
||
// 绘制3D路径可视化
|
||
try
|
||
{
|
||
DrawRouteVisualization(CurrentRoute, isAutoPath: false);
|
||
LogManager.Info($"手工路径3D可视化已更新: {confirmPoint.Name}");
|
||
}
|
||
catch (Exception renderEx)
|
||
{
|
||
LogManager.Error($"绘制手工路径3D可视化失败: {renderEx.Message}");
|
||
}
|
||
|
||
// 触发路径点添加事件
|
||
RaisePathPointOperation(PathPointOperationType.Added, confirmPoint, CurrentRoute);
|
||
|
||
// 触发路径列表更新事件
|
||
RaisePathPointsListUpdated(CurrentRoute, "确认添加预览点");
|
||
|
||
// 保存到数据库
|
||
SavePathToDatabase(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)
|
||
{
|
||
// 检查当前选中的通道状态
|
||
LogManager.Debug($"[手动编辑] 当前_selectedChannels状态: {(_walkableAreas == null ? "NULL" : $"包含{_walkableAreas.Count}个项目")}");
|
||
|
||
// 如果没有选中的通道,尝试实时搜索
|
||
if (_walkableAreas == null || _walkableAreas.Count == 0)
|
||
{
|
||
LogManager.Debug("[手动编辑] 没有预选通道,开始实时搜索可通行的物流模型");
|
||
SearchAndSetTraversableChannels();
|
||
}
|
||
|
||
// 检查是否在可通行的物流模型内并处理点击
|
||
if (_walkableAreas != null && _walkableAreas.Any())
|
||
{
|
||
bool isInTraversableLogisticsModel = IsItemInSelectedChannels(pickResult.ModelItem) ||
|
||
IsItemChildOfSelectedChannels(pickResult.ModelItem);
|
||
|
||
LogManager.Debug($"[手动编辑] 在可通行的物流模型内: {isInTraversableLogisticsModel}");
|
||
|
||
if (isInTraversableLogisticsModel)
|
||
{
|
||
// 手动路径编辑 - 根据当前模式处理点击
|
||
if (PathEditState == PathEditState.AddingPoints)
|
||
{
|
||
// 添加路径点模式 - 使用预览点
|
||
LogManager.Debug("[手动编辑] 设置预览点位置");
|
||
var previewPoint = SetPreviewPoint(pickResult.Point);
|
||
|
||
if (previewPoint != null)
|
||
{
|
||
LogManager.Debug($"[手动编辑] ✓ 预览点已设置: {previewPoint.Name}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[手动编辑] ✗ 预览点设置失败");
|
||
}
|
||
}
|
||
else if (PathEditState == PathEditState.EditingPoint)
|
||
{
|
||
// 修改路径点模式 - 设置预览位置
|
||
LogManager.Debug("[手动编辑] 设置修改路径点预览位置");
|
||
SetEditingPreviewPoint(pickResult.Point);
|
||
LogManager.Debug($"[手动编辑] ✓ 修改路径点预览位置已设置: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})");
|
||
}
|
||
else
|
||
{
|
||
// 其他编辑模式 - 保持原有逻辑
|
||
LogManager.Debug("[手动编辑] 调用AddPathPointIn3D添加路径点");
|
||
var pathPoint = AddPathPointIn3D(pickResult.Point);
|
||
|
||
if (pathPoint != null)
|
||
{
|
||
LogManager.Debug($"[手动编辑] ✓ 路径点添加成功: {pathPoint.Name}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[手动编辑] ✗ 路径点添加失败");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Debug("[手动编辑] ✗ 点击位置不在可通行的物流模型内");
|
||
RaiseErrorOccurred("点击位置不在物流通道内,请选择有效的物流路径位置");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[手动编辑] ✗ 未找到可通行的物流模型");
|
||
RaiseErrorOccurred("未找到可通行的物流通道,请先选择或配置物流通道");
|
||
}
|
||
}
|
||
|
||
/// <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>
|
||
/// 获取PathDatabase实例
|
||
/// </summary>
|
||
public PathDatabase GetPathDatabase()
|
||
{
|
||
return _pathDatabase;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新路径并保存到数据库
|
||
/// </summary>
|
||
public void UpdateRoute(PathRoute route)
|
||
{
|
||
if (route == null) return;
|
||
SavePathToDatabase(route);
|
||
}
|
||
|
||
#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.Points.Add(pathPoint);
|
||
}
|
||
|
||
// 计算路径长度
|
||
route.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, _currentVehicleHeight);
|
||
}
|
||
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="vehicleHeight">车辆高度(米),用于判断层高是否足够</param>
|
||
public void VisualizeGridCells(GridMap gridMap, double vehicleHeight = 2.0)
|
||
{
|
||
if (gridMap == null)
|
||
{
|
||
LogManager.Warning("[网格可视化] 网格地图为null,无法进行可视化");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 保存当前网格地图和车辆高度用于后续刷新
|
||
_currentGridMap = gridMap;
|
||
_currentVehicleHeight = vehicleHeight;
|
||
LogManager.Info($"[网格可视化] 开始可视化网格:{gridMap.Width}x{gridMap.Height}, 车辆高度:{vehicleHeight}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 channelRoute = new PathRoute("GridVisualization_Channel")
|
||
{
|
||
Id = "grid_visualization_channel"
|
||
};
|
||
|
||
var unknownRoute = new PathRoute("GridVisualization_Unknown")
|
||
{
|
||
Id = "grid_visualization_unknown"
|
||
};
|
||
|
||
var obstacleRoute = new PathRoute("GridVisualization_Obstacle")
|
||
{
|
||
Id = "grid_visualization_obstacle"
|
||
};
|
||
|
||
var doorRoute = new PathRoute("GridVisualization_Door")
|
||
{
|
||
Id = "grid_visualization_door"
|
||
};
|
||
|
||
// 统计变量
|
||
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;
|
||
|
||
PathRoute targetRoute = null;
|
||
string gridTypeName = "";
|
||
|
||
// 仅根据 layer.IsWalkable 决定渲染
|
||
if (layer.IsWalkable)
|
||
{
|
||
// 可通行层 - 门特殊处理,其他统一样式
|
||
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.门)
|
||
{
|
||
if (_showDoorGrid)
|
||
{
|
||
targetRoute = doorRoute;
|
||
gridTypeName = "门";
|
||
doorCells++;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 其他可通行类型统一为通道样式(绿色)
|
||
if (_showWalkableGrid)
|
||
{
|
||
targetRoute = channelRoute;
|
||
gridTypeName = "通道";
|
||
channelCells++;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 不可通行层 - 渲染为障碍物(灰色)
|
||
if (_showObstacleGrid)
|
||
{
|
||
targetRoute = obstacleRoute;
|
||
gridTypeName = "障碍";
|
||
obstacleCells++;
|
||
}
|
||
}
|
||
|
||
// 创建该层的可视化点
|
||
if (targetRoute != null)
|
||
{
|
||
var gridCenter = new Point3D(centerX, centerY, layerZ);
|
||
|
||
var gridPoint = new PathPoint
|
||
{
|
||
Position = gridCenter,
|
||
Name = $"网格_{gridTypeName}({x},{y})_Z{layerZ:F2}",
|
||
Type = PathPointType.WayPoint,
|
||
Index = totalVisualized,
|
||
Notes = $"GridType:{cell.CellType}, LayerZ={layerZ:F2}, IsWalkable={layer.IsWalkable}"
|
||
};
|
||
|
||
targetRoute.Points.Add(gridPoint);
|
||
totalVisualized++;
|
||
}
|
||
}
|
||
}
|
||
else if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown)
|
||
{
|
||
// Unknown网格没有高度层,用于调试
|
||
if (_showUnknownGrid)
|
||
{
|
||
var gridCenter = new Point3D(centerX, centerY, gridCorner.Z);
|
||
var gridPoint = new PathPoint
|
||
{
|
||
Position = gridCenter,
|
||
Name = $"网格_空洞({x},{y})",
|
||
Type = PathPointType.WayPoint,
|
||
Index = totalVisualized,
|
||
Notes = $"GridType:Unknown, NoLayers"
|
||
};
|
||
unknownRoute.Points.Add(gridPoint);
|
||
unknownCells++;
|
||
totalVisualized++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 分别渲染四种类型的网格点
|
||
if (channelRoute.Points.Count > 0)
|
||
{
|
||
renderPlugin.RenderPointOnly(channelRoute);
|
||
LogManager.Info($"[网格可视化] 渲染可通行网格层: {channelRoute.Points.Count} 个(绿色)");
|
||
}
|
||
|
||
if (doorRoute.Points.Count > 0)
|
||
{
|
||
renderPlugin.RenderPointOnly(doorRoute);
|
||
LogManager.Info($"[网格可视化] 渲染门网格层: {doorRoute.Points.Count} 个(50%透明绿色)");
|
||
}
|
||
|
||
if (unknownRoute.Points.Count > 0)
|
||
{
|
||
renderPlugin.RenderPointOnly(unknownRoute);
|
||
LogManager.Info($"[网格可视化] 渲染Unknown网格: {unknownRoute.Points.Count} 个(红色,调试用)");
|
||
}
|
||
|
||
if (obstacleRoute.Points.Count > 0)
|
||
{
|
||
renderPlugin.RenderPointOnly(obstacleRoute);
|
||
LogManager.Info($"[网格可视化] 渲染障碍物网格层: {obstacleRoute.Points.Count} 个(灰色)");
|
||
}
|
||
|
||
// 输出统计信息
|
||
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)
|
||
{
|
||
// 清除所有网格可视化路径(包括旧的和新的)
|
||
renderPlugin.RemovePath("grid_visualization_all"); // 旧的单一路径
|
||
renderPlugin.RemovePath("grid_visualization_channel"); // 新的通道路径
|
||
renderPlugin.RemovePath("grid_visualization_unknown"); // 新的Unknown路径
|
||
renderPlugin.RemovePath("grid_visualization_obstacle"); // 新的障碍物路径
|
||
renderPlugin.RemovePath("grid_visualization_door"); // 新的门路径
|
||
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}");
|
||
}
|
||
|
||
// 历史路径加载完成后,触发RouteGenerated事件通知UI刷新
|
||
// 使用特殊的生成方法标记这是数据库加载
|
||
foreach (var route in _routes)
|
||
{
|
||
RaiseRouteGenerated(route, RouteGenerationMethod.DatabaseLoad);
|
||
}
|
||
LogManager.Info($"已触发{_routes.Count}个RouteGenerated事件,通知UI刷新历史路径");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info("数据库中没有历史路径记录");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"加载历史路径失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存路径时同时保存到数据库
|
||
/// </summary>
|
||
private 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
|
||
}
|
||
}
|