using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.Drawing.Imaging; using System.Windows; using System.Windows.Input; using System.Threading.Tasks; using System.Linq; using System.IO; using System.Numerics; using Microsoft.Win32; using Autodesk.Navisworks.Api; using NavisApplication = Autodesk.Navisworks.Api.Application; using NavisworksTransport.Core; using NavisworksTransport.Core.Config; using NavisworksTransport.Commands; using NavisworksTransport.UI.WPF.Views; using NavisworksTransport.UI.WPF.Models; using NavisworksTransport.Utils; using NavisworksTransport.Utils.CoordinateSystem; using NavisworksTransport.Utils.GeometryAnalysis; namespace NavisworksTransport.UI.WPF.ViewModels { /// /// 路径策略选项辅助类 /// public class PathStrategyOption { /// /// 策略值 /// public PathStrategy Value { get; set; } /// /// 显示名称 /// public string DisplayName { get; set; } /// /// 详细描述 /// public string Description { get; set; } } /// /// Rail 构型选项辅助类 /// /// 枚举类型 public class RailConfigOption { /// /// 配置值 /// public T Value { get; set; } /// /// 显示名称 /// public string DisplayName { get; set; } /// /// 详细描述 /// public string Description { get; set; } } /// /// 路径编辑页面专用ViewModel /// public class PathEditingViewModel : ViewModelBase, IDisposable { #region 私有字段 private PathPlanningManager _pathPlanningManager; private readonly UIStateManager _uiStateManager; private readonly PathDataManager _pathDataManager; // 路径集合 private ObservableCollection _pathRoutes; private PathRouteViewModel _selectedPathRoute; private PathPointViewModel _selectedPathPoint; private bool _isPathEditMode; // 自动路径规划参数 private string _autoPathStartPoint = "未选择"; private string _autoPathEndPoint = "未选择"; private Point3D _startPoint3D; private Point3D _endPoint3D; private bool _hasStartPoint = false; private bool _hasEndPoint = false; private bool _isSelectingStartPoint = false; private bool _isSelectingEndPoint = false; // 物体参数 - 改为三个独立参数(从配置初始化) private double _objectLength; // 物体长度(米) private double _objectWidth; // 物体宽度(米) private double _objectHeight; // 物体高度(米) private double _safetyMargin; // 安全间隙(米) // 网格大小参数(从配置初始化) private bool _isGridSizeManuallyEnabled = false; // 是否启用手动网格大小设置 private double _gridSize; // 网格大小(米) // 可视化参数 private bool _showWalkableGrid = false; private bool _showObstacleGrid = false; private bool _showUnknownGrid = false; private bool _showDoorGrid = false; private GridPointType _gridPointType = GridPointType.Rectangle; // 三种渲染模式独立开关 private bool _showControlVisualization = true; // 默认显示控制点连线 private bool _showPathLines = true; // 默认显示路径线(地面连线) private bool _showObjectSpace = false; // 默认不显示通行空间 // 渲染模式已改为独立开关(_showControlVisualization, _showPathLines, _showObjectSpace) // 下面的互斥属性已废弃并删除 // 路径策略参数 private PathStrategyOption _selectedPathStrategy; private ObservableCollection _pathStrategyOptions; private ObservableCollection> _railMountModeOptions; private const double DefaultAssemblyReferenceRodLengthInMeters = 20.0; private static readonly double DefaultAssemblyReferenceRodDiameterInMeters = CalculateDefaultAssemblyReferenceRodDiameterInMeters(); private const double DefaultAssemblyAnchorVerticalOffsetInMeters = 0.0; private const double AssemblyVisualizationPointDiameterInMeters = 0.05; private const double RailNormalOffsetNudgeStepInMeters = 0.1; private const double DefaultAssemblySphereCenterX = 0.0; private const double DefaultAssemblySphereCenterY = 0.0; private const double DefaultAssemblySphereCenterZ = 0.0; private readonly RailAssemblyWorkflowContext _railAssemblyContext = new RailAssemblyWorkflowContext( DefaultAssemblyReferenceRodLengthInMeters, DefaultAssemblyReferenceRodDiameterInMeters, DefaultAssemblyAnchorVerticalOffsetInMeters, DefaultAssemblySphereCenterX, DefaultAssemblySphereCenterY, DefaultAssemblySphereCenterZ); private const string AssemblyAnchorMarkerPathId = "assembly_anchor_marker"; private const string AssemblyCenterGuideLinePathId = "assembly_center_guide_line"; private const string AssemblyReferenceLinePathId = "assembly_reference_line"; private const string AssemblyEndFaceSeedPathId = "assembly_end_face_seed_points"; private const string AssemblyEndFaceCenterPathId = "assembly_end_face_center"; private const string AssemblyEndFaceNormalPathId = "assembly_end_face_normal"; private const string AssemblyInstallationPickPointPathId = "assembly_install_pick_point"; private const string AssemblyInstallationAnchorPointPathId = "assembly_install_anchor_point"; private const string AssemblyInstallationCenterLinePathId = "assembly_install_center_line"; private const string AssemblyInstallationOffsetLinePathId = "assembly_install_offset_line"; private const string AssemblyInstallationPlanePathId = "assembly_install_plane"; private const double AssemblyInstallationPlanePaddingInMeters = 0.5; private const double AssemblyInstallationLineLengthScale = 1.2; private string _assemblyTerminalObjectName { get => _railAssemblyContext.TerminalObjectName; set => _railAssemblyContext.TerminalObjectName = value; } private string _assemblyTerminalObjectInfo { get => _railAssemblyContext.TerminalObjectInfo; set => _railAssemblyContext.TerminalObjectInfo = value; } private double _assemblyReferenceRodLengthInMeters { get => _railAssemblyContext.ReferenceRodLengthInMeters; set => _railAssemblyContext.ReferenceRodLengthInMeters = value; } private double _assemblyReferenceRodDiameterInMeters { get => _railAssemblyContext.ReferenceRodDiameterInMeters; set => _railAssemblyContext.ReferenceRodDiameterInMeters = value; } private double _assemblyAnchorVerticalOffsetInMeters { get => _railAssemblyContext.AnchorVerticalOffsetInMeters; set => _railAssemblyContext.AnchorVerticalOffsetInMeters = value; } private double _assemblySphereCenterX { get => _railAssemblyContext.SphereCenterX; set => _railAssemblyContext.SphereCenterX = value; } private double _assemblySphereCenterY { get => _railAssemblyContext.SphereCenterY; set => _railAssemblyContext.SphereCenterY = value; } private double _assemblySphereCenterZ { get => _railAssemblyContext.SphereCenterZ; set => _railAssemblyContext.SphereCenterZ = value; } private string _assemblyStartPointText { get => _railAssemblyContext.StartPointText; set => _railAssemblyContext.StartPointText = value; } private RailMountMode _assemblyMountMode { get => _railAssemblyContext.MountMode; set => _railAssemblyContext.MountMode = value; } private bool _hasAssemblyTerminalObject { get => _railAssemblyContext.HasTerminalObject; set => _railAssemblyContext.HasTerminalObject = value; } private bool _isSelectingAssemblyStartPoint { get => _railAssemblyContext.IsSelectingStartPoint; set => _railAssemblyContext.IsSelectingStartPoint = value; } private bool _isSelectingAssemblyEndFacePoints { get => _railAssemblyContext.IsSelectingEndFacePoints; set => _railAssemblyContext.IsSelectingEndFacePoints = value; } private bool _isSelectingAssemblyInstallationPoint { get => _railAssemblyContext.IsSelectingInstallationPoint; set => _railAssemblyContext.IsSelectingInstallationPoint = value; } private RailAssemblyWorkflowMode _railAssemblyWorkflowMode { get => _railAssemblyContext.WorkflowMode; set => _railAssemblyContext.WorkflowMode = value; } private bool _hasAssemblyEndFaceAnalysis { get => _railAssemblyContext.HasEndFaceAnalysis; set => _railAssemblyContext.HasEndFaceAnalysis = value; } private bool _hasAssemblyInstallationReference { get => _railAssemblyContext.HasInstallationReference; set => _railAssemblyContext.HasInstallationReference = value; } private ModelItem _assemblyTerminalObject { get => _railAssemblyContext.TerminalObject; set => _railAssemblyContext.TerminalObject = value; } private Point3D _assemblyStartPoint { get => _railAssemblyContext.StartPoint; set => _railAssemblyContext.StartPoint = value; } private Point3D _assemblyEndFaceCenterPoint { get => _railAssemblyContext.EndFaceCenterPoint; set => _railAssemblyContext.EndFaceCenterPoint = value; } private Vector3 _assemblyEndFaceNormal { get => _railAssemblyContext.EndFaceNormal; set => _railAssemblyContext.EndFaceNormal = value; } private Point3D _assemblyInstallationPickPoint { get => _railAssemblyContext.InstallationPickPoint; set => _railAssemblyContext.InstallationPickPoint = value; } private Point3D _assemblyInstallationBaseAnchorPoint { get => _railAssemblyContext.InstallationBaseAnchorPoint; set => _railAssemblyContext.InstallationBaseAnchorPoint = value; } private Point3D _assemblyInstallationAnchorPoint { get => _railAssemblyContext.InstallationAnchorPoint; set => _railAssemblyContext.InstallationAnchorPoint = value; } private Vector3 _assemblyInstallationPlaneNormal { get => _railAssemblyContext.InstallationPlaneNormal; set => _railAssemblyContext.InstallationPlaneNormal = value; } private Vector3 _assemblyInstallationPlaneSpanDirection { get => _railAssemblyContext.InstallationPlaneSpanDirection; set => _railAssemblyContext.InstallationPlaneSpanDirection = value; } private double _assemblyInstallationOffsetDistanceInMeters { get => _railAssemblyContext.InstallationOffsetDistanceInMeters; set => _railAssemblyContext.InstallationOffsetDistanceInMeters = value; } private string _assemblyInstallationReferenceRouteId { get => _railAssemblyContext.InstallationReferenceRouteId; set => _railAssemblyContext.InstallationReferenceRouteId = value; } private List _assemblyInstallationSeedPoints => _railAssemblyContext.InstallationSeedPoints; private List _assemblyEndFaceSeedPoints => _railAssemblyContext.EndFaceSeedPoints; // 自动路径起点和终点的路径对象引用(用于正确的ID管理) private PathRoute _autoPathStartPointRoute = null; private PathRoute _autoPathEndPointRoute = null; // 资源清理管理 private bool _disposed = false; // 多层吊装模式 private bool _isMultiLevelHoistingMode = false; private ObservableCollection _multiLevelItems = new ObservableCollection(); private Point3D _multiLevelStartPoint; private Point3D _multiLevelEndPoint; private bool _hasMultiLevelStartPoint = false; private bool _hasMultiLevelEndPoint = false; private string _multiLevelStartPointText = "未选择"; private string _multiLevelEndPointText = "未选择"; private string _multiLevelStatsText = "层级数: 0 | 预估路径点数: 0"; private bool _isSelectingMultiLevelStartPoint = false; private bool _isSelectingMultiLevelEndPoint = false; private HoistingLevelItem _currentSelectingLevelTarget; #endregion #region 公共属性 public ObservableCollection PathRoutes { get => _pathRoutes; set => SetProperty(ref _pathRoutes, value); } public PathRouteViewModel SelectedPathRoute { get => _selectedPathRoute; set { if (SetProperty(ref _selectedPathRoute, value)) { // 路径变更时清除选中的路径点 SelectedPathPoint = null; ResetRailAssemblyWorkflowState(announce: false); // 🔧 修复:同步PathPlanningManager的CurrentRoute if (_pathPlanningManager != null && value != null) { // 查找对应的Core路径对象 var coreRoute = _pathPlanningManager.Routes?.FirstOrDefault(r => r.Id == value.Id) ?? _pathPlanningManager.Routes?.FirstOrDefault(r => r.Name == value.Name); if (coreRoute != null) { if (coreRoute.PathType == PathType.Rail) { AssemblyMountMode = coreRoute.RailMountMode; _assemblyAnchorVerticalOffsetInMeters = UnitsConverter.ConvertToMeters(coreRoute.RailNormalOffset); OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); } // 🔥 先设置可视化模式(在 SetCurrentRoute 之前,确保事件触发时状态已就绪) UpdatePassageSpaceVisualizationForPathType(coreRoute.PathType); _pathPlanningManager.SetCurrentRoute(coreRoute); LogManager.Info($"UI路径切换:已同步PathPlanningManager的CurrentRoute到 {value.Name}"); // 自动调整视角到路径中心(只有路径有点时才调整) if (coreRoute.Points.Count > 0) { try { ViewpointHelper.AdjustViewpointForPath(coreRoute); LogManager.Info($"UI路径切换:已自动调整视角: {value.Name}, 类型={coreRoute.PathType}"); } catch (Exception ex) { LogManager.Error($"UI路径切换:调整视角失败: {ex.Message}"); } } else { LogManager.Info($"UI路径切换:路径还没有点,跳过视角调整: {value.Name}"); } } else { LogManager.Warning($"UI路径切换:未找到对应的Core路径 {value.Name}"); } } else if (_pathPlanningManager != null && value == null) { // 如果选择了null,也要同步清除CurrentRoute _pathPlanningManager.SetCurrentRoute(null); LogManager.Info("UI路径切换:已清除PathPlanningManager的CurrentRoute"); } // 更新命令状态 OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); OnPropertyChanged(nameof(CanExecuteClearPath)); OnPropertyChanged(nameof(CanExecuteExportPath)); OnPropertyChanged(nameof(CanExecuteSaveAsPath)); OnPropertyChanged(nameof(CanExecuteModifyPoint)); // 🔥 路径类型改变时,检查是否可以使用路径线 OnPropertyChanged(nameof(CanUsePathLines)); OnPropertyChanged(nameof(IsRailRouteSelected)); OnPropertyChanged(nameof(SelectedRailMountMode)); OnPropertyChanged(nameof(SelectedRailNormalOffsetInMeters)); OnPropertyChanged(nameof(CanRepositionRailStartPoint)); NotifyRailAssemblyCommandStateChanged(); if (!CanUsePathLines && ShowPathLines) { // 吊装和空轨路径不能使用路径线,自动关闭 ShowPathLines = false; LogManager.Info("[路径可视化] 切换到吊装/空轨路径,自动关闭路径线"); } // 实现路径选择时的可视化切换 UpdatePathVisualization(); } } } public PathPointViewModel SelectedPathPoint { get => _selectedPathPoint; set { if (SetProperty(ref _selectedPathPoint, value)) { // 更新修改路径点按钮的启用状态 OnPropertyChanged(nameof(CanExecuteModifyPoint)); // 路径点选中可视化:将选中的路径点显示为预览样式 try { if (value != null) { // 从PathPointViewModel创建PathPoint对象 var pathPoint = new PathPoint( new Point3D(value.X, value.Y, value.Z), value.Name, value.Type ) { Id = value.Id }; // 将选中的路径点设为预览样式 PathPointRenderPlugin.Instance.RenderPreviewPoint(pathPoint); LogManager.Info($"路径点已设为预览样式: {value.Name}"); } else { // 清除预览样式 PathPointRenderPlugin.Instance.ClearPreviewPoint(); LogManager.Info("已清除路径点预览样式"); } } catch (Exception ex) { LogManager.Error($"路径点选中可视化失败: {ex.Message}"); } } } } public bool IsRailRouteSelected => SelectedPathRoute?.PathType == PathType.Rail; public ObservableCollection> RailMountModeOptions { get => _railMountModeOptions; } public RailMountMode SelectedRailMountMode { get => GetSelectedCoreRoute()?.RailMountMode ?? RailMountMode.UnderRail; set { var coreRoute = GetSelectedCoreRoute(); if (coreRoute == null || coreRoute.PathType != PathType.Rail || coreRoute.RailMountMode == value) { return; } coreRoute.RailMountMode = value; AssemblyMountMode = value; ApplyRailRouteConfigurationChange(coreRoute, $"安装方式已更新为 {value}"); OnPropertyChanged(nameof(SelectedRailMountMode)); } } public double SelectedRailNormalOffsetInMeters { get { var coreRoute = GetSelectedCoreRoute(); if (coreRoute == null || coreRoute.PathType != PathType.Rail) { return 0.0; } return UnitsConverter.ConvertToMeters(coreRoute.RailNormalOffset); } set { var coreRoute = GetSelectedCoreRoute(); if (coreRoute == null || coreRoute.PathType != PathType.Rail) { return; } double offsetInModelUnits = UnitsConverter.ConvertFromMeters(value); if (Math.Abs(coreRoute.RailNormalOffset - offsetInModelUnits) < 1e-6) { return; } _assemblyAnchorVerticalOffsetInMeters = value; OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); coreRoute.RailNormalOffset = offsetInModelUnits; if (IsAssemblyInstallationReferenceActiveForRoute(coreRoute)) { UpdateAssemblyInstallationAnchorFromVerticalOffset(); PersistAssemblyInstallationReferenceToRailRoute(coreRoute, $"安装法向偏移已更新为 {value:F3} 米"); } else { ApplyRailRouteConfigurationChange(coreRoute, $"安装法向偏移已更新为 {value:F3} 米"); } OnPropertyChanged(nameof(SelectedRailNormalOffsetInMeters)); } } public string AssemblyTerminalObjectName { get => _assemblyTerminalObjectName; set { if (string.Equals(_assemblyTerminalObjectName, value, StringComparison.Ordinal)) { return; } _assemblyTerminalObjectName = value; OnPropertyChanged(nameof(AssemblyTerminalObjectName)); } } public string AssemblyTerminalObjectInfo { get => _assemblyTerminalObjectInfo; set { if (string.Equals(_assemblyTerminalObjectInfo, value, StringComparison.Ordinal)) { return; } _assemblyTerminalObjectInfo = value; OnPropertyChanged(nameof(AssemblyTerminalObjectInfo)); } } public double AssemblyReferenceRodLengthInMeters { get => _assemblyReferenceRodLengthInMeters; set { if (Math.Abs(_assemblyReferenceRodLengthInMeters - value) < 1e-9) { return; } _assemblyReferenceRodLengthInMeters = value; OnPropertyChanged(nameof(AssemblyReferenceRodLengthInMeters)); } } public double AssemblyReferenceRodDiameterInMeters { get => _assemblyReferenceRodDiameterInMeters; set { if (Math.Abs(_assemblyReferenceRodDiameterInMeters - value) < 1e-9) { return; } _assemblyReferenceRodDiameterInMeters = value; OnPropertyChanged(nameof(AssemblyReferenceRodDiameterInMeters)); } } public double AssemblyAnchorVerticalOffsetInMeters { get => _assemblyAnchorVerticalOffsetInMeters; set { if (Math.Abs(_assemblyAnchorVerticalOffsetInMeters - value) < 1e-9) { return; } _assemblyAnchorVerticalOffsetInMeters = value; OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); if (HasAssemblyTerminalObject) { var coreRoute = GetSelectedCoreRoute(); if (_hasAssemblyInstallationReference) { UpdateAssemblyInstallationAnchorFromVerticalOffset(); if (IsAssemblyInstallationReferenceActiveForRoute(coreRoute)) { PersistAssemblyInstallationReferenceToRailRoute(coreRoute, $"安装法向偏移已更新为 {value:F3} 米"); } } RefreshAssemblyTerminalObjectInfo(); if (coreRoute != null && coreRoute.PathType == PathType.Rail && AssemblyReferencePathManager.Instance.HasReferenceLine) { RefreshSelectedRailReferenceLineVisuals(); } else { RefreshAssemblyReferenceRodIfNeeded(); } } } } public double AssemblySphereCenterX { get => _assemblySphereCenterX; set { if (Math.Abs(_assemblySphereCenterX - value) < 1e-9) { return; } _assemblySphereCenterX = value; OnPropertyChanged(nameof(AssemblySphereCenterX)); if (HasAssemblyTerminalObject) { RefreshAssemblyTerminalObjectInfo(); RefreshAssemblyReferenceRodIfNeeded(); } } } public double AssemblySphereCenterY { get => _assemblySphereCenterY; set { if (Math.Abs(_assemblySphereCenterY - value) < 1e-9) { return; } _assemblySphereCenterY = value; OnPropertyChanged(nameof(AssemblySphereCenterY)); if (HasAssemblyTerminalObject) { RefreshAssemblyTerminalObjectInfo(); RefreshAssemblyReferenceRodIfNeeded(); } } } public double AssemblySphereCenterZ { get => _assemblySphereCenterZ; set { if (Math.Abs(_assemblySphereCenterZ - value) < 1e-9) { return; } _assemblySphereCenterZ = value; OnPropertyChanged(nameof(AssemblySphereCenterZ)); if (HasAssemblyTerminalObject) { RefreshAssemblyTerminalObjectInfo(); RefreshAssemblyReferenceRodIfNeeded(); } } } public bool HasAssemblyTerminalObject { get => _hasAssemblyTerminalObject; set { if (_hasAssemblyTerminalObject == value) { return; } _hasAssemblyTerminalObject = value; OnPropertyChanged(nameof(HasAssemblyTerminalObject)); } } public string AssemblyStartPointText { get => _assemblyStartPointText; set { if (string.Equals(_assemblyStartPointText, value, StringComparison.Ordinal)) { return; } _assemblyStartPointText = value; OnPropertyChanged(nameof(AssemblyStartPointText)); } } public RailMountMode AssemblyMountMode { get => _assemblyMountMode; set { if (_assemblyMountMode == value) { return; } _assemblyMountMode = value; OnPropertyChanged(nameof(AssemblyMountMode)); if (HasAssemblyTerminalObject) { RefreshAssemblyTerminalObjectInfo(); RefreshAssemblyReferenceRodIfNeeded(); } } } public bool IsSelectingAssemblyStartPoint { get => _isSelectingAssemblyStartPoint; set { if (_isSelectingAssemblyStartPoint == value) { return; } _isSelectingAssemblyStartPoint = value; OnPropertyChanged(nameof(IsSelectingAssemblyStartPoint)); } } /// /// 更新路径可视化显示:清理其他路径,只显示当前选中的路径 /// private async void UpdatePathVisualization() { await SafeExecuteAsync(() => { try { if (PathPointRenderPlugin.Instance == null) { LogManager.Warning("PathPointRenderPlugin实例为空,无法更新路径可视化"); return; } // 0. 先同步物体参数到渲染插件 SyncObjectParametersToRenderPlugin(); // 检查是否在编辑状态 bool isInEditMode = _pathPlanningManager?.IsInEditableState ?? false; // 1. 清理现有的路径可视化,但保留网格可视化和正在编辑的路径 if (isInEditMode) { // 在编辑状态,不清空路径,避免清空正在编辑的路径 LogManager.Debug("[路径可视化] 在编辑状态,跳过清空路径可视化"); } else { // 不在编辑状态,清空所有路径可视化,但保留网格可视化 PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); } // 2. 如果有选中的路径,则显示该路径 if (_selectedPathRoute != null && _selectedPathRoute.Points.Count > 0) { // 查找对应的Core路径对象 var coreRoute = _pathPlanningManager?.Routes?.FirstOrDefault(r => r.Id == _selectedPathRoute.Id) ?? _pathPlanningManager?.Routes?.FirstOrDefault(r => r.Name == _selectedPathRoute.Name); if (coreRoute != null) { // 使用PathPlanningManager的绘制方法来保持一致性 _pathPlanningManager.DrawRouteVisualization(coreRoute, isAutoPath: false); LogManager.Info($"[路径可视化] 已显示选中路径: {_selectedPathRoute.Name},包含 {_selectedPathRoute.Points.Count} 个点"); } else { LogManager.Warning($"[路径可视化] 未找到对应的Core路径: {_selectedPathRoute.Name}"); } } RefreshSelectedRailInstallationReferenceVisuals(); // 3. 恢复网格可视化(如果网格可视化已启用且当前路径有关联的GridMap) if (_pathPlanningManager?.IsAnyGridVisualizationEnabled == true) { // 检查当前路径是否有关联的GridMap var currentRoute = _pathPlanningManager.CurrentRoute; if (currentRoute?.AssociatedGridMap != null) { // 通过PathPlanningManager的RefreshGridVisualization方法恢复网格 // 这会使用当前路径的AssociatedGridMap来重新渲染网格 var refreshMethod = typeof(PathPlanningManager).GetMethod("RefreshGridVisualization", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (refreshMethod != null) { refreshMethod.Invoke(_pathPlanningManager, null); } else { LogManager.Warning("[路径可视化] 无法找到RefreshGridVisualization方法"); } } else { LogManager.Debug("[路径可视化] 当前路径没有关联的GridMap,跳过网格恢复"); } } } catch (Exception ex) { LogManager.Error($"[路径可视化] 更新路径可视化失败: {ex.Message}", ex); } }, "更新路径可视化"); } public bool IsPathEditMode { get => _isPathEditMode; set => SetProperty(ref _isPathEditMode, value); } #region 自动路径规划属性 public string AutoPathStartPoint { get => _autoPathStartPoint; set => SetProperty(ref _autoPathStartPoint, value); } public string AutoPathEndPoint { get => _autoPathEndPoint; set => SetProperty(ref _autoPathEndPoint, value); } #region 多层吊装属性 public bool IsMultiLevelHoistingMode { get => _isMultiLevelHoistingMode; set => SetProperty(ref _isMultiLevelHoistingMode, value); } public ObservableCollection MultiLevelItems { get => _multiLevelItems; set => SetProperty(ref _multiLevelItems, value); } public string MultiLevelStartPointText { get => _multiLevelStartPointText; set => SetProperty(ref _multiLevelStartPointText, value); } public string MultiLevelEndPointText { get => _multiLevelEndPointText; set => SetProperty(ref _multiLevelEndPointText, value); } public string MultiLevelStatsText { get => _multiLevelStatsText; set => SetProperty(ref _multiLevelStatsText, value); } public bool CanCreateMultiLevelPath => _hasMultiLevelStartPoint && _hasMultiLevelEndPoint && _multiLevelItems.Count > 0; #endregion public double ObjectLength { get => _objectLength; set { if (SetProperty(ref _objectLength, value)) { SyncObjectParametersToRenderPlugin(); OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); } } } public double ObjectWidth { get => _objectWidth; set { if (SetProperty(ref _objectWidth, value)) { SyncObjectParametersToRenderPlugin(); OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); } } } public double ObjectHeight { get => _objectHeight; set { if (SetProperty(ref _objectHeight, value)) { SyncObjectParametersToRenderPlugin(); OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); } } } public double SafetyMargin { get => _safetyMargin; set { // 限制精度到2位小数 var roundedValue = Math.Round(value, 2); if (SetProperty(ref _safetyMargin, roundedValue)) { SyncObjectParametersToRenderPlugin(); OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); } } } public bool IsGridSizeManuallyEnabled { get => _isGridSizeManuallyEnabled; set { if (SetProperty(ref _isGridSizeManuallyEnabled, value)) { OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); } } } public double GridSize { get => _gridSize; set { // 限制精度到1位小数 var roundedValue = Math.Round(value, 1); if (SetProperty(ref _gridSize, roundedValue)) { OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); } } } #region 可视化参数属性 /// /// 是否显示可通行网格点 /// public bool ShowWalkableGrid { get => _showWalkableGrid; set { if (SetProperty(ref _showWalkableGrid, value)) { OnGridVisualizationChanged(); } } } /// /// 是否显示障碍物网格点 /// public bool ShowObstacleGrid { get => _showObstacleGrid; set { if (SetProperty(ref _showObstacleGrid, value)) { OnGridVisualizationChanged(); } } } /// /// 是否显示未知区域网格点 /// public bool ShowUnknownGrid { get => _showUnknownGrid; set { if (SetProperty(ref _showUnknownGrid, value)) { OnGridVisualizationChanged(); } } } /// /// 是否显示门网格点 /// public bool ShowDoorGrid { get => _showDoorGrid; set { if (SetProperty(ref _showDoorGrid, value)) { OnGridVisualizationChanged(); } } } /// /// 网格点类型 /// public GridPointType GridPointType { get => _gridPointType; set { if (SetProperty(ref _gridPointType, value)) { OnPropertyChanged(nameof(IsRectanglePointType)); OnPropertyChanged(nameof(IsCirclePointType)); OnGridPointTypeChanged(); } } } /// /// 是否使用立方体点类型 /// public bool IsRectanglePointType { get => _gridPointType == GridPointType.Rectangle; set { if (value) { GridPointType = GridPointType.Rectangle; } } } /// /// 是否使用圆形点类型 /// public bool IsCirclePointType { get => _gridPointType == GridPointType.Circle; set { if (value) { GridPointType = GridPointType.Circle; } } } /// /// 是否显示控制点连线(独立开关) /// public bool ShowControlVisualization { get => _showControlVisualization; set { if (SetProperty(ref _showControlVisualization, value)) { OnControlVisualizationChanged(); } } } /// /// 是否显示路径线/地面连线(独立开关) /// public bool ShowPathLines { get => _showPathLines; set { // 吊装和空轨路径不能使用路径线 if (value && !CanUsePathLines) { LogManager.Warning("[路径可视化] 吊装和空轨路径不能使用路径线"); return; } if (SetProperty(ref _showPathLines, value)) { OnPathVisualizationModeChanged(); } } } /// /// 是否显示通行空间(独立开关) /// public bool ShowObjectSpace { get => _showObjectSpace; set { if (SetProperty(ref _showObjectSpace, value)) { OnPathVisualizationModeChanged(); } } } /// /// 当前路径是否可以使用路径线 /// 地面路径可以使用路径线,吊装和空轨路径不能使用路径线 /// public bool CanUsePathLines { get { if (SelectedPathRoute == null) return true; // 只有地面路径可以使用路径线 return SelectedPathRoute.PathType == PathType.Ground; } } /// /// 是否使用标准连线模式(向后兼容,根据独立开关计算) /// public bool IsStandardLineMode { get => _showPathLines && !_showObjectSpace; set { if (value) { ShowPathLines = true; ShowObjectSpace = false; } } } /// /// 是否使用物体通行空间模式(向后兼容,等同于 ShowObjectSpace) /// public bool IsObjectSpaceMode { get => _showObjectSpace; set => ShowObjectSpace = value; } /// /// 是否使用带状连线模式(向后兼容,等同于 ShowPathLines) /// public bool IsRibbonLineMode { get => _showPathLines; set => ShowPathLines = value; } #endregion /// /// 路径策略选项集合 /// public ObservableCollection PathStrategyOptions { get => _pathStrategyOptions; } /// /// 当前选择的路径策略 /// public PathStrategyOption SelectedPathStrategy { get => _selectedPathStrategy; set { if (SetProperty(ref _selectedPathStrategy, value)) { OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); } } } public bool IsSelectingStartPoint { get => _isSelectingStartPoint; set => SetProperty(ref _isSelectingStartPoint, value); } public bool IsSelectingEndPoint { get => _isSelectingEndPoint; set => SetProperty(ref _isSelectingEndPoint, value); } #endregion public PathPlanningManager PathPlanningManager { get => _pathPlanningManager; set { if (_pathPlanningManager != value) { // 取消旧的事件订阅 if (_pathPlanningManager != null) { UnsubscribeFromPathPlanningManager(); } _pathPlanningManager = value; // 订阅新的事件 if (_pathPlanningManager != null) { SubscribeToPathPlanningManager(); } } } } #endregion #region 命令 public ICommand NewPathCommand { get; private set; } public ICommand NewRailPathCommand { get; private set; } public ICommand NewHoistingPathCommand { get; private set; } public ICommand NewMultiLevelHoistingPathCommand { get; private set; } public ICommand DuplicatePathCommand { get; private set; } public ICommand DeletePathCommand { get; private set; } public ICommand RenamePathCommand { get; private set; } public ICommand StartEditCommand { get; private set; } public ICommand EndEditCommand { get; private set; } public ICommand ClearPathCommand { get; private set; } public ICommand DeletePointCommand { get; private set; } public ICommand ModifyPointCommand { get; private set; } public ICommand CancelModifyPointCommand { get; private set; } public ICommand SelectStartPointCommand { get; private set; } public ICommand SelectEndPointCommand { get; private set; } public ICommand EditPointCoordinatesCommand { get; private set; } public ICommand AutoPlanPathCommand { get; private set; } public ICommand ClearAutoPathCommand { get; private set; } public ICommand ImportPathCommand { get; private set; } public ICommand ExportPathCommand { get; private set; } public ICommand SaveAsPathCommand { get; private set; } public ICommand CaptureAssemblyTerminalObjectForCreateCommand { get; private set; } public ICommand ReCaptureAssemblyTerminalObjectForEditCommand { get; private set; } public ICommand SelectAssemblyStartPointForCreateCommand { get; private set; } public ICommand RepositionRailStartPointCommand { get; private set; } public ICommand SelectAssemblyInstallationPointForCreateCommand { get; private set; } public ICommand SelectAssemblyInstallationPointForEditCommand { get; private set; } public ICommand ClearAssemblyReferenceRodCommand { get; private set; } public ICommand CancelRailAssemblyWorkflowCommand { get; private set; } public ICommand AnalyzeAssemblyTerminalFaceForCreateCommand { get; private set; } public ICommand AnalyzeAssemblyTerminalFaceForEditCommand { get; private set; } public ICommand DecreaseSelectedRailNormalOffsetCommand { get; private set; } public ICommand IncreaseSelectedRailNormalOffsetCommand { get; private set; } // 多层吊装命令 public ICommand SelectMultiLevelStartPointCommand { get; private set; } public ICommand SelectMultiLevelEndPointCommand { get; private set; } public ICommand AddMultiLevelCommand { get; private set; } public ICommand DeleteMultiLevelCommand { get; private set; } public ICommand PickMultiLevelTargetCommand { get; private set; } public ICommand CreateMultiLevelPathCommand { get; private set; } public ICommand CancelMultiLevelHoistingCommand { get; private set; } #endregion #region Can Execute属性 public bool CanExecuteAutoPlanPath => _hasStartPoint && _hasEndPoint && ObjectLength > 0 && ObjectWidth > 0 && ObjectHeight > 0 && SafetyMargin >= 0; public bool CanExecuteNewPath => !IsSelectingStartPoint && !IsSelectingEndPoint; public bool CanExecuteNewRailPath => !IsSelectingStartPoint && !IsSelectingEndPoint; public bool CanExecuteStartEdit => SelectedPathRoute != null && (_pathPlanningManager?.PathEditState == PathEditState.Viewing); public bool CanExecuteEndEdit => (_pathPlanningManager?.PathEditState == PathEditState.Creating || _pathPlanningManager?.PathEditState == PathEditState.Editing || _pathPlanningManager?.PathEditState == PathEditState.AddingPoints || _pathPlanningManager?.PathEditState == PathEditState.EditingPoint) || CanCancelRailAssemblyWorkflow; public bool CanExecuteClearPath => SelectedPathRoute != null && SelectedPathRoute.Points.Count > 0; public bool CanExecuteExportPath => _pathPlanningManager?.Routes?.Count > 0 || SelectedPathRoute != null; public bool CanExecuteSaveAsPath => SelectedPathRoute != null && SelectedPathRoute.Points.Count > 0; public bool CanSelectAssemblyStartPointForCreate => HasAssemblyTerminalObject && _pathPlanningManager != null && AssemblyReferencePathManager.Instance.HasReferenceLine && !IsSelectingAssemblyStartPoint && !_isSelectingAssemblyEndFacePoints && !_isSelectingAssemblyInstallationPoint; public bool CanRepositionRailStartPoint => HasAssemblyTerminalObject && IsRailRouteSelected && SelectedPathRoute != null && _pathPlanningManager != null && AssemblyReferencePathManager.Instance.HasReferenceLine && !IsSelectingAssemblyStartPoint && !_isSelectingAssemblyEndFacePoints && !_isSelectingAssemblyInstallationPoint; public bool CanSelectAssemblyInstallationPointForCreate => HasAssemblyTerminalObject && _pathPlanningManager != null && !_isSelectingAssemblyStartPoint && !_isSelectingAssemblyEndFacePoints && !_isSelectingAssemblyInstallationPoint; public bool CanSelectAssemblyInstallationPointForEdit => HasAssemblyTerminalObject && IsRailRouteSelected && SelectedPathRoute != null && _pathPlanningManager != null && !_isSelectingAssemblyStartPoint && !_isSelectingAssemblyEndFacePoints && !_isSelectingAssemblyInstallationPoint; public bool CanAnalyzeAssemblyTerminalFaceForCreate => HasAssemblyTerminalObject && _pathPlanningManager != null && !IsSelectingAssemblyStartPoint && !IsSelectingAssemblyEndFacePoints && !_isSelectingAssemblyInstallationPoint; public bool CanAnalyzeAssemblyTerminalFaceForEdit => HasAssemblyTerminalObject && IsRailRouteSelected && SelectedPathRoute != null && _pathPlanningManager != null && !IsSelectingAssemblyStartPoint && !IsSelectingAssemblyEndFacePoints && !_isSelectingAssemblyInstallationPoint; public string CreateAssemblyInstallationPointButtonText => _hasAssemblyInstallationReference && !IsRailEditingWorkflowActive ? "重选安装点" : "选安装点"; public string EditAssemblyInstallationPointButtonText => HasSelectedRailInstallationReference() ? "重选安装点" : "选安装点"; public bool IsSelectingAssemblyEndFacePoints => _isSelectingAssemblyEndFacePoints; public bool CanCancelRailAssemblyWorkflow => _railAssemblyWorkflowMode != RailAssemblyWorkflowMode.None || HasAssemblyTerminalObject || _isSelectingAssemblyStartPoint || _isSelectingAssemblyEndFacePoints || _isSelectingAssemblyInstallationPoint || _hasAssemblyEndFaceAnalysis || _hasAssemblyInstallationReference; private bool IsRailCreateWorkflowActive => _railAssemblyWorkflowMode == RailAssemblyWorkflowMode.CreateNewRailPath; private bool IsRailEditingWorkflowActive => _railAssemblyWorkflowMode == RailAssemblyWorkflowMode.EditSelectedRail; private static bool IsRailAssemblyEditMode(RailAssemblyWorkflowMode workflowMode) { return workflowMode == RailAssemblyWorkflowMode.EditSelectedRail; } private static string GetRailAssemblyWorkflowLabel(RailAssemblyWorkflowMode workflowMode) { return IsRailAssemblyEditMode(workflowMode) ? "编辑现有Rail路径" : "新建Rail路径"; } private bool ShouldShowAssemblyReferenceLineVisuals() { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { return false; } return IsRailEditingWorkflowActive || _hasAssemblyInstallationReference; } public bool CanExecuteModifyPoint => SelectedPathPoint != null && _pathPlanningManager?.PathEditState != PathEditState.EditingPoint; public bool CanExecuteCancelModifyPoint => _pathPlanningManager?.PathEditState == PathEditState.EditingPoint; #endregion #region 构造函数 /// /// 构造函数,需要传入主ViewModel以支持统一状态栏 /// /// 主ViewModel,用于统一状态栏 public PathEditingViewModel(LogisticsControlViewModel mainViewModel) : base() { try { // 设置主ViewModel引用到基类 SetMainViewModel(mainViewModel); _uiStateManager = UIStateManager.Instance; _pathDataManager = new PathDataManager(); // 不在构造函数中创建PathPlanningManager,由外部设置 _pathPlanningManager = null; if (_uiStateManager == null) { throw new InvalidOperationException("UIStateManager初始化失败"); } // PathPlanningManager将在外部设置,这里不需要检查 // 初始化集合 PathRoutes = new ObservableCollection(); // 初始化路径策略选项 InitializePathStrategyOptions(); InitializeRailConfigOptions(); // 从配置加载参数 LoadParametersFromConfig(); // 订阅配置变更事件 SubscribeToConfigChanges(); // 初始化命令 InitializeCommands(); // 初始化物体参数同步(在PathPointRenderPlugin实例不为空时才同步) // 如果PathPointRenderPlugin尚未初始化,将在属性变更时自动同步 SyncObjectParametersToRenderPlugin(); // 注意:不在这里订阅PathPlanningManager事件, // 因为PathPlanningManager还没有设置,事件订阅在PathPlanningManager属性setter中处理 LogManager.Info("PathEditingViewModel构造函数执行完成 - 支持统一状态栏"); } catch (Exception ex) { LogManager.Error($"PathEditingViewModel构造函数异常: {ex.Message}", ex); UpdateMainStatus("初始化失败,请检查日志"); throw; } } #endregion #region 初始化方法 /// /// 初始化路径策略选项 /// private void InitializePathStrategyOptions() { _pathStrategyOptions = new ObservableCollection { new PathStrategyOption { Value = PathStrategy.Shortest, DisplayName = "最短路径", Description = "标准A*算法,选择路径总长度最短的路径" }, new PathStrategyOption { Value = PathStrategy.Straightest, DisplayName = "直线优先", Description = "优先选择主方向直线路径,减少转弯次数" }, new PathStrategyOption { Value = PathStrategy.SafestCenter, DisplayName = "安全优先", Description = "基于障碍物距离选择安全路径,适合大型物体居中行驶" } }; // 默认选择最短路径策略(向后兼容) _selectedPathStrategy = _pathStrategyOptions.FirstOrDefault(x => x.Value == PathStrategy.Shortest); } /// /// 初始化 Rail 构型选项 /// private void InitializeRailConfigOptions() { _railMountModeOptions = new ObservableCollection> { new RailConfigOption { Value = RailMountMode.UnderRail, DisplayName = "轨下安装", Description = "安装头位于双轨下方,构件从轨道平面下侧连接" }, new RailConfigOption { Value = RailMountMode.OverRail, DisplayName = "轨上安装", Description = "安装头位于双轨上方,构件从轨道平面上侧连接" } }; } private PathRoute GetSelectedCoreRoute() { if (_pathPlanningManager == null || SelectedPathRoute == null) { return null; } return _pathPlanningManager.Routes?.FirstOrDefault(route => route.Id == SelectedPathRoute.Id) ?? _pathPlanningManager.Routes?.FirstOrDefault(route => route.Name == SelectedPathRoute.Name); } private void ApplyRailRouteConfigurationChange(PathRoute coreRoute, string logMessage) { coreRoute.LastModified = DateTime.Now; SyncObjectParametersToRenderPlugin(); _pathPlanningManager.DrawRouteVisualization(coreRoute, isAutoPath: false); _pathPlanningManager.SavePathToDatabase(coreRoute); _pathPlanningManager.RaiseVisualizationStateChanged(); LogManager.Info($"[Rail构型] {coreRoute.Name}: {logMessage}"); } private void SetRailAssemblyWorkflowMode(RailAssemblyWorkflowMode mode) { if (_railAssemblyWorkflowMode == mode) { return; } _railAssemblyWorkflowMode = mode; NotifyRailAssemblyCommandStateChanged(); } private void NotifyRailAssemblyCommandStateChanged() { OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFaceForCreate)); OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFaceForEdit)); OnPropertyChanged(nameof(CanSelectAssemblyStartPointForCreate)); OnPropertyChanged(nameof(CanRepositionRailStartPoint)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPointForCreate)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPointForEdit)); OnPropertyChanged(nameof(CreateAssemblyInstallationPointButtonText)); OnPropertyChanged(nameof(EditAssemblyInstallationPointButtonText)); OnPropertyChanged(nameof(CanCancelRailAssemblyWorkflow)); OnPropertyChanged(nameof(CanExecuteEndEdit)); } private bool HasSelectedRailInstallationReference() { var coreRoute = GetSelectedCoreRoute(); return coreRoute != null && coreRoute.PathType == PathType.Rail && coreRoute.RailPreferredNormal != null; } private int GetRoutePointIndexByType(PathRoute route, PathPointType pointType, int fallbackIndex) { if (route?.Points == null || route.Points.Count == 0) { return -1; } for (int i = 0; i < route.Points.Count; i++) { if (route.Points[i].Type == pointType) { return i; } } return fallbackIndex >= 0 && fallbackIndex < route.Points.Count ? fallbackIndex : -1; } private bool IsAssemblyInstallationReferenceActiveForRoute(PathRoute route) { return route != null && route.PathType == PathType.Rail && _hasAssemblyInstallationReference && _assemblyInstallationAnchorPoint != null && !string.IsNullOrWhiteSpace(_assemblyInstallationReferenceRouteId) && string.Equals(_assemblyInstallationReferenceRouteId, route.Id, StringComparison.Ordinal); } private int GetRailRouteInstallationPointIndex(PathRoute route) { return GetRoutePointIndexByType(route, PathPointType.EndPoint, route?.Points?.Count - 1 ?? -1); } private int GetRailRouteStartPointIndex(PathRoute route) { return GetRoutePointIndexByType(route, PathPointType.StartPoint, 0); } private bool TryBuildRailRouteReferenceLine(PathRoute route, out AssemblyReferenceLine referenceLine) { referenceLine = null; if (route == null || route.PathType != PathType.Rail || route.Points == null || route.Points.Count < 2) { return false; } int startIndex = GetRailRouteStartPointIndex(route); int endIndex = GetRailRouteInstallationPointIndex(route); if (startIndex < 0 || endIndex < 0 || startIndex == endIndex) { return false; } Point3D startPoint = route.Points[startIndex].Position; Point3D endPoint = route.Points[endIndex].Position; Vector3D direction = new Vector3D( startPoint.X - endPoint.X, startPoint.Y - endPoint.Y, startPoint.Z - endPoint.Z); double lengthSquared = direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z; if (lengthSquared < 1e-9) { return false; } referenceLine = new AssemblyReferenceLine(startPoint, endPoint, direction.Normalize()); return true; } private void RefreshSelectedRailReferenceLineVisuals(string statusMessage = null) { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { ClearAssemblyReferenceLineVisuals(); return; } AssemblyReferenceLine referenceLine; if (IsRailEditingWorkflowActive) { // 编辑状态:使用默认杆长度构建参考线,方便选择更远的起点 try { referenceLine = BuildAssemblyReferenceLine(); } catch { ClearAssemblyReferenceLineVisuals(); return; } } else { // 非编辑状态:使用路径实际长度 var coreRoute = GetSelectedCoreRoute(); if (!TryBuildRailRouteReferenceLine(coreRoute, out referenceLine)) { ClearAssemblyReferenceLineVisuals(); return; } } AssemblyReferencePathManager.Instance.CreateOrUpdateReferenceRod( referenceLine.StartPoint, referenceLine.EndPoint, AssemblyReferenceRodDiameterInMeters); AssemblyStartPointText = $"({referenceLine.StartPoint.X:F2}, {referenceLine.StartPoint.Y:F2}, {referenceLine.StartPoint.Z:F2})"; RefreshAssemblyTerminalObjectInfo(referenceLine.StartPoint, referenceLine.EndPoint); RenderAssemblyReferenceLine(referenceLine); NotifyRailAssemblyCommandStateChanged(); if (!string.IsNullOrWhiteSpace(statusMessage)) { UpdateMainStatus(statusMessage); } } private void PersistAssemblyInstallationReferenceToRailRoute(PathRoute route, string logMessage) { if (route == null) { throw new ArgumentNullException(nameof(route)); } if (route.PathType != PathType.Rail) { throw new InvalidOperationException("只有 Rail 路径支持安装点参数更新。"); } if (!_hasAssemblyInstallationReference || _assemblyInstallationAnchorPoint == null) { throw new InvalidOperationException("当前没有可应用的安装点参考。"); } int installationPointIndex = GetRailRouteInstallationPointIndex(route); if (installationPointIndex < 0) { throw new InvalidOperationException("当前 Rail 路径没有可更新的终点。"); } // 获取当前起点索引 int startPointIndex = GetRailRouteStartPointIndex(route); if (startPointIndex < 0) { throw new InvalidOperationException("当前 Rail 路径没有可更新的起点。"); } // 计算原路径长度(起点到终点的距离) Point3D currentStartPoint = route.Points[startPointIndex].Position; Point3D currentEndPoint = route.Points[installationPointIndex].Position; double pathLength = GeometryHelper.CalculatePointDistance(currentStartPoint, currentEndPoint); // 更新终点(安装点) if (!_pathPlanningManager.UpdatePathPointWithConstraints(route, installationPointIndex, _assemblyInstallationAnchorPoint)) { throw new InvalidOperationException("更新 Rail 路径安装点失败。"); } // 根据新终点和原路径长度重新计算起点位置 Point3D newStartPoint = CalculateRailStartPointFromEndPoint(_assemblyInstallationAnchorPoint, pathLength); if (!_pathPlanningManager.UpdatePathPointWithConstraints(route, startPointIndex, newStartPoint)) { throw new InvalidOperationException("更新 Rail 路径起点失败。"); } route.RailPreferredNormal = CreatePersistedAssemblyPreferredNormal(); route.RailNormalOffset = UnitsConverter.ConvertFromMeters(AssemblyAnchorVerticalOffsetInMeters); route.LastModified = DateTime.Now; _assemblyInstallationReferenceRouteId = route.Id; _pathPlanningManager.SavePathToDatabase(route); _pathPlanningManager.DrawRouteVisualization(route, isAutoPath: false); _pathPlanningManager.RaiseVisualizationStateChanged(); if (SelectedPathRoute != null && SelectedPathRoute.Id == route.Id) { SyncPathViewModelFromCoreRoute(SelectedPathRoute, route, preserveSelection: true); OnPropertyChanged(nameof(SelectedRailNormalOffsetInMeters)); NotifyRailAssemblyCommandStateChanged(); RefreshAssemblyTerminalObjectInfo(); RefreshSelectedRailInstallationReferenceVisuals(); RefreshSelectedRailReferenceLineVisuals(); } LogManager.Info($"[Rail构型] {route.Name}: {logMessage},起点已同步更新为 ({newStartPoint.X:F3}, {newStartPoint.Y:F3}, {newStartPoint.Z:F3})"); } private void RefreshSelectedRailInstallationReferenceVisuals() { var coreRoute = GetSelectedCoreRoute(); if (!IsAssemblyInstallationReferenceActiveForRoute(coreRoute)) { ClearAssemblyInstallationReferenceVisuals(); return; } RenderAssemblyInstallationPickPoints(_assemblyInstallationSeedPoints); RenderAssemblyInstallationAnchorPoint(_assemblyInstallationAnchorPoint); RenderAssemblyInstallationCenterLine(_assemblyInstallationPickPoint, GetCurrentAssemblyOpticalAxisDirection()); RenderAssemblyInstallationPlane(_assemblyInstallationAnchorPoint, GetCurrentAssemblyOpticalAxisDirection(), _assemblyInstallationPlaneSpanDirection); } private void AdjustSelectedRailNormalOffset(double deltaInMeters) { var coreRoute = GetSelectedCoreRoute(); if (coreRoute == null || coreRoute.PathType != PathType.Rail) { return; } double currentOffsetInMeters = UnitsConverter.ConvertToMeters(coreRoute.RailNormalOffset); double nextOffsetInMeters = Math.Round(currentOffsetInMeters + deltaInMeters, 3); SelectedRailNormalOffsetInMeters = nextOffsetInMeters; } /// /// 从配置文件加载参数 /// private void LoadParametersFromConfig() { try { var config = ConfigManager.Instance.Current; // 从 PathEditing 配置加载所有参数(使用米单位接口) _gridSize = config.PathEditing.CellSizeMeters; _objectLength = config.PathEditing.ObjectLengthMeters; _objectWidth = config.PathEditing.ObjectWidthMeters; _objectHeight = config.PathEditing.ObjectHeightMeters; _safetyMargin = config.PathEditing.SafetyMarginMeters; LogManager.Info($"从配置加载参数 - 物体: {_objectLength:F1}x{_objectWidth:F1}x{_objectHeight:F1}米, 安全间隙: {_safetyMargin:F2}米, 网格: {_gridSize:F1}米"); } catch (Exception ex) { LogManager.Error($"加载配置参数失败: {ex.Message}", ex); // 使用默认值,不抛出异常 } } #region 配置变更处理 /// /// 订阅配置变更事件 /// private void SubscribeToConfigChanges() { try { // 订阅特定类别的配置变更事件 ConfigManager.Instance.CategoryConfigurationChanged += OnCategoryConfigurationChanged; LogManager.Info("PathEditingViewModel 已订阅配置变更事件"); } catch (Exception ex) { LogManager.Error($"订阅配置变更事件失败: {ex.Message}", ex); } } /// /// 取消订阅配置变更事件 /// private void UnsubscribeFromConfigChanges() { try { ConfigManager.Instance.CategoryConfigurationChanged -= OnCategoryConfigurationChanged; LogManager.Info("PathEditingViewModel 已取消订阅配置变更事件"); } catch (Exception ex) { LogManager.Warning($"取消订阅配置变更事件时发生异常: {ex.Message}"); } } /// /// 配置变更事件处理 /// private void OnCategoryConfigurationChanged(object sender, Tuple args) { try { var category = args.Item1; var eventArgs = args.Item2; // 只处理 PathEditing 相关的配置变更 if (category == ConfigCategory.PathEditing || category == ConfigCategory.All) { LogManager.Info($"收到 PathEditing 配置变更通知,正在更新参数..."); // 从配置重新加载参数 LoadParametersFromConfig(); // 触发属性变更通知,更新UI OnPropertiesChanged( nameof(ObjectLength), nameof(ObjectWidth), nameof(ObjectHeight), nameof(SafetyMargin), nameof(GridSize) ); // 同步物体参数到渲染插件 SyncObjectParametersToRenderPlugin(); // 更新状态栏 string source = eventArgs.IsExternalChange ? "外部修改" : "内部更新"; UpdateMainStatus($"配置已更新 ({source}) - 物体参数: {ObjectLength:F1}x{ObjectWidth:F1}x{ObjectHeight:F1}米"); LogManager.Info($"PathEditing 配置参数已更新完成 - 来源: {source}"); } } catch (Exception ex) { LogManager.Error($"处理配置变更事件失败: {ex.Message}", ex); } } #endregion private void InitializeCommands() { NewPathCommand = new RelayCommand(async () => await ExecuteNewPathAsync(), () => CanExecuteNewPath); NewRailPathCommand = new RelayCommand(async () => await ExecuteNewRailPathAsync(), () => CanExecuteNewRailPath); NewHoistingPathCommand = new RelayCommand(async () => await ExecuteNewHoistingPathAsync()); NewMultiLevelHoistingPathCommand = new RelayCommand(async () => await ExecuteNewMultiLevelHoistingPathAsync()); SelectMultiLevelStartPointCommand = new RelayCommand(async () => await ExecuteSelectMultiLevelStartPointAsync()); SelectMultiLevelEndPointCommand = new RelayCommand(async () => await ExecuteSelectMultiLevelEndPointAsync()); AddMultiLevelCommand = new RelayCommand(() => ExecuteAddMultiLevel()); DeleteMultiLevelCommand = new RelayCommand((item) => ExecuteDeleteMultiLevel(item)); PickMultiLevelTargetCommand = new RelayCommand((item) => ExecutePickMultiLevelTarget(item)); CreateMultiLevelPathCommand = new RelayCommand(async () => await ExecuteCreateMultiLevelPathAsync(), () => CanCreateMultiLevelPath); CancelMultiLevelHoistingCommand = new RelayCommand(() => ExecuteCancelMultiLevelHoisting()); DuplicatePathCommand = new RelayCommand(async () => await ExecuteDuplicatePathAsync()); DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync()); RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync()); StartEditCommand = new RelayCommand(async () => await ExecuteAddPathPointAsync(), () => CanExecuteStartEdit); EndEditCommand = new RelayCommand(async () => await ExecuteEndEditAsync(), () => CanExecuteEndEdit); ClearPathCommand = new RelayCommand(async () => await ExecuteClearPathAsync(), () => CanExecuteClearPath); DeletePointCommand = new RelayCommand(async (point) => await ExecuteDeletePointAsync(point)); ModifyPointCommand = new RelayCommand(async () => await ExecuteModifyPointAsync(), () => CanExecuteModifyPoint); CancelModifyPointCommand = new RelayCommand(async () => await ExecuteCancelModifyPointAsync(), () => CanExecuteCancelModifyPoint); SelectStartPointCommand = new RelayCommand(async () => await ExecuteSelectStartPointAsync()); SelectEndPointCommand = new RelayCommand(async () => await ExecuteSelectEndPointAsync()); EditPointCoordinatesCommand = new RelayCommand(ExecuteEditPointCoordinates); AutoPlanPathCommand = new RelayCommand(async () => await ExecuteAutoPlanPathAsync(), () => CanExecuteAutoPlanPath); ClearAutoPathCommand = new RelayCommand(async () => await ExecuteClearAutoPathAsync()); ImportPathCommand = new RelayCommand(async () => await ExecuteImportPathAsync()); ExportPathCommand = new RelayCommand(async () => await ExecuteExportPathAsync(), () => CanExecuteExportPath); SaveAsPathCommand = new RelayCommand(async () => await ExecuteSaveAsPathAsync(), () => CanExecuteSaveAsPath); CaptureAssemblyTerminalObjectForCreateCommand = new RelayCommand(async () => await ExecuteCaptureAssemblyTerminalObjectAsync(RailAssemblyWorkflowMode.CreateNewRailPath)); ReCaptureAssemblyTerminalObjectForEditCommand = new RelayCommand(async () => await ExecuteCaptureAssemblyTerminalObjectAsync(RailAssemblyWorkflowMode.EditSelectedRail)); SelectAssemblyStartPointForCreateCommand = new RelayCommand(async () => await ExecuteSelectAssemblyStartPointAsync(), () => CanSelectAssemblyStartPointForCreate); RepositionRailStartPointCommand = new RelayCommand(async () => await ExecuteRepositionRailStartPointAsync(), () => CanRepositionRailStartPoint); SelectAssemblyInstallationPointForCreateCommand = new RelayCommand(async () => await ExecuteSelectAssemblyInstallationPointAsync(RailAssemblyWorkflowMode.CreateNewRailPath), () => CanSelectAssemblyInstallationPointForCreate); SelectAssemblyInstallationPointForEditCommand = new RelayCommand(async () => await ExecuteSelectAssemblyInstallationPointAsync(RailAssemblyWorkflowMode.EditSelectedRail), () => CanSelectAssemblyInstallationPointForEdit); ClearAssemblyReferenceRodCommand = new RelayCommand(() => ExecuteClearAssemblyReferenceRod()); CancelRailAssemblyWorkflowCommand = new RelayCommand(() => ExecuteCancelRailAssemblyWorkflow(), () => CanCancelRailAssemblyWorkflow); AnalyzeAssemblyTerminalFaceForCreateCommand = new RelayCommand(async () => await ExecuteAnalyzeAssemblyTerminalFaceAsync(RailAssemblyWorkflowMode.CreateNewRailPath), () => CanAnalyzeAssemblyTerminalFaceForCreate); AnalyzeAssemblyTerminalFaceForEditCommand = new RelayCommand(async () => await ExecuteAnalyzeAssemblyTerminalFaceAsync(RailAssemblyWorkflowMode.EditSelectedRail), () => CanAnalyzeAssemblyTerminalFaceForEdit); DecreaseSelectedRailNormalOffsetCommand = new RelayCommand(() => AdjustSelectedRailNormalOffset(-RailNormalOffsetNudgeStepInMeters)); IncreaseSelectedRailNormalOffsetCommand = new RelayCommand(() => AdjustSelectedRailNormalOffset(RailNormalOffsetNudgeStepInMeters)); } #endregion #region 命令实现 - 从LogisticsControlViewModelcopy.cs迁移 #region 路径管理命令 private async Task ExecuteCaptureAssemblyTerminalObjectAsync(RailAssemblyWorkflowMode mode) { await SafeExecuteAsync(() => { ClearNonGridPathVisualizations("[直线装配] 捕获箱体"); var document = NavisApplication.ActiveDocument; var selectedItem = document?.CurrentSelection?.SelectedItems?.FirstOrDefault(); if (selectedItem == null) { throw new InvalidOperationException("请先在 Navisworks 中选择终点处已安装的箱体"); } if (!ModelItemAnalysisHelper.IsModelItemValid(selectedItem)) { throw new InvalidOperationException("当前选择对象无效,请重新选择终点箱体"); } SetRailAssemblyWorkflowMode(mode); _assemblyTerminalObject = selectedItem; _assemblyStartPoint = Point3D.Origin; HasAssemblyTerminalObject = true; AssemblyStartPointText = "未选择"; AssemblyTerminalObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(selectedItem); ResetAssemblyEndFaceAnalysisState(); ResetAssemblyInstallationReferenceState(); RefreshAssemblyTerminalObjectInfo(); ClearAssemblyAnchorMarker(); ClearAssemblyEndFaceAnalysisVisuals(); ClearAssemblyInstallationReferenceVisuals(); NotifyRailAssemblyCommandStateChanged(); // 新建路径时隐藏其他路径的辅助线 ClearAssemblyReferenceLineVisuals(); UpdateMainStatus($"已捕获终点箱体: {AssemblyTerminalObjectName}"); LogManager.Info($"[直线装配] 已捕获终点箱体: {AssemblyTerminalObjectName}"); }, "捕获终点箱体"); } private void ClearNonGridPathVisualizations(string context) { if (PathPointRenderPlugin.Instance == null) { return; } try { PathPointRenderPlugin.Instance.ClearPathsExcept( "grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); LogManager.Info($"{context}:已清除现有路径可视化显示(保留网格可视化)"); } catch (Exception ex) { LogManager.Error($"{context}:清除现有路径可视化失败: {ex.Message}", ex); throw; } } private async Task ExecuteSelectAssemblyStartPointAsync() { await SafeExecuteAsync(() => { if (_pathPlanningManager == null) { throw new InvalidOperationException("路径规划管理器未初始化,无法选择装配起点"); } if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { throw new InvalidOperationException("终点箱体未设置或已失效,请重新捕获终点箱体"); } if (!AssemblyReferencePathManager.Instance.HasReferenceLine) { throw new InvalidOperationException("请先选择安装点,系统生成终端安装辅助线后才能取起点"); } BeginAssemblyStartPointSelection( statusMessage: "请在辅助线附近点击一个起点,系统会自动吸附到辅助线上并生成直线路径", logMessage: "[直线装配] 已进入起点拾取模式", toolPluginFailureMessage: "ToolPlugin 初始化失败,请重试"); }, "选择终端安装起点"); } private async Task ExecuteRepositionRailStartPointAsync() { await SafeExecuteAsync(() => { if (_pathPlanningManager == null) { throw new InvalidOperationException("路径规划管理器未初始化,无法重选起点。"); } if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { throw new InvalidOperationException("终点箱体未设置或已失效,请先捕获终点箱体。"); } var selectedRailRoute = GetSelectedCoreRoute(); if (selectedRailRoute == null || selectedRailRoute.PathType != PathType.Rail) { throw new InvalidOperationException("请先选中一条需要编辑起点的 Rail 路径。"); } SetRailAssemblyWorkflowMode(RailAssemblyWorkflowMode.EditSelectedRail); RefreshSelectedRailReferenceLineVisuals(); if (!AssemblyReferencePathManager.Instance.HasReferenceLine) { throw new InvalidOperationException("当前 Rail 路径无法生成辅助线,请先检查路径起点和终点。"); } BeginAssemblyStartPointSelection( statusMessage: "请在辅助线附近点击新的起点位置,系统会自动吸附到辅助线上并更新当前 Rail 路径。", logMessage: "[Rail构型] 已进入起点重选模式", toolPluginFailureMessage: "ToolPlugin 初始化失败,请重试。"); }, "重选 Rail 路径起点"); } private void ExecuteClearAssemblyReferenceRod() { try { ClearAssemblyReferenceLineVisuals(); ClearAssemblyEndFaceAnalysisVisuals(); ClearAssemblyInstallationReferenceVisuals(); UpdateMainStatus("已隐藏终端安装辅助线"); LogManager.Info("[直线装配] 已隐藏辅助线"); } catch (Exception ex) { LogManager.Error($"[直线装配] 隐藏辅助线失败: {ex.Message}"); } } private async Task ExecuteAnalyzeAssemblyTerminalFaceAsync(RailAssemblyWorkflowMode workflowMode) { await SafeExecuteAsync(() => { if (_pathPlanningManager == null) { throw new InvalidOperationException("路径规划管理器未初始化,无法分析端面。"); } if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { throw new InvalidOperationException("终点箱体未设置或已失效,请先捕获终点箱体。"); } PrepareRailAssemblyWorkflowForMode( workflowMode, editActionDescription: "请先选中一条需要编辑安装点的 Rail 路径。", syncMountModeFromRoute: true); BeginAssemblyEndFaceSelection(workflowMode); }, "分析终端端面"); } private async Task ExecuteSelectAssemblyInstallationPointAsync(RailAssemblyWorkflowMode workflowMode) { await SafeExecuteAsync(() => { if (_pathPlanningManager == null) { throw new InvalidOperationException("路径规划管理器未初始化,无法选择安装点。"); } if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { throw new InvalidOperationException("终点箱体未设置或已失效,请先捕获终点箱体。"); } PrepareRailAssemblyWorkflowForMode( workflowMode, editActionDescription: "请先选中一条需要编辑安装点的 Rail 路径。", syncMountModeFromRoute: false); BeginAssemblyInstallationSelection(); }, "选择安装点"); } private async void OnAssemblyReferenceMouseClicked(object sender, PickItemResult pickResult) { try { if (!IsSelectingAssemblyStartPoint || pickResult == null) { return; } Point3D projectedStartPoint = AssemblyReferencePathManager.Instance.ProjectPointToReferenceLine(pickResult.Point); _assemblyStartPoint = projectedStartPoint; AssemblyStartPointText = $"({projectedStartPoint.X:F2}, {projectedStartPoint.Y:F2}, {projectedStartPoint.Z:F2})"; LogManager.Info( $"[直线装配] 点击点=({pickResult.Point.X:F2}, {pickResult.Point.Y:F2}, {pickResult.Point.Z:F2})," + $"投影起点=({projectedStartPoint.X:F2}, {projectedStartPoint.Y:F2}, {projectedStartPoint.Z:F2})"); await SafeExecuteAsync(() => { ApplyAssemblyStartPointForCurrentWorkflow(projectedStartPoint); CleanupAssemblyReferenceSelection(); }, "生成终端安装路径"); } catch (Exception ex) { LogManager.Error($"[直线装配] 处理起点拾取失败: {ex.Message}", ex); UpdateMainStatus($"终端安装起点拾取失败: {ex.Message}"); CleanupAssemblyReferenceSelection(); } } private async void OnAssemblyEndFaceMouseClicked(object sender, PickItemResult pickResult) { try { if (!_isSelectingAssemblyEndFacePoints || pickResult == null) { return; } await SafeExecuteAsync(() => { if (!IsPickOnAssemblyTerminalObject(pickResult)) { UpdateMainStatus("请点击当前终点箱体的同一个端面,不要点到其他对象。"); return; } _assemblyEndFaceSeedPoints.Add(pickResult.Point); RenderAssemblyEndFaceSeedPoints(); int pickedCount = _assemblyEndFaceSeedPoints.Count; LogManager.Info($"[直线装配] 已记录端面种子点 {pickedCount}: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})"); if (pickedCount < 3) { UpdateMainStatus($"已记录端面点 {pickedCount}/3,请继续在同一端面平面上点击。"); return; } AnalyzeCurrentAssemblyEndFace(); CleanupAssemblyEndFaceSelection(clearVisuals: false); }, "处理端面三点拾取"); } catch (Exception ex) { LogManager.Error($"[直线装配] 端面三点分析失败: {ex.Message}", ex); UpdateMainStatus($"端面三点分析失败: {ex.Message}"); CleanupAssemblyEndFaceSelection(clearVisuals: false); } } private async void OnAssemblyInstallationMouseClicked(object sender, PickItemResult pickResult) { try { if (!_isSelectingAssemblyInstallationPoint || pickResult == null) { return; } await SafeExecuteAsync(() => { if (!IsPickOnAssemblyTerminalObject(pickResult)) { UpdateMainStatus("请点击当前终点箱体表面,不要点到其他对象。"); return; } _assemblyInstallationSeedPoints.Add(pickResult.Point); RenderAssemblyInstallationPickPoints(_assemblyInstallationSeedPoints); int pickedCount = _assemblyInstallationSeedPoints.Count; LogManager.Info($"[直线装配] 已记录安装面点 {pickedCount}: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})"); if (pickedCount < 2) { UpdateMainStatus("已记录安装面点 1/2,请继续在同一安装面上点击第二个点。"); return; } BuildAndRenderAssemblyInstallationReference( _assemblyInstallationSeedPoints[0], _assemblyInstallationSeedPoints[1]); ApplyAssemblyInstallationReferenceForCurrentWorkflow(); CleanupAssemblyInstallationSelection(clearVisuals: false); }, "处理安装点拾取"); } catch (Exception ex) { LogManager.Error($"[直线装配] 安装点计算失败: {ex.Message}", ex); UpdateMainStatus($"安装点计算失败: {ex.Message}"); CleanupAssemblyInstallationSelection(clearVisuals: true); } } private void PrepareRailAssemblyWorkflowForMode( RailAssemblyWorkflowMode workflowMode, string editActionDescription, bool syncMountModeFromRoute) { PathRoute selectedRailRoute = null; if (IsRailAssemblyEditMode(workflowMode)) { selectedRailRoute = RequireSelectedRailRouteForAssemblyEdit(editActionDescription); if (syncMountModeFromRoute) { AssemblyMountMode = selectedRailRoute.RailMountMode; } } SetRailAssemblyWorkflowMode(workflowMode); } private PathRoute RequireSelectedRailRouteForAssemblyEdit(string errorMessage) { var selectedRailRoute = GetSelectedCoreRoute(); if (selectedRailRoute == null || selectedRailRoute.PathType != PathType.Rail) { throw new InvalidOperationException(errorMessage); } return selectedRailRoute; } private void BeginAssemblyStartPointSelection(string statusMessage, string logMessage, string toolPluginFailureMessage) { CleanupAssemblyReferenceSelection(); _pathPlanningManager.DisableMouseHandling(); IsSelectingAssemblyStartPoint = true; NotifyRailAssemblyCommandStateChanged(); PathClickToolPlugin.MouseClicked -= OnAssemblyReferenceMouseClicked; PathClickToolPlugin.MouseClicked += OnAssemblyReferenceMouseClicked; if (!ForceReinitializeToolPlugin(subscribeToEvents: false)) { CleanupAssemblyReferenceSelection(); throw new InvalidOperationException(toolPluginFailureMessage); } _pathPlanningManager.StartClickTool(PathPointType.StartPoint); UpdateMainStatus(statusMessage); LogManager.Info(logMessage); } private void BeginAssemblyEndFaceSelection(RailAssemblyWorkflowMode workflowMode) { CleanupAssemblyReferenceSelection(); CleanupAssemblyEndFaceSelection(clearVisuals: false); CleanupAssemblyInstallationSelection(); ClearAssemblyEndFaceAnalysisVisuals(); ResetAssemblyEndFaceAnalysisState(); ClearAssemblyInstallationReferenceVisuals(); ResetAssemblyInstallationReferenceState(); _assemblyEndFaceSeedPoints.Clear(); _pathPlanningManager.DisableMouseHandling(); _isSelectingAssemblyEndFacePoints = true; NotifyRailAssemblyCommandStateChanged(); PathClickToolPlugin.MouseClicked -= OnAssemblyEndFaceMouseClicked; PathClickToolPlugin.MouseClicked += OnAssemblyEndFaceMouseClicked; if (!ForceReinitializeToolPlugin(subscribeToEvents: false)) { CleanupAssemblyEndFaceSelection(clearVisuals: false); throw new InvalidOperationException("ToolPlugin 初始化失败,请重试。"); } UpdateMainStatus("请在同一个端面平面上连续点击 3 个点,系统将分析端面中心。"); LogManager.Info($"[直线装配] 已进入端面三点分析模式,模式={GetRailAssemblyWorkflowLabel(workflowMode)}"); } private void BeginAssemblyInstallationSelection() { CleanupAssemblyReferenceSelection(); CleanupAssemblyEndFaceSelection(clearVisuals: false); CleanupAssemblyInstallationSelection(); ClearAssemblyInstallationReferenceVisuals(); _pathPlanningManager.DisableMouseHandling(); _isSelectingAssemblyInstallationPoint = true; NotifyRailAssemblyCommandStateChanged(); PathClickToolPlugin.MouseClicked -= OnAssemblyInstallationMouseClicked; PathClickToolPlugin.MouseClicked += OnAssemblyInstallationMouseClicked; if (!ForceReinitializeToolPlugin(subscribeToEvents: false)) { CleanupAssemblyInstallationSelection(); throw new InvalidOperationException("ToolPlugin 初始化失败,请重试。"); } _assemblyInstallationSeedPoints.Clear(); UpdateMainStatus("请在终点箱体表面连续点击两个安装面点,系统将按两点和光轴计算安装参考面与安装点。"); LogManager.Info($"[直线装配] 已进入安装点拾取模式,模式={GetRailAssemblyWorkflowLabel(_railAssemblyWorkflowMode)}"); } private void ApplyAssemblyStartPointForCurrentWorkflow(Point3D projectedStartPoint) { if (IsRailEditingWorkflowActive) { UpdateSelectedRailRouteStartPoint(projectedStartPoint); ClearAssemblyReferenceLineVisuals(); UpdateMainStatus("已根据辅助线更新当前 Rail 路径起点"); return; } CreateAssemblyLinearRoute(projectedStartPoint); UpdateMainStatus("已根据辅助线起点生成终端安装路径"); } private void ApplyAssemblyInstallationReferenceForCurrentWorkflow() { if (IsRailEditingWorkflowActive) { var selectedRailRoute = RequireSelectedRailRouteForAssemblyEdit("当前没有可更新安装点的 Rail 路径。"); PersistAssemblyInstallationReferenceToRailRoute( selectedRailRoute, "已重选安装点并同步更新 Rail 路径安装参数"); return; } LogManager.Info("[直线装配] 安装点已确定,请继续选择起点以生成新路径"); } private void CreateAssemblyLinearRoute(Point3D startPoint) { Point3D endPoint = AssemblyReferencePathManager.Instance.ReferenceLineEnd; string routeName = $"人工_{DateTime.Now:MMdd_HHmmss}"; var route = new PathRoute(routeName) { Description = $"直线装配路径 - {AssemblyTerminalObjectName}", PathType = PathType.Rail, RailMountMode = AssemblyMountMode, RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine, RailNormalOffset = UnitsConverter.ConvertFromMeters(AssemblyAnchorVerticalOffsetInMeters) }; if (_hasAssemblyInstallationReference) { route.RailPreferredNormal = CreatePersistedAssemblyPreferredNormal(); } route.AddPoint(new PathPoint(startPoint, "起点", PathPointType.StartPoint)); route.AddPoint(new PathPoint(endPoint, "装配终点", PathPointType.EndPoint)); if (!_pathPlanningManager.AddRoute(route)) { throw new InvalidOperationException("装配路径添加失败"); } _pathPlanningManager.SetCurrentRoute(route); if (!_pathPlanningManager.GeneratePath(route)) { throw new InvalidOperationException("装配路径生成失败"); } if (!_pathPlanningManager.FinishEditing()) { throw new InvalidOperationException("装配路径完成编辑失败"); } var generatedRouteViewModel = PathRoutes.FirstOrDefault(p => p.Name == route.Name); if (generatedRouteViewModel != null) { SelectedPathRoute = generatedRouteViewModel; UpdatePassageSpaceVisualizationForPathType(route.PathType); } HideAssemblyReferenceVisuals( resetStartPointText: false, statusMessage: null, logMessage: "[直线装配] 路径生成完成后已隐藏辅助线可视化"); AssemblyTerminalObjectInfo = string.Format( "对接终点=({0:F2}, {1:F2}, {2:F2}),装配起点={3},安装方式={4},对接基准={5}", endPoint.X, endPoint.Y, endPoint.Z, AssemblyStartPointText, AssemblyMountMode == RailMountMode.OverRail ? "轨上安装" : "轨下安装", GetAssemblyAnchorText()); OnPropertyChanged(nameof(PathRoutes)); LogManager.Info( $"[直线装配] 已生成并完成路径: {route.Name},起点=({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2}),终点=({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2})" + $"{(route.RailPreferredNormal != null ? $",安装法向=({route.RailPreferredNormal.X:F3}, {route.RailPreferredNormal.Y:F3}, {route.RailPreferredNormal.Z:F3})" : string.Empty)}"); } private void UpdateSelectedRailRouteStartPoint(Point3D startPoint) { var route = GetSelectedCoreRoute(); if (route == null || route.PathType != PathType.Rail) { throw new InvalidOperationException("当前没有可更新起点的 Rail 路径。"); } int startPointIndex = GetRailRouteStartPointIndex(route); if (startPointIndex < 0) { throw new InvalidOperationException("当前 Rail 路径没有可更新的起点。"); } if (!_pathPlanningManager.UpdatePathPointWithConstraints(route, startPointIndex, startPoint)) { throw new InvalidOperationException("更新 Rail 路径起点失败。"); } route.LastModified = DateTime.Now; _pathPlanningManager.SavePathToDatabase(route); _pathPlanningManager.DrawRouteVisualization(route, isAutoPath: false); _pathPlanningManager.RaiseVisualizationStateChanged(); if (SelectedPathRoute != null && SelectedPathRoute.Id == route.Id) { SyncPathViewModelFromCoreRoute(SelectedPathRoute, route, preserveSelection: true); RefreshSelectedRailReferenceLineVisuals(); } LogManager.Info($"[Rail构型] {route.Name}: 已重选起点为 ({startPoint.X:F3}, {startPoint.Y:F3}, {startPoint.Z:F3})"); } private void HideAssemblyReferenceVisuals(bool resetStartPointText, string statusMessage, string logMessage) { CleanupAssemblyReferenceSelection(); CleanupAssemblyEndFaceSelection(clearVisuals: false); ClearAssemblyReferenceLineVisuals(); ClearAssemblyEndFaceAnalysisVisuals(); ClearAssemblyInstallationReferenceVisuals(); if (resetStartPointText) { AssemblyStartPointText = "未选择"; } NotifyRailAssemblyCommandStateChanged(); if (!string.IsNullOrWhiteSpace(statusMessage)) { UpdateMainStatus(statusMessage); } if (!string.IsNullOrWhiteSpace(logMessage)) { LogManager.Info(logMessage); } } private void ResetRailAssemblyWorkflowState(bool announce, string statusMessage = null, string logMessage = null) { CleanupAssemblyReferenceSelection(); CleanupAssemblyEndFaceSelection(clearVisuals: true); CleanupAssemblyInstallationSelection(clearVisuals: true); ClearAssemblyReferenceLineVisuals(); ClearAssemblyEndFaceAnalysisVisuals(); ClearAssemblyInstallationReferenceVisuals(); ResetAssemblyEndFaceAnalysisState(); ResetAssemblyInstallationReferenceState(); _railAssemblyContext.ResetSession(); HasAssemblyTerminalObject = false; AssemblyTerminalObjectName = _assemblyTerminalObjectName; AssemblyTerminalObjectInfo = _assemblyTerminalObjectInfo; AssemblyStartPointText = _assemblyStartPointText; SetRailAssemblyWorkflowMode(RailAssemblyWorkflowMode.None); NotifyRailAssemblyCommandStateChanged(); if (announce && !string.IsNullOrWhiteSpace(statusMessage)) { UpdateMainStatus(statusMessage); } if (!string.IsNullOrWhiteSpace(logMessage)) { LogManager.Info(logMessage); } } private void ExecuteCancelRailAssemblyWorkflow() { try { ResetRailAssemblyWorkflowState( announce: true, statusMessage: "已取消 Rail 装配流程", logMessage: "[直线装配] 已取消 Rail 装配流程并清理状态"); } catch (Exception ex) { LogManager.Error($"[直线装配] 取消 Rail 装配流程失败: {ex.Message}", ex); UpdateMainStatus($"取消 Rail 装配流程失败: {ex.Message}"); } } private Point3D GetAssemblyTerminalAnchorPoint() { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { throw new InvalidOperationException("终点箱体未设置或已失效,无法计算对接点"); } if (_hasAssemblyInstallationReference && _assemblyInstallationAnchorPoint != null) { return _assemblyInstallationAnchorPoint; } HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter); var bounds = _assemblyTerminalObject.BoundingBox(); Point3D canonicalCenter = adapter.ToCanonicalPoint(bounds.Center); Vector3D canonicalUp = adapter.ToCanonicalVector( ModelItemTransformHelper.GetDirectionFromTransform( _assemblyTerminalObject.Transform, projectFrame.DefaultModelAxisConvention.UpAxis)); double direction = GetAssemblyAnchorDirection(); double verticalOffset = UnitsConverter.ConvertFromMeters(AssemblyAnchorVerticalOffsetInMeters); Point3D canonicalAnchorPoint = new Point3D( canonicalCenter.X + canonicalUp.X * verticalOffset * direction, canonicalCenter.Y + canonicalUp.Y * verticalOffset * direction, canonicalCenter.Z + canonicalUp.Z * verticalOffset * direction); return adapter.FromCanonicalPoint(canonicalAnchorPoint); } /// /// 根据 Rail 路径终点和路径长度计算起点位置。 /// 起点位于从终点沿光轴方向(远离球心方向)延伸路径长度的位置。 /// private Point3D CalculateRailStartPointFromEndPoint(Point3D endPoint, double pathLength) { HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter); Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint(); Point3D canonicalEndPoint = adapter.ToCanonicalPoint(endPoint); Point3D canonicalOpticalAxisRef = adapter.ToCanonicalPoint(opticalAxisReferencePoint); // 计算从球心到光轴参考点的方向(作为参考线方向) Vector3D direction = new Vector3D( canonicalOpticalAxisRef.X - projectFrame.SphereCenterInCanonical.X, canonicalOpticalAxisRef.Y - projectFrame.SphereCenterInCanonical.Y, canonicalOpticalAxisRef.Z - projectFrame.SphereCenterInCanonical.Z); direction = direction.Normalize(); // 起点 = 终点 + 方向 * 路径长度 Point3D canonicalStartPoint = new Point3D( canonicalEndPoint.X + direction.X * pathLength, canonicalEndPoint.Y + direction.Y * pathLength, canonicalEndPoint.Z + direction.Z * pathLength); return adapter.FromCanonicalPoint(canonicalStartPoint); } private void UpdateAssemblyInstallationAnchorFromVerticalOffset() { if (!_hasAssemblyInstallationReference || _assemblyInstallationBaseAnchorPoint == null) { return; } double offset = UnitsConverter.ConvertFromMeters(_assemblyAnchorVerticalOffsetInMeters); _assemblyInstallationAnchorPoint = new Point3D( _assemblyInstallationBaseAnchorPoint.X + _assemblyInstallationPlaneNormal.X * (float)offset, _assemblyInstallationBaseAnchorPoint.Y + _assemblyInstallationPlaneNormal.Y * (float)offset, _assemblyInstallationBaseAnchorPoint.Z + _assemblyInstallationPlaneNormal.Z * (float)offset); if (_hasAssemblyInstallationReference) { RenderAssemblyInstallationPickPoints(_assemblyInstallationSeedPoints); RenderAssemblyInstallationAnchorPoint(_assemblyInstallationAnchorPoint); RenderAssemblyInstallationCenterLine(_assemblyInstallationPickPoint, GetCurrentAssemblyOpticalAxisDirection()); RenderAssemblyInstallationPlane(_assemblyInstallationAnchorPoint, GetCurrentAssemblyOpticalAxisDirection(), _assemblyInstallationPlaneSpanDirection); } else { ClearAssemblyInstallationReferenceVisuals(); } } private Point3D CreatePersistedAssemblyPreferredNormal() { HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); Vector3 normalizedPreferredNormal = RailPathPoseHelper.NormalizePreferredNormalToHostUpHemisphere( _assemblyInstallationPlaneNormal, adapter.HostUpVector3); return new Point3D( normalizedPreferredNormal.X, normalizedPreferredNormal.Y, normalizedPreferredNormal.Z); } private Vector3 GetCurrentAssemblyOpticalAxisDirection() { Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint(); Point3D sphereCenterPoint = new Point3D( AssemblySphereCenterX, AssemblySphereCenterY, AssemblySphereCenterZ); Vector3 opticalAxisDirection = new Vector3( (float)(opticalAxisReferencePoint.X - sphereCenterPoint.X), (float)(opticalAxisReferencePoint.Y - sphereCenterPoint.Y), (float)(opticalAxisReferencePoint.Z - sphereCenterPoint.Z)); return Vector3.Normalize(opticalAxisDirection); } private Point3D GetAssemblyOpticalAxisReferencePoint() { if (_hasAssemblyEndFaceAnalysis && _assemblyEndFaceCenterPoint != null) { return _assemblyEndFaceCenterPoint; } if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { throw new InvalidOperationException("终点箱体未设置或已失效,无法计算光轴参考点"); } return _assemblyTerminalObject.BoundingBox().Center; } private AssemblyReferenceLine BuildAssemblyReferenceLine() { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { throw new InvalidOperationException("终点箱体未设置或已失效,无法生成装配参考线"); } HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter); Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint(); Point3D endPoint = GetAssemblyTerminalAnchorPoint(); Point3D canonicalAxisReferencePoint = adapter.ToCanonicalPoint(opticalAxisReferencePoint); Point3D canonicalEndPoint = adapter.ToCanonicalPoint(endPoint); Vector3D direction = new Vector3D( canonicalAxisReferencePoint.X - projectFrame.SphereCenterInCanonical.X, canonicalAxisReferencePoint.Y - projectFrame.SphereCenterInCanonical.Y, canonicalAxisReferencePoint.Z - projectFrame.SphereCenterInCanonical.Z); double directionLengthSquared = direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z; if (directionLengthSquared < 1e-9) { throw new InvalidOperationException("光轴参考点与项目球心重合,无法生成装配参考线方向"); } direction = direction.Normalize(); double rodLength = UnitsConverter.ConvertFromMeters(AssemblyReferenceRodLengthInMeters); Point3D canonicalStartPoint = new Point3D( canonicalEndPoint.X + direction.X * rodLength, canonicalEndPoint.Y + direction.Y * rodLength, canonicalEndPoint.Z + direction.Z * rodLength); Point3D startPoint = adapter.FromCanonicalPoint(canonicalStartPoint); LogManager.Info( $"[直线装配] 参考线已计算,终点锚点=({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2}), " + $"光轴参考点=({opticalAxisReferencePoint.X:F2}, {opticalAxisReferencePoint.Y:F2}, {opticalAxisReferencePoint.Z:F2}), " + $"参考线外端=({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2}), " + $"球心到光轴参考点方向(内部坐标)=({direction.X:F3}, {direction.Y:F3}, {direction.Z:F3})"); return new AssemblyReferenceLine(startPoint, endPoint, direction); } private void RefreshAssemblyTerminalObjectInfo(Point3D referenceStartPoint = null, Point3D referenceEndPoint = null) { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { return; } var bounds = _assemblyTerminalObject.BoundingBox(); string anchorText = GetAssemblyAnchorText(); string mountText = AssemblyMountMode == RailMountMode.OverRail ? "轨上安装" : "轨下安装"; Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint(); string opticalAxisText = _hasAssemblyEndFaceAnalysis ? string.Format("端面中心=({0:F2}, {1:F2}, {2:F2})", opticalAxisReferencePoint.X, opticalAxisReferencePoint.Y, opticalAxisReferencePoint.Z) : string.Format("箱体中心=({0:F2}, {1:F2}, {2:F2})", opticalAxisReferencePoint.X, opticalAxisReferencePoint.Y, opticalAxisReferencePoint.Z); string anchorPointText = _hasAssemblyInstallationReference ? string.Format("{0}点=({1:F2}, {2:F2}, {3:F2})", anchorText, _assemblyInstallationAnchorPoint.X, _assemblyInstallationAnchorPoint.Y, _assemblyInstallationAnchorPoint.Z) : string.Format("{0}点=未选择", anchorText); string installationText = _hasAssemblyInstallationReference ? string.Format( "安装面点中点=({0:F2}, {1:F2}, {2:F2}),安装点=({3:F2}, {4:F2}, {5:F2}),偏距={6:F3}m", _assemblyInstallationPickPoint.X, _assemblyInstallationPickPoint.Y, _assemblyInstallationPickPoint.Z, _assemblyInstallationAnchorPoint.X, _assemblyInstallationAnchorPoint.Y, _assemblyInstallationAnchorPoint.Z, _assemblyInstallationOffsetDistanceInMeters) : "安装点=未选择"; if (referenceStartPoint != null && referenceEndPoint != null) { AssemblyTerminalObjectInfo = string.Format( "中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7},{8},{9},垂直偏移={10:F3}m,辅助线外端=({11:F2}, {12:F2}, {13:F2})", bounds.Center.X, bounds.Center.Y, bounds.Center.Z, bounds.Max.X - bounds.Min.X, bounds.Max.Y - bounds.Min.Y, bounds.Max.Z - bounds.Min.Z, mountText, anchorPointText, opticalAxisText, installationText, AssemblyAnchorVerticalOffsetInMeters, referenceStartPoint.X, referenceStartPoint.Y, referenceStartPoint.Z); return; } AssemblyTerminalObjectInfo = string.Format( "中心=({0:F2}, {1:F2}, {2:F2}),尺寸=({3:F2}, {4:F2}, {5:F2}),{6},{7},{8},{9},垂直偏移={10:F3}m", bounds.Center.X, bounds.Center.Y, bounds.Center.Z, bounds.Max.X - bounds.Min.X, bounds.Max.Y - bounds.Min.Y, bounds.Max.Z - bounds.Min.Z, mountText, anchorPointText, opticalAxisText, installationText, AssemblyAnchorVerticalOffsetInMeters); } private void RefreshAssemblyReferenceRodIfNeeded() { if (!ShouldShowAssemblyReferenceLineVisuals()) { ClearAssemblyReferenceLineVisuals(); return; } if (_assemblyTerminalObject == null) { return; } try { AssemblyReferenceLine referenceLine = BuildAssemblyReferenceLine(); AssemblyReferencePathManager.Instance.CreateOrUpdateReferenceRod( referenceLine.StartPoint, referenceLine.EndPoint, AssemblyReferenceRodDiameterInMeters); RefreshAssemblyTerminalObjectInfo(referenceLine.StartPoint, referenceLine.EndPoint); RenderAssemblyAnchorMarker(); RenderAssemblyCenterGuideLine(); RenderAssemblyReferenceLine(referenceLine); NotifyRailAssemblyCommandStateChanged(); } catch (Exception ex) { LogManager.Error($"[直线装配] 刷新参考杆失败: {ex.Message}", ex); } } private void InitializeAssemblyAnchorVerticalOffsetFromTerminalObject() { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { return; } HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter); var bounds = _assemblyTerminalObject.BoundingBox(); double suggestedOffset = UnitsConverter.ConvertToMeters( Math.Max(0.0, projectFrame.DefaultModelAxisConvention.GetUpSize(bounds) / 2.0)); _assemblyAnchorVerticalOffsetInMeters = suggestedOffset; OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); } private double GetAssemblyAnchorDirection() { return PathRoute.IsTopPayloadAnchorForMountMode(AssemblyMountMode) ? 1.0 : -1.0; } private string GetAssemblyAnchorText() { return PathRoute.IsTopPayloadAnchorForMountMode(AssemblyMountMode) ? "顶面对接" : "底面对接"; } private sealed class AssemblyReferenceLine { public AssemblyReferenceLine(Point3D startPoint, Point3D endPoint, Vector3D direction) { StartPoint = startPoint; EndPoint = endPoint; Direction = direction; } public Point3D StartPoint { get; } public Point3D EndPoint { get; } public Vector3D Direction { get; } } private static double CalculateDefaultAssemblyReferenceRodDiameterInMeters() { double standardRadiusInMeters = ConfigManager.Instance.Current.PathEditing.CellSizeMeters; standardRadiusInMeters = Math.Max(0.1, Math.Min(0.5, standardRadiusInMeters)); return standardRadiusInMeters * 0.8; } private void RenderAssemblyAnchorMarker() { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { return; } var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } Point3D anchorPoint = GetAssemblyTerminalAnchorPoint(); LogManager.Info( $"[直线装配] 已渲染终点锚点标记: ({anchorPoint.X:F2}, {anchorPoint.Y:F2}, {anchorPoint.Z:F2})"); var markerRoute = new PathRoute("装配对接点") { Id = AssemblyAnchorMarkerPathId, Description = "直线装配终点对接标记" }; AddVisualizationPoint(markerRoute, anchorPoint, "对接点", PathPointType.EndPoint); renderPlugin.RenderPointOnly(markerRoute); } private void RenderAssemblyReferenceLine(AssemblyReferenceLine referenceLine) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null || referenceLine == null) { return; } renderPlugin.RenderRailBaseline( AssemblyReferenceLinePathId, new List { referenceLine.EndPoint, referenceLine.StartPoint }); LogManager.Info( $"[直线装配] 已渲染参考线: 终点锚点=({referenceLine.EndPoint.X:F2}, {referenceLine.EndPoint.Y:F2}, {referenceLine.EndPoint.Z:F2}), " + $"参考线外端=({referenceLine.StartPoint.X:F2}, {referenceLine.StartPoint.Y:F2}, {referenceLine.StartPoint.Z:F2})"); } private void RenderAssemblyCenterGuideLine() { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { return; } var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter); Point3D centerPoint = GetAssemblyOpticalAxisReferencePoint(); Point3D sphereCenterPoint = adapter.FromCanonicalPoint(projectFrame.SphereCenterInCanonical); renderPlugin.RenderRailBaseline( AssemblyCenterGuideLinePathId, new List { sphereCenterPoint, centerPoint }, RenderStyleName.AssemblyGuideLine); LogManager.Info( $"[直线装配] 已渲染球心到光轴参考点基准线: 球心=({sphereCenterPoint.X:F2}, {sphereCenterPoint.Y:F2}, {sphereCenterPoint.Z:F2}), " + $"光轴参考点=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2})"); } private void RenderAssemblyEndFaceSeedPoints() { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.RemovePath(AssemblyEndFaceSeedPathId); if (_assemblyEndFaceSeedPoints.Count == 0) { return; } var markerRoute = new PathRoute("端面三点") { Id = AssemblyEndFaceSeedPathId, Description = "终端安装端面三点" }; for (int i = 0; i < _assemblyEndFaceSeedPoints.Count; i++) { AddVisualizationPoint( markerRoute, _assemblyEndFaceSeedPoints[i], $"端面点{i + 1}", PathPointType.WayPoint, UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); } renderPlugin.RenderPointOnly(markerRoute); } private void RenderAssemblyEndFaceCenter(Point3D centerPoint) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.RemovePath(AssemblyEndFaceCenterPathId); var markerRoute = new PathRoute("端面中心") { Id = AssemblyEndFaceCenterPathId, Description = "终端安装端面中心" }; AddVisualizationPoint( markerRoute, centerPoint, "端面中心", PathPointType.WayPoint, UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); renderPlugin.RenderPointOnly(markerRoute); } private static void AddVisualizationPoint( PathRoute route, Point3D position, string name, PathPointType type, double? visualizationDiameter = null) { if (route == null) { throw new ArgumentNullException(nameof(route)); } route.Points.Add(new PathPoint(position, name, type) { Index = route.Points.Count, VisualizationDiameter = visualizationDiameter }); } private void RenderAssemblyEndFaceNormal(Point3D centerPoint, Vector3 normal) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.ClearRailBaseline(AssemblyEndFaceNormalPathId); double lineLength = UnitsConverter.ConvertFromMeters(Math.Max(0.5, AssemblyReferenceRodDiameterInMeters * 4.0)); Point3D normalEndPoint = new Point3D( centerPoint.X + normal.X * (float)lineLength, centerPoint.Y + normal.Y * (float)lineLength, centerPoint.Z + normal.Z * (float)lineLength); renderPlugin.RenderRailBaseline( AssemblyEndFaceNormalPathId, new List { centerPoint, normalEndPoint }, RenderStyleName.AssemblyGuideLine); } private void BuildAndRenderAssemblyInstallationReference(Point3D firstPickPoint, Point3D secondPickPoint) { Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint(); Point3D sphereCenterPoint = new Point3D( AssemblySphereCenterX, AssemblySphereCenterY, AssemblySphereCenterZ); Vector3 opticalAxisDirection = new Vector3( (float)(opticalAxisReferencePoint.X - sphereCenterPoint.X), (float)(opticalAxisReferencePoint.Y - sphereCenterPoint.Y), (float)(opticalAxisReferencePoint.Z - sphereCenterPoint.Z)); AssemblyInstallationReferenceResult result = AssemblyInstallationReferenceBuilder.Build( new Vector3((float)opticalAxisReferencePoint.X, (float)opticalAxisReferencePoint.Y, (float)opticalAxisReferencePoint.Z), opticalAxisDirection, new Vector3((float)firstPickPoint.X, (float)firstPickPoint.Y, (float)firstPickPoint.Z), new Vector3((float)secondPickPoint.X, (float)secondPickPoint.Y, (float)secondPickPoint.Z)); _hasAssemblyInstallationReference = true; _assemblyInstallationPickPoint = AssemblyEndFaceAnalyzer.ToPoint3D((result.PickPoint + result.SecondaryPickPoint) * 0.5f); _assemblyInstallationBaseAnchorPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.AnchorPoint); _assemblyInstallationPlaneNormal = result.PlaneNormal; _assemblyInstallationPlaneSpanDirection = result.PlaneSpanDirection; _assemblyInstallationOffsetDistanceInMeters = UnitsConverter.ConvertToMeters(result.OffsetDistance); _assemblyAnchorVerticalOffsetInMeters = DefaultAssemblyAnchorVerticalOffsetInMeters; OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); UpdateAssemblyInstallationAnchorFromVerticalOffset(); RenderAssemblyInstallationPickPoints(_assemblyInstallationSeedPoints); RenderAssemblyInstallationCenterLine(AssemblyEndFaceAnalyzer.ToPoint3D(result.InstallLineBasePoint), result.OpticalAxisDirection); RefreshAssemblyTerminalObjectInfo(); RefreshAssemblyReferenceRodIfNeeded(); UpdateMainStatus( $"安装参考已计算:安装点=({_assemblyInstallationAnchorPoint.X:F2}, {_assemblyInstallationAnchorPoint.Y:F2}, {_assemblyInstallationAnchorPoint.Z:F2}),偏距={_assemblyInstallationOffsetDistanceInMeters:F3}m,可继续取起点"); LogManager.Info( $"[直线装配] 安装参考已计算: 安装面点1=({firstPickPoint.X:F3}, {firstPickPoint.Y:F3}, {firstPickPoint.Z:F3}), " + $"安装面点2=({secondPickPoint.X:F3}, {secondPickPoint.Y:F3}, {secondPickPoint.Z:F3}), " + $"安装点=({_assemblyInstallationAnchorPoint.X:F3}, {_assemblyInstallationAnchorPoint.Y:F3}, {_assemblyInstallationAnchorPoint.Z:F3}), " + $"偏距={_assemblyInstallationOffsetDistanceInMeters:F3}m, 平面法向=({result.PlaneNormal.X:F4}, {result.PlaneNormal.Y:F4}, {result.PlaneNormal.Z:F4})"); NotifyRailAssemblyCommandStateChanged(); } private void RenderAssemblyInstallationPickPoints(IReadOnlyList pickPoints) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.RemovePath(AssemblyInstallationPickPointPathId); var markerRoute = new PathRoute("安装点") { Id = AssemblyInstallationPickPointPathId, Description = "终端安装点" }; for (int i = 0; i < pickPoints.Count; i++) { AddVisualizationPoint( markerRoute, pickPoints[i], $"安装面点{i + 1}", PathPointType.WayPoint, UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); } renderPlugin.RenderPointOnly(markerRoute); } private void RenderAssemblyInstallationAnchorPoint(Point3D anchorPoint) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.RemovePath(AssemblyInstallationAnchorPointPathId); var markerRoute = new PathRoute("安装点") { Id = AssemblyInstallationAnchorPointPathId, Description = "终端安装点" }; AddVisualizationPoint( markerRoute, anchorPoint, "安装点", PathPointType.WayPoint, UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); renderPlugin.RenderPointOnly(markerRoute); } private void RenderAssemblyInstallationCenterLine(Point3D lineBasePoint, Vector3 axisDirection) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.ClearRailBaseline(AssemblyInstallationCenterLinePathId); double halfLength = GetAssemblyInstallationPlaneSideLength() * AssemblyInstallationLineLengthScale / 2.0; Point3D startPoint = new Point3D( lineBasePoint.X - axisDirection.X * (float)halfLength, lineBasePoint.Y - axisDirection.Y * (float)halfLength, lineBasePoint.Z - axisDirection.Z * (float)halfLength); Point3D endPoint = new Point3D( lineBasePoint.X + axisDirection.X * (float)halfLength, lineBasePoint.Y + axisDirection.Y * (float)halfLength, lineBasePoint.Z + axisDirection.Z * (float)halfLength); renderPlugin.RenderRailBaseline( AssemblyInstallationCenterLinePathId, new List { startPoint, endPoint }, RenderStyleName.AssemblyGuideLine); } private void RenderAssemblyInstallationPlane(Point3D planeCenter, Vector3 axisDirection, Vector3 planeSpanDirection) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } double sideLength = GetAssemblyInstallationPlaneSideLength(); double halfLength = sideLength / 2.0; double thickness = UnitsConverter.ConvertFromMeters(0.01); Vector3D xVector = new Vector3D( axisDirection.X * (float)sideLength, axisDirection.Y * (float)sideLength, axisDirection.Z * (float)sideLength); Vector3D yVector = new Vector3D( planeSpanDirection.X * (float)sideLength, planeSpanDirection.Y * (float)sideLength, planeSpanDirection.Z * (float)sideLength); Point3D origin = new Point3D( planeCenter.X - axisDirection.X * (float)halfLength - planeSpanDirection.X * (float)halfLength + _assemblyInstallationPlaneNormal.X * thickness, planeCenter.Y - axisDirection.Y * (float)halfLength - planeSpanDirection.Y * (float)halfLength + _assemblyInstallationPlaneNormal.Y * thickness, planeCenter.Z - axisDirection.Z * (float)halfLength - planeSpanDirection.Z * (float)halfLength + _assemblyInstallationPlaneNormal.Z * thickness); renderPlugin.RemovePath(AssemblyInstallationPlanePathId); renderPlugin.RenderRectanglePlane( AssemblyInstallationPlanePathId, origin, xVector, yVector, RenderStyleName.AssemblyInstallationPlane, filled: true); } private double GetAssemblyInstallationPlaneSideLength() { if (_assemblyTerminalObject == null || !ModelItemAnalysisHelper.IsModelItemValid(_assemblyTerminalObject)) { return UnitsConverter.ConvertFromMeters(1.0); } BoundingBox3D bounds = _assemblyTerminalObject.BoundingBox(); double maxSize = Math.Max( bounds.Max.X - bounds.Min.X, Math.Max(bounds.Max.Y - bounds.Min.Y, bounds.Max.Z - bounds.Min.Z)); return maxSize + UnitsConverter.ConvertFromMeters(AssemblyInstallationPlanePaddingInMeters) * 2.0; } private ProjectReferenceFrame CreateAssemblyProjectReferenceFrame(HostCoordinateAdapter adapter) { if (adapter == null) { throw new ArgumentNullException(nameof(adapter)); } Point3D hostSphereCenter = new Point3D( AssemblySphereCenterX, AssemblySphereCenterY, AssemblySphereCenterZ); Point3D canonicalSphereCenter = adapter.ToCanonicalPoint(hostSphereCenter); return new ProjectReferenceFrame( canonicalSphereCenter, HostCoordinateAdapter.CanonicalUp, ModelAxisConvention.CreateDefaultForHost(adapter.HostType)); } private void ClearAssemblyAnchorMarker() { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.RemovePath(AssemblyAnchorMarkerPathId); } private void ClearAssemblyReferenceLine() { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.ClearRailBaseline(AssemblyReferenceLinePathId); } private void ClearAssemblyCenterGuideLine() { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.ClearRailBaseline(AssemblyCenterGuideLinePathId); } private void ClearAssemblyReferenceLineVisuals() { AssemblyReferencePathManager.Instance.HideReferenceRod(); ClearAssemblyAnchorMarker(); ClearAssemblyCenterGuideLine(); ClearAssemblyReferenceLine(); NotifyRailAssemblyCommandStateChanged(); OnPropertyChanged(nameof(CanRepositionRailStartPoint)); } private void ClearAssemblyEndFaceAnalysisVisuals() { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.RemovePath(AssemblyEndFaceSeedPathId); renderPlugin.RemovePath(AssemblyEndFaceCenterPathId); renderPlugin.ClearRailBaseline(AssemblyEndFaceNormalPathId); } private void ResetAssemblyEndFaceAnalysisState() { _railAssemblyContext.ResetEndFaceAnalysis(); } private void ClearAssemblyInstallationReferenceVisuals() { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { return; } renderPlugin.RemovePath(AssemblyInstallationPickPointPathId); renderPlugin.RemovePath(AssemblyInstallationAnchorPointPathId); renderPlugin.ClearRailBaseline(AssemblyInstallationCenterLinePathId); renderPlugin.ClearRailBaseline(AssemblyInstallationOffsetLinePathId); renderPlugin.RemovePath(AssemblyInstallationPlanePathId); } private void ResetAssemblyInstallationReferenceState() { _railAssemblyContext.ResetInstallationReference(); OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); NotifyRailAssemblyCommandStateChanged(); } private void CleanupAssemblyReferenceSelection() { try { PathClickToolPlugin.MouseClicked -= OnAssemblyReferenceMouseClicked; IsSelectingAssemblyStartPoint = false; _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); NotifyRailAssemblyCommandStateChanged(); } catch (Exception ex) { LogManager.Error($"[直线装配] 清理起点拾取状态失败: {ex.Message}", ex); } } private void CleanupAssemblyInstallationSelection(bool clearVisuals = true) { try { PathClickToolPlugin.MouseClicked -= OnAssemblyInstallationMouseClicked; _isSelectingAssemblyInstallationPoint = false; _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); if (clearVisuals) { ClearAssemblyInstallationReferenceVisuals(); } NotifyRailAssemblyCommandStateChanged(); } catch (Exception ex) { LogManager.Error($"[直线装配] 清理参考安装点拾取状态失败: {ex.Message}", ex); } } private void CleanupAssemblyEndFaceSelection(bool clearVisuals) { try { PathClickToolPlugin.MouseClicked -= OnAssemblyEndFaceMouseClicked; _isSelectingAssemblyEndFacePoints = false; _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); if (clearVisuals) { _assemblyEndFaceSeedPoints.Clear(); ClearAssemblyEndFaceAnalysisVisuals(); ResetAssemblyEndFaceAnalysisState(); } NotifyRailAssemblyCommandStateChanged(); } catch (Exception ex) { LogManager.Error($"[直线装配] 清理端面三点拾取状态失败: {ex.Message}", ex); } } private bool IsPickOnAssemblyTerminalObject(PickItemResult pickResult) { if (pickResult?.ModelItem == null || _assemblyTerminalObject == null) { return false; } if (ModelItemAnalysisHelper.ModelItemEquals(pickResult.ModelItem, _assemblyTerminalObject)) { return true; } return pickResult.ModelItem.AncestorsAndSelf.Any(ancestor => ModelItemAnalysisHelper.ModelItemEquals(ancestor, _assemblyTerminalObject)); } private void AnalyzeCurrentAssemblyEndFace() { if (_assemblyEndFaceSeedPoints.Count != 3) { throw new InvalidOperationException("端面分析需要恰好 3 个种子点。"); } var triangles = GeometryHelper.ExtractTriangles(new[] { _assemblyTerminalObject }) .Select(triangle => new AnalysisTriangle3( new Vector3((float)triangle.Point1.X, (float)triangle.Point1.Y, (float)triangle.Point1.Z), new Vector3((float)triangle.Point2.X, (float)triangle.Point2.Y, (float)triangle.Point2.Z), new Vector3((float)triangle.Point3.X, (float)triangle.Point3.Y, (float)triangle.Point3.Z))) .ToList(); EndFaceAnalysisResult result = AssemblyEndFaceAnalyzer.Analyze( triangles, new Vector3((float)_assemblyEndFaceSeedPoints[0].X, (float)_assemblyEndFaceSeedPoints[0].Y, (float)_assemblyEndFaceSeedPoints[0].Z), new Vector3((float)_assemblyEndFaceSeedPoints[1].X, (float)_assemblyEndFaceSeedPoints[1].Y, (float)_assemblyEndFaceSeedPoints[1].Z), new Vector3((float)_assemblyEndFaceSeedPoints[2].X, (float)_assemblyEndFaceSeedPoints[2].Y, (float)_assemblyEndFaceSeedPoints[2].Z)); if (!result.IsReliable) { throw new InvalidOperationException(result.DiagnosticMessage); } Point3D centerPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.Center); Point3D sphereCenterPoint = new Point3D( AssemblySphereCenterX, AssemblySphereCenterY, AssemblySphereCenterZ); Vector3 orientedNormal = AssemblyEndFaceAnalyzer.OrientNormalTowardTarget( result.Normal, new Vector3((float)centerPoint.X, (float)centerPoint.Y, (float)centerPoint.Z), new Vector3((float)sphereCenterPoint.X, (float)sphereCenterPoint.Y, (float)sphereCenterPoint.Z)); _hasAssemblyEndFaceAnalysis = true; _assemblyEndFaceCenterPoint = centerPoint; _assemblyEndFaceNormal = orientedNormal; RenderAssemblyEndFaceSeedPoints(); RenderAssemblyEndFaceCenter(centerPoint); RenderAssemblyEndFaceNormal(centerPoint, orientedNormal); RefreshAssemblyTerminalObjectInfo(); RefreshAssemblyReferenceRodIfNeeded(); UpdateMainStatus($"端面分析完成:中心=({centerPoint.X:F2}, {centerPoint.Y:F2}, {centerPoint.Z:F2}),候选三角形={result.CandidateTriangleCount}"); LogManager.Info( $"[直线装配] 端面分析完成: 中心=({centerPoint.X:F3}, {centerPoint.Y:F3}, {centerPoint.Z:F3}), " + $"法向=({orientedNormal.X:F4}, {orientedNormal.Y:F4}, {orientedNormal.Z:F4}), " + $"三角形={result.CandidateTriangleCount}, 顶点={result.CandidateVertexCount}, 偏差={result.MaxPlaneDeviation:F6}"); } private async Task ExecuteNewPathAsync() { await SafeExecuteAsync(() => { UpdateMainStatus("正在创建新路径..."); // 清除所有现有路径的可视化显示 if (PathPointRenderPlugin.Instance != null) { try { PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); LogManager.Info("新建路径:已清除现有路径可视化显示(保留网格可视化)"); } catch (Exception ex) { LogManager.Error($"新建路径:清除现有路径可视化失败: {ex.Message}", ex); throw; // 重新抛出异常,让上层处理 } } if (_pathPlanningManager != null) { var newRoute = _pathPlanningManager.StartCreatingNewRoute(); if (newRoute != null) { // 创建对应的 WPF ViewModel var newPathViewModel = new PathRouteViewModel { Route = newRoute, IsActive = true }; // 使用统一的辅助方法创建路径点 var pathPoints = CreatePathPointViewModelsFromCoreRoute(newRoute); foreach (var point in pathPoints) { newPathViewModel.Points.Add(point); } // 添加到 UI 列表并选中 PathRoutes.Add(newPathViewModel); SelectedPathRoute = newPathViewModel; // 强制重新初始化ToolPlugin以确保获得鼠标焦点,新建路径需要订阅事件 if (!ForceReinitializeToolPlugin(subscribeToEvents: true)) { UpdateMainStatus("ToolPlugin初始化失败,请重试"); LogManager.Error("新建路径:ToolPlugin初始化失败"); return; } UpdateMainStatus($"已进入新建路径模式: {newRoute.Name} - 请在3D视图中点击设置路径点"); LogManager.Info($"开始新建路径: {newRoute.Name},已强制重新初始化ToolPlugin获取鼠标焦点"); // 不再显示对话框,状态文本已提供足够的反馈 } else { UpdateMainStatus("创建新路径失败:没有可通行的物流模型"); LogManager.Error("创建新路径失败:没有可通行的物流模型"); MessageBox.Show("创建新路径失败:没有找到任何可通行的物流模型。\n请先为模型设置可通行的物流属性,然后再尝试创建路径。", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } else { UpdateMainStatus("路径规划管理器未初始化"); LogManager.Error("路径规划管理器未初始化"); } }, "新建路径"); } private async Task ExecuteNewRailPathAsync() { await SafeExecuteAsync(() => { UpdateMainStatus("正在创建传统空轨路径..."); // 检查是否有空轨基准路径 if (PathPointRenderPlugin.Instance != null) { try { // 清除所有现有路径的可视化显示 PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); LogManager.Info("新建传统空轨路径:已清除现有路径可视化显示(保留网格可视化)"); } catch (Exception ex) { LogManager.Error($"新建传统空轨路径:清除现有路径可视化失败: {ex.Message}", ex); throw; } } // 自动提取并显示所有空轨模型的基准线 ExtractAndRenderRailBaselinePaths(); if (_pathPlanningManager != null) { // 创建传统空轨路径 var newRoute = _pathPlanningManager.StartCreatingNewRoute( isRailPath: true, pathType: PathType.Rail); if (newRoute != null) { // 创建对应的 WPF ViewModel var newPathViewModel = new PathRouteViewModel { Route = newRoute, IsActive = true, PathType = PathType.Rail }; // 使用统一的辅助方法创建路径点 var pathPoints = CreatePathPointViewModelsFromCoreRoute(newRoute); foreach (var point in pathPoints) { newPathViewModel.Points.Add(point); } // 添加到 UI 列表并选中 PathRoutes.Add(newPathViewModel); SelectedPathRoute = newPathViewModel; // 强制重新初始化ToolPlugin以确保获得鼠标焦点 if (!ForceReinitializeToolPlugin(subscribeToEvents: true)) { UpdateMainStatus("ToolPlugin初始化失败,请重试"); LogManager.Error("新建传统空轨路径:ToolPlugin初始化失败"); return; } // 启动点击工具,设置空轨吸附模式 _pathPlanningManager.StartClickTool(PathPointType.WayPoint, enableRailSnapping: true); LogManager.Info($"已启动传统空轨路径点击工具(吸附模式): {newRoute.Name}"); UpdateMainStatus($"已进入传统空轨路径模式: {newRoute.Name} - 请在3D视图中点击空轨模型设置路径点(将自动吸附到基准路径)"); LogManager.Info($"开始新建传统空轨路径: {newRoute.Name},已强制重新初始化ToolPlugin获取鼠标焦点"); } else { UpdateMainStatus("创建传统空轨路径失败:没有可通行的物流模型"); LogManager.Error("创建传统空轨路径失败:没有可通行的物流模型"); MessageBox.Show("创建传统空轨路径失败:没有找到任何可通行的物流模型。\n请先为模型设置可通行的物流属性,然后再尝试创建路径。", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } else { UpdateMainStatus("路径规划管理器未初始化"); LogManager.Error("路径规划管理器未初始化"); } }, "新建传统空轨路径"); } /// /// 执行新建吊装路径命令 /// private async Task ExecuteNewHoistingPathAsync() { await SafeExecuteAsync(() => { UpdateMainStatus("正在创建新吊装路径..."); // 清除所有现有路径的可视化显示 if (PathPointRenderPlugin.Instance != null) { try { PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); LogManager.Info("新建吊装路径:已清除现有路径可视化显示(保留网格可视化)"); } catch (Exception ex) { LogManager.Error($"新建吊装路径:清除现有路径可视化失败: {ex.Message}", ex); throw; } } if (_pathPlanningManager != null) { // 创建吊装路径 var newRoute = _pathPlanningManager.StartCreatingNewRoute( isRailPath: false, pathType: PathType.Hoisting); if (newRoute != null) { // 创建对应的 WPF ViewModel var newPathViewModel = new PathRouteViewModel { Route = newRoute, IsActive = true, PathType = PathType.Hoisting }; // 使用统一的辅助方法创建路径点 var pathPoints = CreatePathPointViewModelsFromCoreRoute(newRoute); foreach (var point in pathPoints) { newPathViewModel.Points.Add(point); } // 添加到 UI 列表并选中 PathRoutes.Add(newPathViewModel); SelectedPathRoute = newPathViewModel; // 强制重新初始化ToolPlugin以确保获得鼠标焦点 if (!ForceReinitializeToolPlugin(subscribeToEvents: true)) { UpdateMainStatus("ToolPlugin初始化失败,请重试"); LogManager.Error("新建吊装路径:ToolPlugin初始化失败"); return; } // 启动点击工具,启用吊装模式 _pathPlanningManager.StartClickTool(PathPointType.WayPoint, enableRailSnapping: false, enableHoistingMode: true); LogManager.Info($"已启动吊装路径点击工具: {newRoute.Name}"); UpdateMainStatus($"已进入新建吊装路径模式: {newRoute.Name} - 请在3D视图中点击吊装起点(地面位置)"); LogManager.Info($"开始新建吊装路径: {newRoute.Name},已强制重新初始化ToolPlugin获取鼠标焦点"); } else { UpdateMainStatus("创建新吊装路径失败:没有可通行的物流模型"); LogManager.Error("创建新吊装路径失败:没有可通行的物流模型"); MessageBox.Show("创建新吊装路径失败:没有找到任何可通行的物流模型。\n请先为模型设置可通行的物流属性,然后再尝试创建路径。", "错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } else { UpdateMainStatus("路径规划管理器未初始化"); LogManager.Error("路径规划管理器未初始化"); } }, "新建吊装路径"); } #region 多层吊装页签模式实现 /// /// 执行新建多层阶梯式吊装路径命令 - 启用页签模式 /// private async Task ExecuteNewMultiLevelHoistingPathAsync() { await SafeExecuteAsync(() => { UpdateMainStatus("正在创建多层阶梯式吊装路径..."); // 清除所有现有路径的可视化显示 if (PathPointRenderPlugin.Instance != null) { try { PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); LogManager.Info("新建多层吊装路径:已清除现有路径可视化显示(保留网格可视化)"); } catch (Exception ex) { LogManager.Error($"新建多层吊装路径:清除现有路径可视化失败: {ex.Message}", ex); throw; } } if (_pathPlanningManager == null) { UpdateMainStatus("路径规划管理器未初始化"); LogManager.Error("路径规划管理器未初始化"); return; } // 启用多层吊装页签模式 IsMultiLevelHoistingMode = true; // 清空集合和重置状态 MultiLevelItems.Clear(); _multiLevelStartPoint = null; _multiLevelEndPoint = null; _hasMultiLevelStartPoint = false; _hasMultiLevelEndPoint = false; MultiLevelStartPointText = "未选择"; MultiLevelEndPointText = "未选择"; UpdateMultiLevelStats(); // 重置选择状态 _isSelectingMultiLevelStartPoint = false; _isSelectingMultiLevelEndPoint = false; _currentSelectingLevelTarget = null; // 清理之前的点击事件订阅 PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; UpdateMainStatus("多层吊装模式已启用 - 请设置起点、终点并添加层级"); LogManager.Info("[多层吊装] 页签模式已启用"); }, "新建多层吊装路径"); } /// /// 执行选择多层吊装起点命令 /// private async Task ExecuteSelectMultiLevelStartPointAsync() { await SafeExecuteAsync(() => { if (_pathPlanningManager == null) return; // 清除之前的起点标记 if (_hasMultiLevelStartPoint && PathPointRenderPlugin.Instance != null) { var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelStartPoint"); if (existingRoute != null) { PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id); } } // 使用自动路径规划相同的模式 _pathPlanningManager.DisableMouseHandling(); // 设置状态 _isSelectingMultiLevelStartPoint = true; _isSelectingMultiLevelEndPoint = false; _currentSelectingLevelTarget = null; // 订阅点击事件 PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; PathClickToolPlugin.MouseClicked += OnMultiLevelHoistingMouseClicked; // 强制重新初始化ToolPlugin以获取鼠标焦点 ForceReinitializeToolPlugin(subscribeToEvents: false); UpdateMainStatus("请在3D视图中点击选择起点(地面起吊位置)..."); LogManager.Info("[多层吊装] 开始选择起点"); }, "选择多层吊装起点"); } /// /// 执行选择多层吊装终点命令 /// private async Task ExecuteSelectMultiLevelEndPointAsync() { await SafeExecuteAsync(() => { if (_pathPlanningManager == null) return; // 清除之前的终点标记 if (_hasMultiLevelEndPoint && PathPointRenderPlugin.Instance != null) { var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelEndPoint"); if (existingRoute != null) { PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id); } } // 使用自动路径规划相同的模式 _pathPlanningManager.DisableMouseHandling(); // 设置状态 _isSelectingMultiLevelStartPoint = false; _isSelectingMultiLevelEndPoint = true; _currentSelectingLevelTarget = null; // 订阅点击事件 PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; PathClickToolPlugin.MouseClicked += OnMultiLevelHoistingMouseClicked; // 强制重新初始化ToolPlugin以获取鼠标焦点 ForceReinitializeToolPlugin(subscribeToEvents: false); UpdateMainStatus("请在3D视图中点击选择终点(地面落地位置)..."); LogManager.Info("[多层吊装] 开始选择终点"); }, "选择多层吊装终点"); } /// /// 执行添加层级命令 /// private void ExecuteAddMultiLevel() { var level = new HoistingLevelItem { Index = MultiLevelItems.Count, HeightInMeters = 3.0, Description = $"层级{MultiLevelItems.Count + 1}" }; MultiLevelItems.Add(level); UpdateMultiLevelStats(); LogManager.Info($"[多层吊装] 添加层级{level.Index + 1}, 高度={level.HeightInMeters}m"); UpdateMainStatus($"已添加层级{level.Index + 1},请设置目标位置"); } /// /// 执行删除层级命令 /// private void ExecuteDeleteMultiLevel(HoistingLevelItem item) { if (item == null) return; // 清除该层级的可视化标记 if (PathPointRenderPlugin.Instance != null) { var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == $"MultiLevelLevel{item.Index}"); if (existingRoute != null) { PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id); } } MultiLevelItems.Remove(item); // 重新编号 for (int i = 0; i < MultiLevelItems.Count; i++) { MultiLevelItems[i].Index = i; } UpdateMultiLevelStats(); LogManager.Info($"[多层吊装] 删除层级,剩余{MultiLevelItems.Count}个层级"); } /// /// 执行拾取层级目标位置命令 /// private void ExecutePickMultiLevelTarget(HoistingLevelItem item) { if (item == null || _pathPlanningManager == null) return; // 清除之前的标记 if (PathPointRenderPlugin.Instance != null) { var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == $"MultiLevelLevel{item.Index}"); if (existingRoute != null) { PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id); } } // 使用自动路径规划相同的模式 _pathPlanningManager.DisableMouseHandling(); // 设置状态 _isSelectingMultiLevelStartPoint = false; _isSelectingMultiLevelEndPoint = false; _currentSelectingLevelTarget = item; // 订阅点击事件 PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; PathClickToolPlugin.MouseClicked += OnMultiLevelHoistingMouseClicked; // 强制重新初始化ToolPlugin以获取鼠标焦点 ForceReinitializeToolPlugin(subscribeToEvents: false); UpdateMainStatus($"请在3D视图中点击层级{item.Index + 1}的目标位置..."); LogManager.Info($"[多层吊装] 开始选择层级{item.Index + 1}的目标位置"); } /// /// 执行创建多层吊装路径命令 /// private async Task ExecuteCreateMultiLevelPathAsync() { await SafeExecuteAsync(() => { // 验证输入 if (!_hasMultiLevelStartPoint) { MessageBox.Show("请先选择起点", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (!_hasMultiLevelEndPoint) { MessageBox.Show("请先选择终点", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (MultiLevelItems.Count == 0) { MessageBox.Show("请至少添加一个层级", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // 验证所有层级 foreach (var level in MultiLevelItems) { if (level.HeightInMeters <= 0) { MessageBox.Show($"层级{level.Index + 1}的高度必须大于0", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (level.HorizontalTarget == null) { MessageBox.Show($"请为层级{level.Index + 1}设置目标位置", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning); return; } } // 创建层级列表 var levels = new List(); foreach (var item in MultiLevelItems) { levels.Add(new HoistingLevel { Index = item.Index, HeightInMeters = item.HeightInMeters, HorizontalTarget = item.HorizontalTarget, IsRise = true, Description = item.Description }); } LogManager.Info($"[多层吊装] 创建路径:起点({_multiLevelStartPoint.X:F2},{_multiLevelStartPoint.Y:F2},{_multiLevelStartPoint.Z:F2}), " + $"终点({_multiLevelEndPoint.X:F2},{_multiLevelEndPoint.Y:F2},{_multiLevelEndPoint.Z:F2}), 层级数={levels.Count}"); // 使用命令创建路径 var parameters = new AerialPathParameters { PathType = PathType.Hoisting, HoistingMode = HoistingMode.MultiLevel, StartPoint = _multiLevelStartPoint, EndPoint = _multiLevelEndPoint, HoistingLevels = levels }; var command = new CreateAerialPathCommand(parameters); var result = command.ExecuteAsync().Result; if (result.IsSuccess && result is PathPlanningResult typedResult) { var route = typedResult.Data; LogManager.Info($"[多层吊装] 路径创建成功: {route.Name}, 点数: {route.Points.Count}"); // 创建 ViewModel var newPathViewModel = new PathRouteViewModel { Route = route, IsActive = true, PathType = PathType.Hoisting }; var pathPoints = CreatePathPointViewModelsFromCoreRoute(route); foreach (var point in pathPoints) { newPathViewModel.Points.Add(point); } PathRoutes.Add(newPathViewModel); SelectedPathRoute = newPathViewModel; UpdateMainStatus($"✓ 多层吊装路径创建成功: {route.Name} - {levels.Count}个层级, {route.Points.Count}个路径点"); OnPropertyChanged(nameof(PathRoutes)); // 退出多层吊装模式 ExecuteCancelMultiLevelHoisting(); } else { UpdateMainStatus($"创建失败: {result.Message}"); MessageBox.Show($"创建多层吊装路径失败: {result.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } }, "创建多层吊装路径"); } /// /// 执行取消多层吊装命令 /// private void ExecuteCancelMultiLevelHoisting() { // 清理事件订阅 PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; // 清理可视化标记 if (PathPointRenderPlugin.Instance != null) { // 清理起点标记 var startRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelStartPoint"); if (startRoute != null) { PathPointRenderPlugin.Instance.RemovePath(startRoute.Route.Id); } // 清理终点标记 var endRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == "MultiLevelEndPoint"); if (endRoute != null) { PathPointRenderPlugin.Instance.RemovePath(endRoute.Route.Id); } // 清理层级标记 for (int i = 0; i < MultiLevelItems.Count; i++) { var levelRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == $"MultiLevelLevel{i}"); if (levelRoute != null) { PathPointRenderPlugin.Instance.RemovePath(levelRoute.Route.Id); } } } // 重置状态 IsMultiLevelHoistingMode = false; MultiLevelItems.Clear(); _multiLevelStartPoint = null; _multiLevelEndPoint = null; _hasMultiLevelStartPoint = false; _hasMultiLevelEndPoint = false; MultiLevelStartPointText = "未选择"; MultiLevelEndPointText = "未选择"; _isSelectingMultiLevelStartPoint = false; _isSelectingMultiLevelEndPoint = false; _currentSelectingLevelTarget = null; // 恢复PathPlanningManager鼠标处理 _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); UpdateMultiLevelStats(); UpdateMainStatus("多层吊装模式已取消"); LogManager.Info("[多层吊装] 页签模式已取消"); } /// /// 更新多层吊装统计信息 /// private void UpdateMultiLevelStats() { int levelCount = MultiLevelItems.Count; int pointCount = levelCount > 0 ? 2 + levelCount * 2 + 2 : 0; MultiLevelStatsText = $"层级数: {levelCount} | 预估路径点数: {pointCount}"; // 通知CanCreateMultiLevelPath变更 OnPropertyChanged(nameof(CanCreateMultiLevelPath)); // 通知命令可执行状态变更 System.Windows.Input.CommandManager.InvalidateRequerySuggested(); } /// /// 多层吊装路径鼠标点击处理 /// private void OnMultiLevelHoistingMouseClicked(object sender, PickItemResult e) { if (e?.Point == null) return; var point = e.Point; if (_isSelectingMultiLevelStartPoint) { // 设置起点 _multiLevelStartPoint = point; _hasMultiLevelStartPoint = true; MultiLevelStartPointText = $"({point.X:F2}, {point.Y:F2}, {point.Z:F2})"; // 渲染起点标记 RenderMultiLevelPointMarker(point, isStart: true); LogManager.Info($"[多层吊装] 起点已设置: ({point.X:F2}, {point.Y:F2}, {point.Z:F2})"); UpdateMainStatus($"起点已设置,请选择终点或添加层级"); // 清理事件订阅并停用ToolPlugin PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); _isSelectingMultiLevelStartPoint = false; // 更新统计 UpdateMultiLevelStats(); } else if (_isSelectingMultiLevelEndPoint) { // 设置终点 _multiLevelEndPoint = point; _hasMultiLevelEndPoint = true; MultiLevelEndPointText = $"({point.X:F2}, {point.Y:F2}, {point.Z:F2})"; // 渲染终点标记 RenderMultiLevelPointMarker(point, isStart: false); LogManager.Info($"[多层吊装] 终点已设置: ({point.X:F2}, {point.Y:F2}, {point.Z:F2})"); UpdateMainStatus($"终点已设置,可以添加层级创建路径"); // 清理事件订阅并停用ToolPlugin PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); _isSelectingMultiLevelEndPoint = false; // 更新统计 UpdateMultiLevelStats(); } else if (_currentSelectingLevelTarget != null) { // 设置层级目标位置 var targetPoint = new Point3D(point.X, point.Y, point.Z); _currentSelectingLevelTarget.HorizontalTarget = targetPoint; // 渲染层级目标标记 RenderMultiLevelLevelMarker(targetPoint, _currentSelectingLevelTarget.Index); LogManager.Info($"[多层吊装] 层级{_currentSelectingLevelTarget.Index + 1}目标位置已设置: ({point.X:F2}, {point.Y:F2})"); UpdateMainStatus($"层级{_currentSelectingLevelTarget.Index + 1}目标位置已设置,可以继续添加层级或点击创建路径"); // 清理事件订阅并停用ToolPlugin PathClickToolPlugin.MouseClicked -= OnMultiLevelHoistingMouseClicked; _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); _currentSelectingLevelTarget = null; } } /// /// 渲染层级目标位置标记 /// private void RenderMultiLevelLevelMarker(Point3D point, int levelIndex) { if (PathPointRenderPlugin.Instance == null) return; var routeName = $"MultiLevelLevel{levelIndex}"; // 移除之前的标记 var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == routeName); if (existingRoute != null) { PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id); } // 创建临时路径 var route = new PathRoute(routeName) { Description = $"层级{levelIndex + 1}目标位置" }; var pathPoint = new PathPoint { Name = $"层级{levelIndex + 1}", Position = point, Type = PathPointType.WayPoint }; route.AddPoint(pathPoint); // 渲染点(使用不同颜色区分层级) PathPointRenderPlugin.Instance.RenderPointOnly(route); LogManager.Info($"[多层吊装] 层级{levelIndex + 1}目标标记已渲染"); } /// /// 渲染多层吊装路径的点标记 /// private void RenderMultiLevelPointMarker(Point3D point, bool isStart) { if (PathPointRenderPlugin.Instance == null) return; var routeName = isStart ? "MultiLevelStartPoint" : "MultiLevelEndPoint"; var displayName = isStart ? "多层吊装起点" : "多层吊装终点"; var pointType = isStart ? PathPointType.StartPoint : PathPointType.EndPoint; // 移除之前的标记 var existingRoute = PathRoutes.FirstOrDefault(r => r.Route.Name == routeName); if (existingRoute != null) { PathPointRenderPlugin.Instance.RemovePath(existingRoute.Route.Id); } // 创建临时路径 var route = new PathRoute(routeName) { Description = displayName }; var pathPoint = new PathPoint { Name = displayName, Position = point, Type = pointType }; route.AddPoint(pathPoint); // 渲染点 PathPointRenderPlugin.Instance.RenderPointOnly(route); } #endregion /// /// 提取并渲染所有空轨模型的基准路径 /// private void ExtractAndRenderRailBaselinePaths() { try { LogManager.Info("[空轨路径] 开始提取所有空轨模型的基准路径"); int successCount = PathPlanning.RailGeometryHelper.ExtractAndRenderAllRailBaselines(); LogManager.Info($"[空轨路径] 空轨基准路径提取完成,成功 {successCount} 个"); } catch (Exception ex) { LogManager.Error($"[空轨路径] 提取空轨基准路径失败: {ex.Message}"); } } private async Task ExecuteDeletePathAsync() { if (SelectedPathRoute == null) return; await SafeExecuteAsync(() => { var pathName = SelectedPathRoute.Name; UpdateMainStatus($"正在删除路径: {pathName}..."); // 清理当前路径的3D可视化显示(使用新的统一API) if (PathPointRenderPlugin.Instance != null) { try { // 使用路径名找到对应的Core路径,获取其ID进行精确清理 string pathIdToRemove = null; if (_pathPlanningManager != null) { var coreRoute = _pathPlanningManager.Routes.FirstOrDefault(r => r.Name == pathName); if (coreRoute != null) { pathIdToRemove = coreRoute.Id; LogManager.Info($"删除路径:找到Core路径ID: {pathIdToRemove}"); // 使用新的API精确删除该路径 PathPointRenderPlugin.Instance.RemovePath(pathIdToRemove); LogManager.Info($"删除路径:已清除路径 {pathName} (ID: {pathIdToRemove}) 的可视化显示"); } else { LogManager.Warning($"删除路径:未找到路径 {pathName} 对应的Core路径,无法清理可视化"); } } } catch (Exception ex) { LogManager.Error($"删除路径:清除路径可视化失败: {ex.Message}", ex); throw; // 重新抛出异常,让上层处理 } } // 通知PathPlanningManager删除对应的路径(包括数据库删除) if (_pathPlanningManager != null) { var coreRoute = _pathPlanningManager.Routes.FirstOrDefault(r => r.Name == pathName); if (coreRoute != null) { // 使用PathPlanningManager的DeleteRoute方法,确保数据库也被删除 bool deleteSuccess = _pathPlanningManager.DeleteRoute(coreRoute); if (deleteSuccess) { LogManager.Info($"删除路径:已从PathPlanningManager和数据库删除路径: {pathName}"); } else { LogManager.Warning($"删除路径:PathPlanningManager删除失败: {pathName}"); } } } // 更新UI PathRoutes.Remove(SelectedPathRoute); SelectedPathRoute = null; UpdateMainStatus($"已完全删除路径: {pathName}"); }, "删除路径"); } private async Task ExecuteDuplicatePathAsync() { if (SelectedPathRoute == null || _pathPlanningManager == null) return; await SafeExecuteAsync(async () => { var sourceRoute = SelectedPathRoute.Route ?? _pathPlanningManager.Routes.FirstOrDefault(r => r.Id == SelectedPathRoute.Id); if (sourceRoute == null) { throw new InvalidOperationException("未找到要复制的路径数据"); } UpdateMainStatus($"正在复制路径: {sourceRoute.Name}..."); var duplicatedRoute = sourceRoute.Clone(); duplicatedRoute.Name = PathHelper.BuildDuplicatedPathName(sourceRoute.Name); duplicatedRoute.CreatedTime = DateTime.Now; duplicatedRoute.LastModified = DateTime.Now; if (!_pathPlanningManager.AddRoute(duplicatedRoute)) { throw new InvalidOperationException($"复制路径失败: {sourceRoute.Name}"); } var duplicatedPathViewModel = new PathRouteViewModel(isFromDatabase: true) { Route = duplicatedRoute, IsActive = false }; duplicatedPathViewModel.SetTimeInfo(duplicatedRoute.CreatedTime, duplicatedRoute.LastModified); foreach (var point in CreatePathPointViewModelsFromCoreRoute(duplicatedRoute)) { duplicatedPathViewModel.Points.Add(point); } PathRoutes.Add(duplicatedPathViewModel); SelectedPathRoute = duplicatedPathViewModel; UpdateMainStatus($"已复制路径: {sourceRoute.Name} -> {duplicatedPathViewModel.Name}"); await Task.CompletedTask; }, "复制路径"); } #endregion #region 坐标编辑命令 private void ExecuteEditPointCoordinates(PathPointViewModel point) { if (point == null) return; try { // 创建并显示对话框 // 注意:在Navisworks插件的ViewModel中,不设置Owner // 因为Application.Current.MainWindow在Navisworks环境中不可用 var dialog = new EditCoordinatesWindow(point.X, point.Y, point.Z, point.Name, point.Type.ToString()); if (dialog.ShowDialog() == true) { // 同步更新到底层数据模型 if (_pathPlanningManager != null) { var coreRoute = _pathPlanningManager.Routes.FirstOrDefault(r => r.Id == SelectedPathRoute.Id); if (coreRoute != null) { // 🔥 通过Id查找路径点索引(而不是使用可能过时的point.Index) int pointIndex = -1; for (int i = 0; i < coreRoute.Points.Count; i++) { if (coreRoute.Points[i].Id == point.Id) { pointIndex = i; break; } } // 验证索引是否有效 if (pointIndex < 0 || pointIndex >= coreRoute.Points.Count) { LogManager.Error($"[坐标编辑] 索引错误: 通过Id查找失败,point.Id={point.Id}, coreRoute.Points.Count={coreRoute.Points.Count}"); System.Windows.MessageBox.Show($"路径点索引错误:无法找到对应的路径点,请刷新路径列表后重试", "错误", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return; } LogManager.Info($"[坐标编辑] 修改点: {point.Name}, 类型: {point.Type}, 索引: {pointIndex}"); var corePoint = coreRoute.Points[pointIndex]; LogManager.Info($"[坐标编辑] Core路径点: {corePoint.Name}, 索引: {pointIndex}, 位置: ({corePoint.Position.X:F3}, {corePoint.Position.Y:F3}, {corePoint.Position.Z:F3})"); // 使用公共方法更新路径点(自动处理吊装路径约束) var newPosition = new Point3D(dialog.X, dialog.Y, dialog.Z); bool success = _pathPlanningManager.UpdatePathPointWithConstraints(coreRoute, pointIndex, newPosition); if (success) { // 更新ViewModel中的坐标(从Core数据同步) corePoint = coreRoute.Points[pointIndex]; point.X = corePoint.Position.X; point.Y = corePoint.Position.Y; point.Z = corePoint.Position.Z; // 刷新视图 UpdatePathVisualization(); LogManager.Info($"已更新路径点 {point.Name} 的坐标为 ({point.X:F3}, {point.Y:F3}, {point.Z:F3})"); } else { LogManager.Error($"更新路径点失败: {point.Name}"); System.Windows.MessageBox.Show($"更新路径点失败", "错误", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } } } } catch (Exception ex) { LogManager.Error($"编辑坐标对话框操作失败: {ex.Message}", ex); System.Windows.MessageBox.Show($"无法打开编辑对话框: {ex.Message}", "错误", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } /// /// 从Core路径创建PathPointViewModel列表(统一处理吊装路径属性) /// private List CreatePathPointViewModelsFromCoreRoute(PathRoute coreRoute) { var points = new List(); for (int i = 0; i < coreRoute.Points.Count; i++) { var corePoint = coreRoute.Points[i]; var wpfPoint = new PathPointViewModel { Id = corePoint.Id, Name = corePoint.Name, X = corePoint.X, Y = corePoint.Y, Z = corePoint.Z, Type = corePoint.Type, Index = i // 🔥 设置正确的索引 }; points.Add(wpfPoint); } return points; } private bool SyncPathViewModelFromCoreRoute(PathRouteViewModel pathViewModel, PathRoute coreRoute, bool preserveSelection = true) { if (pathViewModel == null || coreRoute == null) { return false; } bool needsUpdate = !ReferenceEquals(pathViewModel.Route, coreRoute) || pathViewModel.Points.Count != coreRoute.Points.Count; if (!needsUpdate && pathViewModel.Points.Count == coreRoute.Points.Count) { for (int i = 0; i < pathViewModel.Points.Count; i++) { var uiPoint = pathViewModel.Points[i]; var corePoint = coreRoute.Points[i]; if (uiPoint.Id != corePoint.Id || uiPoint.Name != corePoint.Name || uiPoint.Type != corePoint.Type) { needsUpdate = true; break; } const double tolerance = 1e-6; if (Math.Abs(uiPoint.X - corePoint.Position.X) > tolerance || Math.Abs(uiPoint.Y - corePoint.Position.Y) > tolerance || Math.Abs(uiPoint.Z - corePoint.Position.Z) > tolerance) { needsUpdate = true; break; } } } pathViewModel.Route = coreRoute; pathViewModel.SetTimeInfo(coreRoute.CreatedTime, coreRoute.LastModified); if (!needsUpdate) { pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.TotalLength)); pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.LastModifiedTime)); return false; } string selectedPointId = preserveSelection ? SelectedPathPoint?.Id : null; bool hadSelection = !string.IsNullOrEmpty(selectedPointId); PathPointViewModel newSelectedPoint = null; pathViewModel.Points.Clear(); foreach (var pointViewModel in CreatePathPointViewModelsFromCoreRoute(coreRoute)) { pathViewModel.Points.Add(pointViewModel); if (hadSelection && pointViewModel.Id == selectedPointId) { newSelectedPoint = pointViewModel; } } pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.PointCount)); pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.TotalLength)); pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.SummaryInfo)); pathViewModel.NotifyPropertyChanged(nameof(PathRouteViewModel.LastModifiedTime)); if (hadSelection) { SelectedPathPoint = newSelectedPoint; } return true; } /// /// 更新吊装路径的关联点(UI层) /// /// 路径 /// 修改的点的索引 private void UpdateAerialPathRelatedPoints(PathRouteViewModel pathRoute, int modifiedPointIndex) { if (pathRoute.PathType != NavisworksTransport.PathType.Hoisting || pathRoute.Points.Count != 4) { return; } var points = pathRoute.Points; // 修改起点(索引0):更新提升点(索引1)的X,Y坐标 if (modifiedPointIndex == 0) { var startPoint = points[0]; var liftPoint = points[1]; liftPoint.X = startPoint.X; liftPoint.Y = startPoint.Y; } // 修改终点(索引3):更新平移终点(索引2)的X,Y坐标 else if (modifiedPointIndex == 3) { var endPoint = points[3]; var moveEndPoint = points[2]; moveEndPoint.X = endPoint.X; moveEndPoint.Y = endPoint.Y; } } /// /// 更新吊装路径的关联点(Core层) /// /// Core路径 /// 修改的点的索引 private void UpdateAerialPathRelatedPointsCore(PathRoute coreRoute, int modifiedPointIndex) { if (coreRoute.PathType != NavisworksTransport.PathType.Hoisting || coreRoute.Points.Count != 4) { return; } var points = coreRoute.Points; // 修改起点(索引0):更新提升点(索引1)的X,Y坐标 if (modifiedPointIndex == 0) { var startPoint = points[0]; var liftPoint = points[1]; liftPoint.X = startPoint.X; liftPoint.Y = startPoint.Y; } // 修改终点(索引3):更新平移终点(索引2)的X,Y坐标 else if (modifiedPointIndex == 3) { var endPoint = points[3]; var moveEndPoint = points[2]; moveEndPoint.X = endPoint.X; moveEndPoint.Y = endPoint.Y; } } #endregion #region 自动路径规划命令 private async Task ExecuteSelectStartPointAsync() { await SafeExecuteAsync(() => { LogManager.Info("=== 开始执行选择起点命令 ==="); if (_pathPlanningManager == null) { LogManager.Error("PathPlanningManager为null,无法选择起点"); UpdateMainStatus("路径规划管理器未初始化"); return; } // 立即清理现有的起点标记(使用正确的路径ID) if (PathPointRenderPlugin.Instance != null && _autoPathStartPointRoute != null) { PathPointRenderPlugin.Instance.RemovePath(_autoPathStartPointRoute.Id); _autoPathStartPointRoute = null; // 清除引用 } // 重置状态 _hasStartPoint = false; AutoPathStartPoint = "未选择"; LogManager.Info("PathPlanningManager已初始化,开始设置自动路径规划状态"); IsSelectingStartPoint = true; IsSelectingEndPoint = false; // 通知按钮状态更新,禁用"手动创建"按钮 OnPropertyChanged(nameof(CanExecuteNewPath)); UpdateMainStatus("请在3D视图中点击选择起点..."); // 进入自动路径规划模式 - 动态事件订阅 LogManager.Info("进入自动路径规划模式,启用动态事件订阅"); // 1. 禁用PathPlanningManager的鼠标处理 _pathPlanningManager.DisableMouseHandling(); // 2. 启用PathEditingViewModel的事件订阅 PathClickToolPlugin.MouseClicked -= OnAutoPathMouseClicked; // 先取消避免重复 PathClickToolPlugin.MouseClicked += OnAutoPathMouseClicked; LogManager.Info("已启用PathEditingViewModel的鼠标事件订阅"); // 强制重新初始化ToolPlugin以确保获得鼠标焦点,自动路径不需要PathPlanningManager订阅事件 if (!ForceReinitializeToolPlugin(subscribeToEvents: false)) { UpdateMainStatus("ToolPlugin初始化失败,请重试"); return; } // 在STA线程中调用Navisworks API _pathPlanningManager.StartClickTool(PathPointType.StartPoint); LogManager.Info("=== 选择起点命令设置完成,已启用动态事件订阅,等待用户点击 ==="); }, "选择起点"); } private async Task ExecuteSelectEndPointAsync() { await SafeExecuteAsync(() => { LogManager.Info("=== 开始执行选择终点命令 ==="); if (_pathPlanningManager == null) { LogManager.Error("PathPlanningManager为null,无法选择终点"); UpdateMainStatus("路径规划管理器未初始化"); return; } // 立即清理现有的终点标记(使用正确的路径ID) if (PathPointRenderPlugin.Instance != null && _autoPathEndPointRoute != null) { PathPointRenderPlugin.Instance.RemovePath(_autoPathEndPointRoute.Id); LogManager.Debug($"[选择终点] 已清除之前的终点标记,ID: {_autoPathEndPointRoute.Id}"); _autoPathEndPointRoute = null; // 清除引用 } // 重置状态 _hasEndPoint = false; AutoPathEndPoint = "未选择"; LogManager.Info("PathPlanningManager已初始化,开始设置自动路径规划状态"); IsSelectingStartPoint = false; IsSelectingEndPoint = true; // 通知按钮状态更新,禁用"手动创建"按钮 OnPropertyChanged(nameof(CanExecuteNewPath)); UpdateMainStatus("请在3D视图中点击选择终点..."); // 进入自动路径规划模式 - 动态事件订阅 LogManager.Info("进入自动路径规划模式,启用动态事件订阅"); // 1. 禁用PathPlanningManager的鼠标处理 _pathPlanningManager.DisableMouseHandling(); // 2. 启用PathEditingViewModel的事件订阅 PathClickToolPlugin.MouseClicked -= OnAutoPathMouseClicked; // 先取消避免重复 PathClickToolPlugin.MouseClicked += OnAutoPathMouseClicked; LogManager.Info("已启用PathEditingViewModel的鼠标事件订阅"); // 强制重新初始化ToolPlugin以确保获得鼠标焦点,自动路径不需要PathPlanningManager订阅事件 if (!ForceReinitializeToolPlugin(subscribeToEvents: false)) { UpdateMainStatus("ToolPlugin初始化失败,请重试"); return; } // 在STA线程中调用Navisworks API _pathPlanningManager.StartClickTool(PathPointType.EndPoint); LogManager.Info("=== 选择终点命令设置完成,已启用动态事件订阅,等待用户点击 ==="); }, "选择终点"); } private async Task ExecuteAutoPlanPathAsync() { await SafeExecuteAsync(async () => { // 保存当前光标 var oldCursor = Mouse.OverrideCursor; try { if (!_hasStartPoint || !_hasEndPoint) { UpdateMainStatus("请先选择起点和终点"); return; } // 设置等待光标 Mouse.OverrideCursor = Cursors.Wait; UpdateMainStatus("正在计算最优路径...", -1, true); LogManager.Info("=== 开始执行自动路径规划 ==="); // 确保在开始自动路径规划前清理任何残留的事件订阅 CleanupAutoPathEventSubscriptions(); if (_pathPlanningManager != null) { _pathPlanningManager.StopClickTool(); } // 清理所有临时路径,确保干净的起始状态 ClearTemporaryAutoPathMarkers(); if (PathPointRenderPlugin.Instance != null) { // 检查是否有网格可视化启用,只在启用时保留网格可视化路径 if (_pathPlanningManager?.IsAnyGridVisualizationEnabled == true) { PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); } else { PathPointRenderPlugin.Instance.ClearPathsExcept(); // 清理所有路径,不保留任何内容 } } // 调用PathPlanningManager的自动路径规划功能 var startPathPoint = new PathPoint { Name = "自动起点", Position = _startPoint3D, Type = PathPointType.StartPoint }; var endPathPoint = new PathPoint { Name = "自动终点", Position = _endPoint3D, Type = PathPointType.EndPoint }; LogManager.Info($"起点: ({_startPoint3D.X:F2}, {_startPoint3D.Y:F2}, {_startPoint3D.Z:F2})"); LogManager.Info($"终点: ({_endPoint3D.X:F2}, {_endPoint3D.Y:F2}, {_endPoint3D.Z:F2})"); // 计算物体半径:基于长度和宽度的较大值的一半,高度暂时不参与半径计算 var objectRadius = Math.Max(ObjectLength, ObjectWidth) / 2.0; LogManager.Info($"物体参数: 长{ObjectLength:F1}m × 宽{ObjectWidth:F1}m × 高{ObjectHeight:F1}m"); LogManager.Info($"计算得出物体半径: {objectRadius:F2}m (基于长宽较大值的一半)"); // 确定网格大小:如果启用手动设置则使用用户设置的值,否则使用-1(自动选择) var gridSize = IsGridSizeManuallyEnabled ? GridSize : -1; // 获取选择的路径策略,如果未选择则默认使用最短路径 var selectedStrategy = SelectedPathStrategy?.Value ?? PathStrategy.Shortest; LogManager.Info($"网格大小设置: {(IsGridSizeManuallyEnabled ? $"手动设置 {GridSize:F1}米" : "自动选择")}"); LogManager.Info($"路径策略: {SelectedPathStrategy?.DisplayName ?? "最短路径"}"); // 在STA线程中执行Navisworks API调用 var pathRoute = await _pathPlanningManager?.AutoPlanPath(startPathPoint, endPathPoint, objectRadius, SafetyMargin, gridSize, ObjectHeight, selectedStrategy); if (pathRoute != null && pathRoute.Points.Count > 0) { LogManager.Info($"路径规划成功,共 {pathRoute.Points.Count} 个点"); // 自动路径规划完成后,执行完整的事件订阅清理 CleanupAutoPathEventSubscriptions(); if (_pathPlanningManager != null) { _pathPlanningManager.StopClickTool(); } // 清理临时的起点和终点标记 ClearTemporaryAutoPathMarkers(); // 不在这里直接操作UI,让RouteGenerated事件处理UI更新 // 这避免了重复的路径添加和UI更新冲突 // 不在这里设置状态,让RouteGenerated事件处理UI更新 LogManager.Info($"✅ 自动路径规划完成,RouteGenerated事件将处理UI更新"); } else { // 这里的逻辑已经在上方处理过了 // 失败情况下也要清理事件订阅和临时标记 CleanupAutoPathEventSubscriptions(); if (_pathPlanningManager != null) { _pathPlanningManager.StopClickTool(); } // 清理临时的起点和终点标记 ClearTemporaryAutoPathMarkers(); // 确保失败后也清理所有可能的残留路径对象 if (PathPointRenderPlugin.Instance != null) { // 检查是否有网格可视化启用,只在启用时保留网格可视化路径 if (_pathPlanningManager?.IsAnyGridVisualizationEnabled == true) { PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); } else { PathPointRenderPlugin.Instance.ClearPathsExcept(); // 清理所有路径,不保留任何内容 } } } } finally { // 恢复光标 Mouse.OverrideCursor = oldCursor; } }, "自动路径规划"); } private async Task ExecuteClearAutoPathAsync() { // 使用基类的SafeExecuteAsync方法,确保在STA线程上执行 await SafeExecuteAsync(() => { LogManager.Info("=== 开始重置自动路径规划状态 ==="); // 停止任何正在进行的点选择 if (_pathPlanningManager != null) { _pathPlanningManager.StopClickTool(); PathClickToolPlugin.MouseClicked -= OnAutoPathMouseClicked; } // 清除所有路径的可视化显示(与新建路径按钮保持一致) if (PathPointRenderPlugin.Instance != null) { try { // 清除路径可视化,但保留网格可视化 PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door"); LogManager.Info("重置参数:已清除路径可视化显示(保留网格可视化)"); } catch (Exception ex) { LogManager.Error($"重置参数:清除路径可视化失败: {ex.Message}", ex); // 不抛出异常,继续执行状态重置 } } // 强制重新初始化ToolPlugin以重新获得鼠标焦点,重置时不需要订阅事件 if (_pathPlanningManager != null) { ForceReinitializeToolPlugin(subscribeToEvents: false); } // 重置所有状态 _hasStartPoint = false; _hasEndPoint = false; _startPoint3D = new Point3D(); _endPoint3D = new Point3D(); AutoPathStartPoint = "未选择"; AutoPathEndPoint = "未选择"; UpdateMainStatus("就绪"); IsSelectingStartPoint = false; IsSelectingEndPoint = false; // 清除路径引用 _autoPathStartPointRoute = null; _autoPathEndPointRoute = null; // 通知Can Execute属性更改 OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); OnPropertyChanged(nameof(CanExecuteNewPath)); LogManager.Info("自动路径规划参数和起终点标记已完全重置,ToolPlugin已重新初始化"); }, "重置自动路径规划"); } #endregion #region 路径编辑命令 - 待实现 private async Task ExecuteRenamePathAsync() { if (SelectedPathRoute == null) return; await SafeExecuteAsync(async () => { await _uiStateManager.ExecuteUIUpdateAsync(() => { // TODO: 实现重命名对话框 var newName = $"路径 {PathRoutes.Count}"; var oldName = SelectedPathRoute.Name; SelectedPathRoute.Name = newName; UpdateMainStatus($"路径已重命名: {oldName} -> {newName}"); }); }, "重命名路径"); } private async Task ExecuteAddPathPointAsync() { if (!CanExecuteStartEdit) return; await SafeExecuteAsync(async () => { await _uiStateManager.ExecuteUIUpdateAsync(() => { IsPathEditMode = true; if (SelectedPathRoute != null) { SelectedPathRoute.IsActive = true; UpdateMainStatus($"正在激活3D路径编辑模式: {SelectedPathRoute.Name}..."); // 检查是否是空轨路径 bool isRailPath = SelectedPathRoute.PathType == PathType.Rail; // 启动PathPlanningManager的点击工具,这会设置正确的编辑状态 if (_pathPlanningManager != null) { try { if (isRailPath) { _pathPlanningManager.StartClickTool(PathPointType.WayPoint, enableRailSnapping: true); LogManager.Info($"已启动PathPlanningManager点击工具(空轨吸附模式): {SelectedPathRoute.Name}"); } else { _pathPlanningManager.StartClickTool(PathPointType.WayPoint); LogManager.Info($"已启动PathPlanningManager点击工具: {SelectedPathRoute.Name}"); } } catch (Exception ex) { LogManager.Error($"启动点击工具失败: {ex.Message}"); UpdateMainStatus("启动3D编辑工具失败,请重试"); IsPathEditMode = false; return; } } else { UpdateMainStatus("路径规划管理器未初始化"); LogManager.Error("添加路径点:PathPlanningManager未初始化"); IsPathEditMode = false; return; } string modeText = isRailPath ? "(空轨吸附模式)" : ""; UpdateMainStatus($"已进入3D路径编辑模式: {SelectedPathRoute.Name} {modeText} - 在3D视图中点击设置路径点"); LogManager.Info($"开始添加路径点: {SelectedPathRoute.Name},已启动点击工具"); // 手动触发按钮状态更新 OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); } }); }, "添加路径点"); } private async Task ExecuteEndEditAsync() { if (!CanExecuteEndEdit) return; if ((_pathPlanningManager == null || _pathPlanningManager.PathEditState == PathEditState.Viewing) && CanCancelRailAssemblyWorkflow) { ExecuteCancelRailAssemblyWorkflow(); return; } await SafeExecuteAsync(async () => { bool success = false; string operationMessage = ""; bool wasInPreviewMode = false; if (_pathPlanningManager != null) { // 检查是否在修改路径点模式 bool isEditingPoint = _pathPlanningManager.PathEditState == PathEditState.EditingPoint; if (isEditingPoint) { // 修改路径点模式 - 尝试确认修改 success = _pathPlanningManager.UpdatePointPosition(); if (success) { operationMessage = "路径点修改已确认"; } else { // 确认失败(可能没有预览点),执行取消操作以安全退出 success = _pathPlanningManager.CancelEditPoint(); operationMessage = success ? "已取消路径点修改" : "取消路径点修改失败"; } LogManager.Info($"[UI-修改路径点] {operationMessage}"); } else { // 记录是否在预览模式(因为确认预览点后会退出预览模式) wasInPreviewMode = _pathPlanningManager.IsPreviewMode; if (wasInPreviewMode) { // 预览模式 - 先确认预览点,然后立即完成编辑 var confirmedPoint = _pathPlanningManager.ConvertPreviewToPathPoint(); if (confirmedPoint != null) { LogManager.Info($"[UI-预览模式] 预览点已确认: {confirmedPoint.Name},现在完成编辑"); // 立即完成编辑,不让用户继续添加点 success = _pathPlanningManager.FinishEditing(); operationMessage = success ? $"预览点已确认并完成编辑: {confirmedPoint.Name}" : "预览点确认后完成编辑失败"; } else { LogManager.Error("[UI-预览模式] 预览点确认失败"); operationMessage = "预览点确认失败"; success = false; } LogManager.Info($"[UI-预览模式] {operationMessage}"); } else { // 普通模式 - 直接完成编辑 success = _pathPlanningManager.FinishEditing(); operationMessage = success ? "路径编辑完成" : "路径编辑完成失败"; LogManager.Info($"[UI-编辑模式] {operationMessage}"); } } } await _uiStateManager.ExecuteUIUpdateAsync(() => { if (success) { // 编辑完成成功 if (SelectedPathRoute != null) { if (_pathPlanningManager?.PathEditState == PathEditState.EditingPoint || operationMessage.Contains("路径点修改")) { UpdateMainStatus($"✅ 路径点修改完成: {SelectedPathRoute.Name}"); } else if (wasInPreviewMode) { UpdateMainStatus($"✅ 预览点已确认,路径编辑完成: {SelectedPathRoute.Name}"); } else { UpdateMainStatus($"✅ 路径编辑完成: {SelectedPathRoute.Name} - 最后一个点已自动设置为终点"); } } LogManager.Info("[UI状态] 路径编辑完成,等待状态变更事件更新按钮"); } else { // 编辑完成失败 if (SelectedPathRoute != null) { UpdateMainStatus($"❌ 路径编辑失败: {SelectedPathRoute.Name}"); } LogManager.Error("[UI状态] 路径编辑失败"); } // 手动触发按钮状态更新 - 确保UI及时响应 OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); OnPropertyChanged(nameof(CanExecuteCancelModifyPoint)); }); }, "结束路径编辑"); } private async Task ExecuteClearPathAsync() { if (!CanExecuteClearPath) return; await SafeExecuteAsync(async () => { var pointCount = SelectedPathRoute.Points.Count; // 清空 Core 层的路径点 var coreRoute = _pathPlanningManager?.Routes?.FirstOrDefault(r => r.Name == SelectedPathRoute.Name); if (coreRoute != null) { coreRoute.Points.Clear(); coreRoute.RecalculateAndSaveRoute("清空路径"); LogManager.Info($"已清空Core路径: {coreRoute.Name}"); } // 清空 UI 层的路径点 await _uiStateManager.ExecuteUIUpdateAsync(() => { SelectedPathRoute.Points.Clear(); UpdateMainStatus($"已清空路径 {SelectedPathRoute.Name} 的 {pointCount} 个点"); }); }, "清空路径"); } private async Task ExecuteDeletePointAsync(PathPointViewModel point) { if (SelectedPathRoute == null || point == null) return; await SafeExecuteAsync(async () => { // 1. 查找对应的Core数据 var coreRoute = _pathPlanningManager?.Routes?.FirstOrDefault(r => r.Name == SelectedPathRoute.Name); if (coreRoute == null) { LogManager.Warning($"未找到对应的Core路径: {SelectedPathRoute.Name}"); return; } // 2. 路径完整性验证:检查删除后是否至少还有2个点 if (coreRoute.Points.Count <= 2) { LogManager.Warning($"路径点不足,无法删除。当前路径只有{coreRoute.Points.Count}个点,至少需要保留2个点(起点和终点)"); UpdateMainStatus($"❌ 无法删除路径点:路径至少需要保留2个点(起点和终点),当前只有{coreRoute.Points.Count}个点"); return; } // 3. 在Core路径中查找对应的点(通过位置和名称匹配) var corePoint = coreRoute.Points.FirstOrDefault(p => p.Name == point.Name && Math.Abs(p.Position.X - point.X) < 0.001 && Math.Abs(p.Position.Y - point.Y) < 0.001 && Math.Abs(p.Position.Z - point.Z) < 0.001); if (corePoint == null) { LogManager.Warning($"未找到对应的Core路径点: {point.Name}"); // 即使Core数据不匹配,也要更新UI数据保持一致性 } await _uiStateManager.ExecuteUIUpdateAsync(() => { // 4. 禁止删除起点和终点 if (corePoint != null) { int pointIndex = coreRoute.Points.IndexOf(corePoint); if (corePoint.Type == PathPointType.StartPoint) { LogManager.Warning($"不能删除路径的起点"); UpdateMainStatus($"❌ 无法删除:{corePoint.Name} 是路径的起点"); return; } if (corePoint.Type == PathPointType.EndPoint) { LogManager.Warning($"不能删除路径的终点"); UpdateMainStatus($"❌ 无法删除:{corePoint.Name} 是路径的终点"); return; } // 吊装路径:禁止删除关键点(提升点、下降点) if (coreRoute.PathType == PathType.Hoisting && (corePoint.Name == "提升点" || corePoint.Name == "下降点")) { LogManager.Warning($"不能删除吊装路径的关键点: {corePoint.Name}"); UpdateMainStatus($"❌ 无法删除:{corePoint.Name} 是吊装路径的关键点"); return; } // 删除Core数据 coreRoute.Points.Remove(corePoint); LogManager.Info($"已从Core数据删除路径点: {corePoint.Name}"); // 吊装路径:正交化路径(处理斜线和清除多余点) if (coreRoute.PathType == PathType.Hoisting) { _pathPlanningManager?.OrthogonalizePath(coreRoute); // 吊装路径:检测并移除矩形环路 LogManager.Info($"[吊装路径] 删除路径点后检测矩形环路"); _pathPlanningManager?.RemoveRectangularLoops(coreRoute); } // 调用PathPlanningManager的3D删除方法 _pathPlanningManager.RemovePathPoint(corePoint); // 触发UI同步更新事件 _pathPlanningManager.RaisePathPointsListUpdated(coreRoute, "删除路径点并重新分配类型"); LogManager.Info("已触发PathPointsListUpdated事件,UI将自动同步"); } // 5. 删除UI数据(事件驱动会自动处理,这里只作为备份) SelectedPathRoute.Points.Remove(point); LogManager.Info($"已从UI删除路径点: {point.Name}"); // 🔥 如果被删除的点是当前选中的点,清空选中状态 if (SelectedPathPoint != null && SelectedPathPoint.Id == point.Id) { SelectedPathPoint = null; LogManager.Info("已清空选中状态(删除的是当前选中的点)"); } // 6. 更新UI状态 UpdateMainStatus($"✅ 已删除路径点: {point.Name}"); LogManager.Info($"路径点删除完成,当前UI点数: {SelectedPathRoute.Points.Count}, Core点数: {coreRoute?.Points.Count ?? 0}"); }); // 7. 刷新3D可视化显示(路径变更会自动更新可视化) UpdatePathVisualization(); LogManager.Info("已触发路径可视化更新"); }, "删除路径点"); } private async Task ExecuteModifyPointAsync() { if (!CanExecuteModifyPoint) return; await SafeExecuteAsync(async () => { await _uiStateManager.ExecuteUIUpdateAsync(() => { if (SelectedPathPoint != null && _pathPlanningManager != null) { // 获取选中路径点的索引 var currentRoute = _pathPlanningManager.CurrentRoute; if (currentRoute != null && currentRoute.Points != null) { int pointIndex = -1; // 详细调试信息 LogManager.Info($"开始查找路径点索引 - 选中点Id: {SelectedPathPoint.Id}, 选中点名称: {SelectedPathPoint.Name}"); LogManager.Info($"当前路径包含 {currentRoute.Points.Count} 个路径点"); // 遍历查找匹配的Id for (int i = 0; i < currentRoute.Points.Count; i++) { var currentPoint = currentRoute.Points[i]; //LogManager.Info($"检查路径点 [{i}] - Id: {currentPoint.Id}, 名称: {currentPoint.Name}"); if (currentPoint.Id == SelectedPathPoint.Id) { pointIndex = i; LogManager.Info($"找到匹配的路径点,索引: {pointIndex}"); break; } } if (pointIndex >= 0) { // 调用PathPlanningManager开始修改路径点 bool success = _pathPlanningManager.StartEditingPoint(pointIndex); if (success) { UpdateMainStatus($"正在修改路径点: {SelectedPathPoint.Name} - 请在3D视图中点击新位置"); LogManager.Info($"已开始修改路径点: {SelectedPathPoint.Name}, 索引: {pointIndex}"); // 刷新命令状态 OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); OnPropertyChanged(nameof(CanExecuteModifyPoint)); OnPropertyChanged(nameof(CanExecuteCancelModifyPoint)); } else { UpdateMainStatus("修改路径点失败,请检查路径状态"); LogManager.Error($"开始修改路径点失败: {SelectedPathPoint.Name}, 索引: {pointIndex}"); } } else { UpdateMainStatus("找不到选中的路径点"); LogManager.Error($"找不到路径点索引 - 选中点Id: {SelectedPathPoint.Id}, 选中点名称: {SelectedPathPoint.Name}"); LogManager.Error("可能原因:1)选中的路径点与当前路径不匹配 2)路径点数据不同步 3)Id不匹配"); } } else { if (currentRoute == null) { UpdateMainStatus("当前没有活动路径"); LogManager.Error("尝试修改路径点时没有当前路径"); } else { UpdateMainStatus("当前路径没有路径点数据"); LogManager.Error("当前路径的Points集合为空"); } } } else { if (SelectedPathPoint == null) { UpdateMainStatus("请先选中要修改的路径点"); LogManager.Warning("修改路径点时没有选中路径点"); } else { UpdateMainStatus("路径规划管理器未初始化"); LogManager.Error("修改路径点时PathPlanningManager为空"); } } }); }, "修改路径点"); } /// /// 取消修改路径点 /// private async Task ExecuteCancelModifyPointAsync() { if (!CanExecuteCancelModifyPoint) return; await SafeExecuteAsync(async () => { await _uiStateManager.ExecuteUIUpdateAsync(() => { if (_pathPlanningManager != null) { // 调用PathPlanningManager取消修改路径点 bool success = _pathPlanningManager.CancelEditPoint(); if (success) { UpdateMainStatus("已取消修改路径点"); LogManager.Info("用户取消了修改路径点操作"); } else { UpdateMainStatus("取消修改路径点失败"); LogManager.Error("取消修改路径点失败"); } // 刷新命令状态 OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); OnPropertyChanged(nameof(CanExecuteModifyPoint)); OnPropertyChanged(nameof(CanExecuteCancelModifyPoint)); } }); }, "取消修改路径点"); } #endregion #region 文件管理命令 private async Task ExecuteImportPathAsync() { await SafeExecuteAsync(async () => { try { // 打开文件选择对话框 var openFileDialog = new OpenFileDialog { Title = "导入路径文件", Filter = "路径文件 (*.xml;*.json)|*.xml;*.json|XML文件 (*.xml)|*.xml|JSON文件 (*.json)|*.json|所有文件 (*.*)|*.*", FilterIndex = 1, CheckFileExists = true, CheckPathExists = true }; if (openFileDialog.ShowDialog() == true) { var filePath = openFileDialog.FileName; var extension = Path.GetExtension(filePath).ToLower(); // 确定导入格式 ExportFormat importFormat; switch (extension) { case ".json": importFormat = ExportFormat.Json; break; case ".csv": importFormat = ExportFormat.Csv; break; default: importFormat = ExportFormat.Xml; break; } // 创建导入参数 var importParameters = new ImportPathParameters { FilePath = filePath, ImportFormat = importFormat, MergeWithExisting = true, CreateBackup = true, DuplicateHandling = DuplicateNameHandling.Rename, ValidateImportedData = true, AutoSelectFirstRoute = true, Description = $"导入路径文件: {Path.GetFileName(filePath)}" }; // 创建并执行导入命令 var importCommand = new ImportPathCommand(importParameters, _pathPlanningManager); var result = await importCommand.ExecuteAsync(); if (result.IsSuccess) { // 更新UI状态 RefreshPathRoutes(); // 如果需要自动选择第一个导入的路径,通过专门的选择逻辑处理 if (importParameters.AutoSelectFirstRoute && PathRoutes.Count > 0) { var firstRoute = PathRoutes.FirstOrDefault(); if (firstRoute != null) { SelectedPathRoute = firstRoute; LogManager.Info($"导入完成后自动选择路径: {firstRoute.Name}"); } } var importResult = (result as PathPlanningResult)?.Data; if (importResult != null) { var statusMessage = $"✅ 导入成功: {importResult.ImportedCount}/{importResult.TotalInFile} 个路径"; if (importResult.FailedCount > 0) { statusMessage += $" (失败 {importResult.FailedCount} 个)"; } UpdateMainStatus(statusMessage); if (!string.IsNullOrEmpty(importResult.BackupFilePath)) { LogManager.Info($"导入前备份已创建: {importResult.BackupFilePath}"); } } else { UpdateMainStatus($"✅ 路径导入完成: {Path.GetFileName(filePath)}"); } } else { UpdateMainStatus($"❌ 导入失败: {result.ErrorMessage}"); LogManager.Error($"路径导入失败: {result.ErrorMessage}"); } } } catch (Exception ex) { LogManager.Error($"导入路径异常: {ex.Message}", ex); UpdateMainStatus($"❌ 导入异常: {ex.Message}"); } }, "导入路径"); } private async Task ExecuteExportPathAsync() { if (!CanExecuteExportPath) return; await SafeExecuteAsync(async () => { try { // 打开文件保存对话框 var saveFileDialog = new SaveFileDialog { Title = "导出全部路径", Filter = "XML文件 (*.xml)|*.xml|JSON文件 (*.json)|*.json|CSV文件 (*.csv)|*.csv|DELMIA Apriso XML (*.aprxml)|*.aprxml|所有文件 (*.*)|*.*", FilterIndex = 1, FileName = $"路径导出_{DateTime.Now:yyyyMMdd_HHmmss}", DefaultExt = "xml", AddExtension = true }; if (saveFileDialog.ShowDialog() == true) { var filePath = saveFileDialog.FileName; // 确定导出格式(通过FilterIndex判断,因为DELMIA Apriso和普通XML都是.xml扩展名) ExportFormat exportFormat; switch (saveFileDialog.FilterIndex) { case 2: // JSON文件 exportFormat = ExportFormat.Json; break; case 3: // CSV文件 exportFormat = ExportFormat.Csv; break; case 4: // DELMIA Apriso XML exportFormat = ExportFormat.DelmiaApriso; break; default: // XML文件或其他 exportFormat = ExportFormat.Xml; break; } // 创建导出参数 - 导出所有路径 var exportParameters = new ExportPathParameters { ExportAll = true, OutputFilePath = filePath, ExportFormat = exportFormat, OverwriteExisting = true, ValidateExportData = true, GenerateReport = false, Description = $"导出全部路径到: {Path.GetFileName(filePath)}" }; // 创建并执行导出命令 var exportCommand = new ExportPathCommand(exportParameters, _pathPlanningManager); var result = await exportCommand.ExecuteAsync(); if (result.IsSuccess) { var exportResult = (result as PathPlanningResult)?.Data; if (exportResult != null) { var statusMessage = $"✅ 导出全部成功: {exportResult.ExportedCount} 个路径到 {Path.GetFileName(filePath)}"; if (exportResult.FailedCount > 0) { statusMessage += $" (失败 {exportResult.FailedCount} 个)"; } UpdateMainStatus(statusMessage); LogManager.Info($"导出完成,文件大小: {exportResult.FileSizeMB:F2} MB"); } else { UpdateMainStatus($"✅ 全部路径导出完成: {Path.GetFileName(filePath)}"); } } else { UpdateMainStatus($"❌ 导出全部失败: {result.ErrorMessage}"); LogManager.Error($"全部路径导出失败: {result.ErrorMessage}"); } } } catch (Exception ex) { LogManager.Error($"导出全部路径异常: {ex.Message}", ex); UpdateMainStatus($"❌ 导出全部异常: {ex.Message}"); } }, "导出全部路径"); } private async Task ExecuteSaveAsPathAsync() { if (!CanExecuteSaveAsPath) return; await SafeExecuteAsync(async () => { try { // 打开文件保存对话框 var saveFileDialog = new SaveFileDialog { Title = $"导出选中路径: {SelectedPathRoute?.Name}", Filter = "XML文件 (*.xml)|*.xml|JSON文件 (*.json)|*.json|CSV文件 (*.csv)|*.csv|DELMIA Apriso XML (*.aprxml)|*.aprxml|所有文件 (*.*)|*.*", FilterIndex = 1, FileName = SelectedPathRoute?.Name?.Replace(" ", "_") ?? "路径", DefaultExt = "xml", AddExtension = true }; if (saveFileDialog.ShowDialog() == true) { var filePath = saveFileDialog.FileName; // 确定导出格式(通过FilterIndex判断,因为DELMIA Apriso和普通XML都是.xml扩展名) ExportFormat exportFormat; switch (saveFileDialog.FilterIndex) { case 2: // JSON文件 exportFormat = ExportFormat.Json; break; case 3: // CSV文件 exportFormat = ExportFormat.Csv; break; case 4: // DELMIA Apriso XML exportFormat = ExportFormat.DelmiaApriso; break; default: // XML文件或其他 exportFormat = ExportFormat.Xml; break; } // 查找当前选中路径对应的Core路径 var coreRoute = _pathPlanningManager.Routes.FirstOrDefault(r => r.Name == SelectedPathRoute.Name); if (coreRoute != null) { // 创建导出参数 - 只导出当前选中的路径 var exportParameters = new ExportPathParameters { PathsToExport = new System.Collections.Generic.List { coreRoute }, OutputFilePath = filePath, ExportFormat = exportFormat, OverwriteExisting = true, ValidateExportData = true, GenerateReport = false, Description = $"导出选中路径: {SelectedPathRoute.Name}" }; // 创建并执行导出命令 var exportCommand = new ExportPathCommand(exportParameters, _pathPlanningManager); var result = await exportCommand.ExecuteAsync(); if (result.IsSuccess) { var exportResult = (result as PathPlanningResult)?.Data; if (exportResult != null) { UpdateMainStatus($"✅ 导出选中路径成功: {SelectedPathRoute.Name} -> {Path.GetFileName(filePath)}"); LogManager.Info($"导出选中路径完成,文件大小: {exportResult.FileSizeMB:F2} MB"); } else { UpdateMainStatus($"✅ 导出选中路径完成: {Path.GetFileName(filePath)}"); } } else { UpdateMainStatus($"❌ 导出选中路径失败: {result.ErrorMessage}"); LogManager.Error($"导出选中路径失败: {result.ErrorMessage}"); } } else { UpdateMainStatus($"❌ 导出选中路径失败:找不到对应的Core路径: {SelectedPathRoute.Name}"); } } } catch (Exception ex) { LogManager.Error($"导出选中路径异常: {ex.Message}", ex); UpdateMainStatus($"❌ 导出选中路径异常: {ex.Message}"); } }, "导出选中路径"); } /// /// 生成导航地图的纯业务逻辑方法 /// /// 渲染样式 /// 图像宽度 /// 图像高度 /// 图像格式 /// 生成的图像数据 public Bitmap GenerateNavigationMap(ImageGenerationStyle renderStyle, int width, int height, ImageFormat format) { try { LogManager.Info($"开始生成导航地图: 样式={renderStyle}, 尺寸={width}x{height}, 格式={format}"); // 创建导航地图生成器 var generator = new NavigationMapGenerator(); // 生成图像 var bitmap = generator.GenerateNavigationMapImage(renderStyle, width, height); LogManager.Info($"导航地图生成成功: {width}x{height}像素"); return bitmap; } catch (Exception ex) { LogManager.Error($"生成导航地图失败: {ex.Message}", ex); throw; } } /// /// 刷新路径集合显示 /// private void RefreshPathRoutes() { try { if (_pathPlanningManager?.Routes != null) { // 清除现有的UI路径 PathRoutes.Clear(); // 重新加载所有路径 foreach (var coreRoute in _pathPlanningManager.Routes) { var pathViewModel = new PathRouteViewModel(isFromDatabase: true) { Route = coreRoute, IsActive = false }; // 设置时间信息 pathViewModel.SetTimeInfo(coreRoute.CreatedTime, coreRoute.LastModified); // 使用统一的辅助方法创建路径点 var pathPoints = CreatePathPointViewModelsFromCoreRoute(coreRoute); foreach (var point in pathPoints) { pathViewModel.Points.Add(point); } PathRoutes.Add(pathViewModel); LogManager.Info($"RefreshPathRoutes: 添加路径ViewModel {coreRoute.Name} (Hash: {pathViewModel.GetHashCode()})"); } LogManager.Info($"已刷新路径集合显示,共 {PathRoutes.Count} 个路径"); // 调试:列出所有PathRoutes中的路径名称和哈希码 for (int i = 0; i < PathRoutes.Count; i++) { var route = PathRoutes[i]; LogManager.Info($" PathRoutes[{i}]: {route.Name} (Hash: {route.GetHashCode()}, Points: {route.Points.Count})"); } } } catch (Exception ex) { LogManager.Error($"刷新路径集合失败: {ex.Message}", ex); } } #endregion #endregion #region 事件处理 /// /// 处理自动路径规划的鼠标点击事件 /// private async void OnAutoPathMouseClicked(object sender, PickItemResult pickResult) { try { if (pickResult == null) { return; } var point3D = pickResult.Point; if (IsSelectingStartPoint) { SetAutoPathStartPoint(point3D); await SafeExecuteAsync(() => { // 退出自动路径规划模式 - 恢复事件订阅 LogManager.Info("起点选择完成,恢复正常事件订阅状态"); CleanupAutoPathEventSubscriptions(); _pathPlanningManager?.EnableMouseHandling(); // 恢复PathPlanningManager的鼠标处理 _pathPlanningManager?.StopClickTool(); }, "停止起点选择工具"); } else if (IsSelectingEndPoint) { SetAutoPathEndPoint(point3D); await SafeExecuteAsync(() => { // 退出自动路径规划模式 - 恢复事件订阅 LogManager.Info("终点选择完成,恢复正常事件订阅状态"); CleanupAutoPathEventSubscriptions(); _pathPlanningManager?.EnableMouseHandling(); // 恢复PathPlanningManager的鼠标处理 _pathPlanningManager?.StopClickTool(); }, "停止终点选择工具"); } else { CleanupAutoPathEventSubscriptions(); _pathPlanningManager?.EnableMouseHandling(); // 恢复PathPlanningManager的鼠标处理 } } catch (Exception ex) { LogManager.Error($"[自动路径事件] 处理点击事件异常: {ex.Message}"); UpdateMainStatus($"获取点击位置失败: {ex.Message}"); IsSelectingStartPoint = false; IsSelectingEndPoint = false; // 异常情况下也要清理事件订阅并恢复状态 try { CleanupAutoPathEventSubscriptions(); _pathPlanningManager?.EnableMouseHandling(); // 恢复PathPlanningManager的鼠标处理 _pathPlanningManager?.StopClickTool(); } catch (Exception cleanupEx) { LogManager.Error($"[自动路径事件] 清理异常: {cleanupEx.Message}"); } } } /// /// 设置自动路径规划的起点 /// public async void SetAutoPathStartPoint(Point3D point) { await SafeExecuteAsync(() => { // 直接设置新的起点(清理逻辑已在ExecuteSelectStartPointAsync中处理) _startPoint3D = point; _hasStartPoint = true; AutoPathStartPoint = $"({point.X:F2}, {point.Y:F2}, {point.Z:F2})"; IsSelectingStartPoint = false; // 通知Can Execute属性更改 OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); OnPropertyChanged(nameof(CanExecuteNewPath)); // 渲染新的起点标记 if (PathPointRenderPlugin.Instance != null) { // 创建临时路径只包含起点 _autoPathStartPointRoute = new PathRoute("AutoPathStartPoint") { Description = "自动路径规划起点" }; var startPathPoint = new PathPoint { Name = "起点", Position = point, Type = PathPointType.StartPoint }; _autoPathStartPointRoute.AddPoint(startPathPoint); // 使用新的只渲染点的API,不绘制连线 PathPointRenderPlugin.Instance.RenderPointOnly(_autoPathStartPointRoute); LogManager.Info($"起点路径已创建,ID: {_autoPathStartPointRoute.Id}"); } if (_hasEndPoint) { UpdateMainStatus("起点和终点已设置,可以开始路径规划"); } else { UpdateMainStatus("起点已设置,请选择终点"); } LogManager.Info($"起点已设置: ({point.X:F2}, {point.Y:F2}, {point.Z:F2})"); }, "设置起点"); } /// /// 设置自动路径规划的终点 /// public async void SetAutoPathEndPoint(Point3D point) { await SafeExecuteAsync(() => { // 直接设置新的终点(清理逻辑已在ExecuteSelectEndPointAsync中处理) _endPoint3D = point; _hasEndPoint = true; AutoPathEndPoint = $"({point.X:F2}, {point.Y:F2}, {point.Z:F2})"; IsSelectingEndPoint = false; // 通知Can Execute属性更改 OnPropertyChanged(nameof(CanExecuteAutoPlanPath)); OnPropertyChanged(nameof(CanExecuteNewPath)); // 渲染新的终点标记 if (PathPointRenderPlugin.Instance != null) { // 创建临时路径只包含终点 _autoPathEndPointRoute = new PathRoute("AutoPathEndPoint") { Description = "自动路径规划终点" }; var endPathPoint = new PathPoint { Name = "终点", Position = point, Type = PathPointType.EndPoint }; _autoPathEndPointRoute.AddPoint(endPathPoint); // 使用新的只渲染点的API,不绘制连线 PathPointRenderPlugin.Instance.RenderPointOnly(_autoPathEndPointRoute); LogManager.Info($"终点路径已创建,ID: {_autoPathEndPointRoute.Id}"); } if (_hasStartPoint) { UpdateMainStatus("起点和终点已设置,可以开始路径规划"); } else { UpdateMainStatus("终点已设置,请选择起点"); } LogManager.Info($"终点已设置: ({point.X:F2}, {point.Y:F2}, {point.Z:F2})"); }, "设置终点"); } #endregion #region 物体参数同步 /// /// 同步物体参数到渲染插件 /// private void SyncObjectParametersToRenderPlugin() { try { if (PathPointRenderPlugin.Instance != null) { // 检查检测动画页签是否选择了物体或使用了虚拟物体 // 如果有,使用动画页签的物体参数来更新通行空间(而不是跳过) var animationVm = AnimationControlViewModel.Instance; if (animationVm != null && animationVm.HasSelectedAnimatedObject) { // 动画页签已选择物体,使用动画页签的物体尺寸和当前路径的类型 LogManager.Debug("[物体参数同步] 检测动画页签已选择动画对象,使用动画页签的物体尺寸"); SyncAnimationViewObjectParameters(animationVm); return; } // 将米单位转换为模型单位(SetPassageSpaceParameters需要模型单位) var metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); double objectLengthModel = ObjectLength * metersToUnits; double objectWidthModel = ObjectWidth * metersToUnits; double objectHeightModel = ObjectHeight * metersToUnits; double safetyMarginModel = SafetyMargin * metersToUnits; // 根据路径类型计算通行空间尺寸 double passageAcrossPath, passageNormalToPath, passageAlongPath; PathType pathType = SelectedPathRoute?.PathType ?? PathType.Ground; if (pathType == PathType.Rail) { // 空轨路径:物体水平悬挂,垂直于路径的截面包含物体的宽度和高度 passageAcrossPath = objectWidthModel + 2 * safetyMarginModel; // Y方向(宽度)+ 2*间隙(垂直于路径方向) passageNormalToPath = objectHeightModel + 2 * safetyMarginModel; // Z方向(高度)+ 2*间隙(法线方向) passageAlongPath = objectLengthModel; // X方向(长度)(沿路径方向) } else if (pathType == PathType.Hoisting) { // 吊装路径:物体垂直悬挂,垂直于路径的截面包含物体的长度和宽度 passageAcrossPath = objectWidthModel + 2 * safetyMarginModel; // Y方向(宽度)+ 2*间隙(垂直于路径方向) passageNormalToPath = objectLengthModel + 2 * safetyMarginModel; // X方向(长度)+ 2*间隙(法线方向) passageAlongPath = objectHeightModel; // Z方向(高度)(沿路径方向) } else // Ground { // 地面路径:高度只有上方加间隙 passageAcrossPath = objectWidthModel + 2 * safetyMarginModel; // Y方向(宽度)+ 2*间隙(垂直于路径方向) passageNormalToPath = objectHeightModel + safetyMarginModel; // Z方向(高度)+ 间隙(法线方向,只有上方) passageAlongPath = objectLengthModel; // X方向(长度)(沿路径方向) } // 设置通行空间参数到渲染插件 // passageNormalToPathVertical: 垂直段的高度(物体长度 + 2×安全间隙) // passageNormalToPathHorizontal: 水平段的高度(物体高度 + 2×安全间隙) double passageNormalToPathVertical = objectLengthModel + 2 * safetyMarginModel; double passageNormalToPathHorizontal = objectHeightModel + 2 * safetyMarginModel; PathPointRenderPlugin.Instance.SetPassageSpaceParameters( passageAcrossPath, passageNormalToPath, passageAlongPath, passageNormalToPathVertical, passageNormalToPathHorizontal); LogManager.Debug($"[物体参数同步] 已设置通行空间参数: 垂直路径={passageAcrossPath / metersToUnits:F2}m, 法线={passageNormalToPath / metersToUnits:F2}m, 沿路径={passageAlongPath / metersToUnits:F2}m, 间隙={SafetyMargin:F2}m"); } else { LogManager.Warning("[物体参数同步] PathPointRenderPlugin实例为空,无法同步物体参数"); } } catch (Exception ex) { LogManager.Error($"[物体参数同步] 同步物体参数失败: {ex.Message}", ex); } } /// /// 使用动画页签的物体参数同步到渲染插件 /// private void SyncAnimationViewObjectParameters(AnimationControlViewModel animationVm) { try { // 获取当前路径类型(使用路径编辑页签的当前选择) PathType pathType = SelectedPathRoute?.PathType ?? PathType.Ground; // 获取单位转换因子 var metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); double safetyMarginModel = SafetyMargin * metersToUnits; double objectLengthModel, objectWidthModel, objectHeightModel; if (animationVm.UseVirtualObject) { // 使用虚拟物体尺寸 objectLengthModel = animationVm.VirtualObjectLengthInMeters * metersToUnits; objectWidthModel = animationVm.VirtualObjectWidthInMeters * metersToUnits; objectHeightModel = animationVm.VirtualObjectHeightInMeters * metersToUnits; LogManager.Debug($"[物体参数同步] 使用虚拟物体尺寸: {animationVm.VirtualObjectLengthInMeters:F2}m x {animationVm.VirtualObjectWidthInMeters:F2}m x {animationVm.VirtualObjectHeightInMeters:F2}m"); } else if (animationVm.SelectedAnimatedObject != null) { var animationManager = NavisworksTransport.Core.Animation.PathAnimationManager.GetInstance(); if ((pathType == PathType.Ground || pathType == PathType.Hoisting) && animationManager != null && animationManager.TryGetCurrentRouteRealObjectPlanarProjectedExtents( out double resolvedPlanarForward, out double resolvedPlanarSide, out double resolvedPlanarUp)) { objectLengthModel = resolvedPlanarForward; objectWidthModel = resolvedPlanarSide; objectHeightModel = resolvedPlanarUp; LogManager.Debug( $"[物体参数同步] 使用真实物体平面路径最终姿态尺寸: " + $"沿路径={resolvedPlanarForward / metersToUnits:F2}m, " + $"垂直路径={resolvedPlanarSide / metersToUnits:F2}m, " + $"法线={resolvedPlanarUp / metersToUnits:F2}m"); } else if (pathType == PathType.Rail && animationManager != null && animationManager.TryGetCurrentRouteRealObjectRailProjectedExtents( out double resolvedRailForward, out double resolvedRailSide, out double resolvedRailUp)) { objectLengthModel = resolvedRailForward; objectWidthModel = resolvedRailSide; objectHeightModel = resolvedRailUp; LogManager.Debug( $"[物体参数同步] 使用真实物体 Rail 最终姿态尺寸: " + $"沿路径={resolvedRailForward / metersToUnits:F2}m, " + $"垂直路径={resolvedRailSide / metersToUnits:F2}m, " + $"法线={resolvedRailUp / metersToUnits:F2}m"); } else { // 回退:使用选择物体时保存的原始尺寸(米) objectLengthModel = animationVm.ObjectOriginalLength * metersToUnits; objectWidthModel = animationVm.ObjectOriginalWidth * metersToUnits; objectHeightModel = animationVm.ObjectOriginalHeight * metersToUnits; LogManager.Debug( $"[物体参数同步] 回退使用选择物体保存的原始尺寸: " + $"{animationVm.ObjectOriginalLength:F2}m x {animationVm.ObjectOriginalWidth:F2}m x {animationVm.ObjectOriginalHeight:F2}m"); } } else { // 没有物体,使用默认参数 LogManager.Warning("[物体参数同步] 动画页签已选择物体但实际无物体,使用默认参数"); objectLengthModel = ObjectLength * metersToUnits; objectWidthModel = ObjectWidth * metersToUnits; objectHeightModel = ObjectHeight * metersToUnits; } // 根据路径类型计算通行空间尺寸 double passageAcrossPath, passageNormalToPath, passageAlongPath; if (pathType == PathType.Rail) { passageAcrossPath = objectWidthModel + 2 * safetyMarginModel; passageNormalToPath = objectHeightModel + 2 * safetyMarginModel; passageAlongPath = objectLengthModel; } else if (pathType == PathType.Hoisting) { passageAcrossPath = objectWidthModel + 2 * safetyMarginModel; passageNormalToPath = objectLengthModel + 2 * safetyMarginModel; passageAlongPath = objectHeightModel; } else // Ground { passageAcrossPath = objectWidthModel + 2 * safetyMarginModel; passageNormalToPath = objectHeightModel + safetyMarginModel; passageAlongPath = objectLengthModel; } // 设置通行空间参数到渲染插件 double passageNormalToPathVertical = objectLengthModel + 2 * safetyMarginModel; double passageNormalToPathHorizontal = objectHeightModel + 2 * safetyMarginModel; PathPointRenderPlugin.Instance.SetPassageSpaceParameters( passageAcrossPath, passageNormalToPath, passageAlongPath, passageNormalToPathVertical, passageNormalToPathHorizontal); LogManager.Debug($"[物体参数同步] 已使用动画页签物体参数设置通行空间: 类型={pathType}, 沿路径={passageAlongPath / metersToUnits:F2}m, 垂直路径={passageAcrossPath / metersToUnits:F2}m"); } catch (Exception ex) { LogManager.Error($"[物体参数同步] 同步动画页签物体参数失败: {ex.Message}", ex); } } #endregion #region 辅助方法 /// /// 安全执行异步操作 /// private async Task SafeExecuteAsync(Func action, string operationName = "未知操作") { try { await action(); } catch (Exception ex) { LogManager.Error($"{operationName}发生异常: {ex.Message}", ex); UpdateMainStatus($"{operationName}失败: {ex.Message}"); } } /// /// 网格可视化设置变更事件处理 /// private void OnGridVisualizationChanged() { try { LogManager.Info($"网格可视化设置已更改: 通行={ShowWalkableGrid}, 障碍物={ShowObstacleGrid}, 未知={ShowUnknownGrid}, 门={ShowDoorGrid}"); // 通知路径规划管理器更新网格可视化 if (_pathPlanningManager != null) { _pathPlanningManager.UpdateGridVisualizationSettings( showWalkable: ShowWalkableGrid, showObstacle: ShowObstacleGrid, showUnknown: ShowUnknownGrid, showDoor: ShowDoorGrid); UpdateMainStatus("网格可视化设置已更新"); } else { LogManager.Warning("无法获取路径规划管理器,网格可视化设置可能不会立即生效"); UpdateMainStatus("网格可视化设置已保存"); } } catch (Exception ex) { LogManager.Error($"应用网格可视化设置失败: {ex.Message}", ex); UpdateMainStatus("网格可视化设置应用失败"); } } /// /// 网格点类型变更事件处理 /// private void OnGridPointTypeChanged() { try { LogManager.Info($"网格点类型已更改: {GridPointType}"); // 通知路径点渲染插件更新网格点类型 var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin != null) { renderPlugin.GridPointType = GridPointType; UpdateMainStatus("网格点类型已更新"); } else { LogManager.Warning("无法获取PathPointRenderPlugin实例,网格点类型可能不会立即生效"); UpdateMainStatus("网格点类型已保存"); } } catch (Exception ex) { LogManager.Error($"应用网格点类型失败: {ex.Message}", ex); UpdateMainStatus("网格点类型应用失败"); } } /// /// 根据路径类型自动设置可视化模式 /// - 地面路径:控制点连线 + 路径线 /// - 吊装和空轨:控制点连线 + 通行空间(不能使用路径线) /// private void UpdatePassageSpaceVisualizationForPathType(PathType pathType) { try { // 所有路径类型默认都打开控制点连线 ShowControlVisualization = true; switch (pathType) { case PathType.Rail: case PathType.Hoisting: // 空中路径:通行空间 + 控制点,不能使用路径线 ShowPathLines = false; ShowObjectSpace = true; LogManager.Debug($"[路径编辑] 路径类型={pathType}(空中路径),默认模式:控制点+通行空间"); break; case PathType.Ground: // 地面路径:路径线 + 控制点,不使用通行空间 ShowPathLines = true; ShowObjectSpace = false; LogManager.Debug($"[路径编辑] 路径类型={pathType}(地面路径),默认模式:控制点+路径线"); break; default: LogManager.Warning($"[路径编辑] 未知的路径类型: {pathType}"); return; } LogManager.Info($"[路径编辑] 已根据路径类型自动设置可视化模式:控制点={ShowControlVisualization}, 路径线={ShowPathLines}, 通行空间={ShowObjectSpace}"); // 🔥 触发可视化状态变更事件,通知状态栏刷新按钮 _pathPlanningManager?.RaiseVisualizationStateChanged(); } catch (Exception ex) { LogManager.Error($"[路径编辑] 设置可视化模式失败: {ex.Message}", ex); } } /// /// 路径可视化模式变更事件处理 /// private void OnPathVisualizationModeChanged() { try { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin != null) { // 使用新的独立开关设置渲染插件 renderPlugin.ShowPathLines = ShowPathLines; renderPlugin.ShowObjectSpace = ShowObjectSpace; string modeName = ""; if (ShowPathLines && ShowObjectSpace) modeName = "路径线 + 通行空间"; else if (ShowPathLines) modeName = "路径线"; else if (ShowObjectSpace) modeName = "通行空间"; else modeName = "无路径可视化"; // 🔥 触发视图刷新 if (NavisApplication.ActiveDocument?.ActiveView != null) { NavisApplication.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render); } LogManager.Info($"路径可视化模式已更改: {modeName}"); UpdateMainStatus($"路径可视化: {modeName}"); } else { LogManager.Warning("无法获取PathPointRenderPlugin实例"); } } catch (Exception ex) { LogManager.Error($"应用路径可视化模式失败: {ex.Message}", ex); UpdateMainStatus("路径可视化模式更新失败"); } } /// /// 控制点可视化变更事件处理 /// private void OnControlVisualizationChanged() { try { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin != null) { // 更新所有路径的 ShowControlVisualization 属性 lock (typeof(PathPointRenderPlugin)) { var allVisualizations = renderPlugin.GetAllPathVisualizations(); foreach (var visualization in allVisualizations) { visualization.ShowControlVisualization = ShowControlVisualization; } } // 触发视图刷新 if (NavisApplication.ActiveDocument?.ActiveView != null) { NavisApplication.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render); } LogManager.Info($"控制点可视化已更改: {(ShowControlVisualization ? "显示" : "隐藏")}"); UpdateMainStatus("控制点可视化已更新"); } else { LogManager.Warning("无法获取PathPointRenderPlugin实例"); } } catch (Exception ex) { LogManager.Error($"应用控制点可视化设置失败: {ex.Message}", ex); UpdateMainStatus("控制点可视化设置失败"); } } /// /// 清理自动路径相关的事件订阅 /// private void CleanupAutoPathEventSubscriptions() { try { // 简单安全地移除事件订阅 // 由于C#事件处理机制,多次取消订阅同一个处理程序是安全的 // 即使处理程序未订阅,取消操作也不会抛出异常 PathClickToolPlugin.MouseClicked -= OnAutoPathMouseClicked; } catch (Exception ex) { LogManager.Error($"[事件清理] 清理自动路径事件订阅失败: {ex.Message}", ex); // 记录异常但不抛出,避免影响主流程 } } /// /// 清理临时的自动路径起点和终点标记 /// private void ClearTemporaryAutoPathMarkers() { try { if (PathPointRenderPlugin.Instance != null) { // 清除临时的起点标记(使用正确的路径ID) if (_autoPathStartPointRoute != null) { PathPointRenderPlugin.Instance.RemovePath(_autoPathStartPointRoute.Id); _autoPathStartPointRoute = null; } // 清除临时的终点标记(使用正确的路径ID) if (_autoPathEndPointRoute != null) { PathPointRenderPlugin.Instance.RemovePath(_autoPathEndPointRoute.Id); _autoPathEndPointRoute = null; } } else { LogManager.Warning("[临时标记清理] PathPointRenderPlugin实例为null,无法清理标记"); } } catch (Exception ex) { LogManager.Error($"[临时标记清理] 清理临时标记失败: {ex.Message}"); } } #endregion #region PathPlanningManager事件处理 /// /// 订阅PathPlanningManager事件 /// private void SubscribeToPathPlanningManager() { if (_pathPlanningManager == null) { LogManager.Debug("PathPlanningManager尚未设置,跳过事件订阅"); return; } try { // 订阅路径点操作事件(用于手工路径编辑) _pathPlanningManager.PathPointOperation += OnPathPointOperation; // 订阅路径点列表更新事件 _pathPlanningManager.PathPointsListUpdated += OnPathPointsListUpdated; // 订阅当前路径变更事件 _pathPlanningManager.CurrentRouteChanged += OnCurrentRouteChanged; // 订阅编辑状态变更事件 _pathPlanningManager.EditStateChanged += OnEditStateChanged; // 订阅路径生成事件(用于处理自动路径) _pathPlanningManager.RouteGenerated += OnRouteGenerated; // 订阅路径加载完成事件(用于处理数据库加载的路径) _pathPlanningManager.RoutesLoaded += OnRoutesLoaded; // 添加错误事件订阅 - 用于处理自动路径规划失败等错误 _pathPlanningManager.ErrorOccurred += OnPathPlanningError; LogManager.Debug("PathEditingViewModel已成功订阅PathPlanningManager事件"); } catch (Exception ex) { LogManager.Error($"*** 订阅PathPlanningManager事件失败: {ex.Message} ***", ex); } } /// /// 取消订阅PathPlanningManager事件 /// private void UnsubscribeFromPathPlanningManager() { if (_pathPlanningManager == null) return; try { _pathPlanningManager.PathPointOperation -= OnPathPointOperation; _pathPlanningManager.PathPointsListUpdated -= OnPathPointsListUpdated; _pathPlanningManager.CurrentRouteChanged -= OnCurrentRouteChanged; _pathPlanningManager.EditStateChanged -= OnEditStateChanged; _pathPlanningManager.RouteGenerated -= OnRouteGenerated; _pathPlanningManager.RoutesLoaded -= OnRoutesLoaded; _pathPlanningManager.ErrorOccurred -= OnPathPlanningError; LogManager.Info("PathEditingViewModel已取消订阅PathPlanningManager事件,包括ErrorOccurred"); } catch (Exception ex) { LogManager.Error($"取消订阅PathPlanningManager事件失败: {ex.Message}", ex); } } /// /// 处理路径点操作事件(手工路径编辑的核心) /// private async void OnPathPointOperation(object sender, PathPointOperationEventArgs e) { if (e == null || e.PathPoint == null) return; try { await SafeExecuteAsync(() => { LogManager.Info($"PathEditingViewModel收到路径点操作事件: {e.OperationType}, 点: {e.PathPoint.Name}"); // 只处理手动编辑的路径点,跳过自动路径点 // 自动路径通过RouteGenerated事件处理 if (e.PathPoint.Name.Contains("自动")) { LogManager.Info($"跳过自动路径点,将通过RouteGenerated事件处理: {e.PathPoint.Name}"); return; } // 注意:不在这里直接添加/移除单个路径点到UI // 因为PathPointsListUpdated事件会处理完整的路径同步 // 这里只做状态更新和选择管理 if (e.OperationType == PathPointOperationType.Added) { // 确保对应的路径在UI中存在并选中 var pathViewModel = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name); if (pathViewModel != null) { // 设置为选中路径 SelectedPathRoute = pathViewModel; } UpdateMainStatus($"已添加路径点: {e.PathPoint.Name} 到 {e.Route.Name}"); LogManager.Info($"路径点操作完成: {e.PathPoint.Name} 已添加到路径 {e.Route.Name}"); } else if (e.OperationType == PathPointOperationType.Removed) { UpdateMainStatus($"已移除路径点: {e.PathPoint.Name} 从 {e.Route.Name}"); LogManager.Info($"路径点操作完成: {e.PathPoint.Name} 已从路径 {e.Route.Name} 移除"); } }, "处理路径点操作事件"); } catch (Exception ex) { LogManager.Error($"处理路径点操作事件失败: {ex.Message}", ex); } } /// /// 处理路径点列表更新事件 /// private async void OnPathPointsListUpdated(object sender, PathPointsListUpdatedEventArgs e) { if (e?.Route == null) return; try { await SafeExecuteAsync(() => { LogManager.Info($"路径点列表更新: {e.Route.Name}, 原因: {e.UpdateReason}"); // 刷新对应路径的显示 var pathViewModel = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name); if (pathViewModel != null) { bool updated = SyncPathViewModelFromCoreRoute(pathViewModel, e.Route, preserveSelection: true); if (!updated) { LogManager.Info($"路径点数据无变化({pathViewModel.Points.Count}),跳过更新"); return; } LogManager.Info($"已更新路径点列表,当前点数: {pathViewModel.Points.Count}"); // 更新真实路径可视化 UpdatePathVisualization(); } else { LogManager.Warning($"未找到对应的PathViewModel: {e.Route.Name}"); } }, "处理路径点列表更新事件"); } catch (Exception ex) { LogManager.Error($"处理路径点列表更新事件失败: {ex.Message}", ex); } } /// /// 处理当前路径变更事件 /// private async void OnCurrentRouteChanged(object sender, CurrentRouteChangedEventArgs e) { try { await SafeExecuteAsync(() => { LogManager.Info($"当前路径变更: {e.PreviousRoute?.Name} -> {e.NewRoute?.Name}"); if (e.NewRoute != null) { // 查找并选中对应的路径ViewModel var pathViewModel = PathRoutes.FirstOrDefault(p => p.Name == e.NewRoute.Name); if (pathViewModel != null) { bool synced = SyncPathViewModelFromCoreRoute(pathViewModel, e.NewRoute, preserveSelection: true); if (synced) { LogManager.Info($"当前路径切换时已同步路径点列表: {e.NewRoute.Name}, 点数: {pathViewModel.Points.Count}"); } // 只在真的不同时才设置,避免重复触发 if (SelectedPathRoute != pathViewModel) { SelectedPathRoute = pathViewModel; LogManager.Info($"UI已同步选择路径: {e.NewRoute.Name}"); } } else { LogManager.Warning($"OnCurrentRouteChanged: UI中未找到路径 {e.NewRoute.Name},可能UI还未刷新"); } } else { // 当前路径被设为null,清除UI选择 if (SelectedPathRoute != null) { SelectedPathRoute = null; LogManager.Info("UI已清除路径选择"); } } }, "处理当前路径变更事件"); } catch (Exception ex) { LogManager.Error($"处理当前路径变更事件失败: {ex.Message}", ex); } } /// /// 处理编辑状态变更事件 /// private async void OnEditStateChanged(object sender, PathEditStateChangedEventArgs e) { try { await SafeExecuteAsync(() => { LogManager.Info($"路径编辑状态变更: {e.PreviousState} -> {e.NewState}"); // 修复:包含 AddingPoints 和 EditingPoint 状态 IsPathEditMode = (e.NewState == PathEditState.Creating || e.NewState == PathEditState.Editing || e.NewState == PathEditState.AddingPoints || e.NewState == PathEditState.EditingPoint); LogManager.Info($"[UI状态更新] IsPathEditMode 设为: {IsPathEditMode}"); // 更新状态提示 switch (e.NewState) { case PathEditState.Creating: UpdateMainStatus($"正在新建路径: {e.EditingRoute?.Name} - 请在3D视图中点击设置路径点"); break; case PathEditState.Editing: UpdateMainStatus($"正在编辑路径: {e.EditingRoute?.Name} - 请在3D视图中点击设置路径点"); break; case PathEditState.AddingPoints: UpdateMainStatus($"正在添加路径点到: {e.EditingRoute?.Name} - 请在3D视图中点击设置路径点"); break; case PathEditState.EditingPoint: UpdateMainStatus($"正在修改路径点: {e.EditingRoute?.Name} - 请在3D视图中点击新位置,然后点击'结束'确认"); break; case PathEditState.Viewing: // 如果当前状态包含路径规划完成信息(包括完成度百分比),则保留不覆盖 var currentStatus = _mainViewModel?.StatusText ?? ""; if (!currentStatus.Contains("路径规划完成") && !currentStatus.Contains("路径规划部分完成") && !currentStatus.Contains("✅") && !currentStatus.Contains("🔶")) { UpdateMainStatus("路径编辑已完成"); } break; default: UpdateMainStatus("就绪"); break; } // 通知命令状态更新 OnPropertyChanged(nameof(CanExecuteNewPath)); OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); OnPropertyChanged(nameof(CanExecuteModifyPoint)); OnPropertyChanged(nameof(CanExecuteCancelModifyPoint)); LogManager.Info($"[UI状态更新] 按钮状态已通知更新 - CanExecuteStartEdit: {CanExecuteStartEdit}, CanExecuteEndEdit: {CanExecuteEndEdit}, CanExecuteModifyPoint: {CanExecuteModifyPoint}, CanExecuteCancelModifyPoint: {CanExecuteCancelModifyPoint}"); }, "处理编辑状态变更事件"); } catch (Exception ex) { LogManager.Error($"处理编辑状态变更事件失败: {ex.Message}", ex); } } /// /// 处理路径生成事件(用于自动路径) /// private async void OnRouteGenerated(object sender, RouteGeneratedEventArgs e) { // 添加调试日志确认事件是否被调用 LogManager.Info($"*** OnRouteGenerated被调用!Sender: {sender?.GetType().Name}, Route: {e?.Route?.Name}, Method: {e?.GenerationMethod}, GridSize: {e?.GridSize} ***"); if (e?.Route == null) { LogManager.Warning("*** OnRouteGenerated: RouteGeneratedEventArgs或Route为null,退出处理 ***"); return; } try { await SafeExecuteAsync(() => { LogManager.Info($"PathEditingViewModel收到路径生成事件: {e.Route.Name}, 生成方式: {e.GenerationMethod}"); // 处理自动路径生成 if (e.GenerationMethod == RouteGenerationMethod.AutoPlanning) { LogManager.Info($"*** 开始处理自动路径生成: {e.Route.Name} ***"); // 创建对应的 WPF ViewModel var autoPathViewModel = new PathRouteViewModel(isFromDatabase: true) { Route = e.Route, IsActive = true }; // 设置时间信息 autoPathViewModel.SetTimeInfo(e.Route.CreatedTime, e.Route.LastModified); // 使用统一的辅助方法创建路径点 var pathPoints = CreatePathPointViewModelsFromCoreRoute(e.Route); foreach (var point in pathPoints) { autoPathViewModel.Points.Add(point); } LogManager.Info($"*** 已创建PathRouteViewModel,包含 {autoPathViewModel.Points.Count} 个点 ***"); // 检查是否已存在同名路径,避免重复添加 var existingPath = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name); if (existingPath == null) { // 添加到 UI 列表并选中 PathRoutes.Add(autoPathViewModel); SelectedPathRoute = autoPathViewModel; LogManager.Info($"*** 自动路径已添加到UI列表: {e.Route.Name},当前PathRoutes.Count = {PathRoutes.Count} ***"); } else { // 更新现有路径 existingPath.Points.Clear(); foreach (var wpfPoint in autoPathViewModel.Points) { existingPath.Points.Add(wpfPoint); } SelectedPathRoute = existingPath; LogManager.Info($"*** 自动路径已更新UI列表: {e.Route.Name} ***"); } // 构建包含网格信息和完成度的完整状态消息,使用事件参数中的网格大小信息 var gridInfo = e.GridSize > 0 ? $"网格{e.GridSize:F1}m" : "自动网格"; string statusMessage; if (e.Route.IsComplete) { // 完全到达终点 statusMessage = $"✅ 自动路径规划完成: {e.Route.Name},共 {e.Route.Points.Count} 个路径点,长度 {e.Route.TotalLength:F2}米"; } else { // 部分到达终点 statusMessage = $"🔶 自动路径规划部分完成: {e.Route.Name},共 {e.Route.Points.Count} 个路径点,长度 {e.Route.TotalLength:F2}米,(完成度: {e.Route.CompletionPercentage:F1}%)"; } UpdateMainStatus(statusMessage); LogManager.Info($"*** 状态已更新: {statusMessage} ***"); } else if (e.GenerationMethod == RouteGenerationMethod.Manual) { LogManager.Info($"手工路径生成完成: {e.Route.Name},开始刷新路径列表"); var existingPath = PathRoutes.FirstOrDefault(p => p.Name == e.Route.Name); if (existingPath == null) { existingPath = new PathRouteViewModel(isFromDatabase: true) { Route = e.Route, IsActive = true }; PathRoutes.Add(existingPath); LogManager.Info($"手工路径生成后补建UI路径壳: {e.Route.Name}"); } SyncPathViewModelFromCoreRoute(existingPath, e.Route, preserveSelection: true); SelectedPathRoute = existingPath; LogManager.Info($"手工路径已同步并重新选中: {e.Route.Name},点数: {existingPath.Points.Count}"); OnPropertyChanged(nameof(PathRoutes)); OnPropertyChanged(nameof(CanExecuteStartEdit)); OnPropertyChanged(nameof(CanExecuteEndEdit)); } else if (e.GenerationMethod == RouteGenerationMethod.DatabaseLoad) { // 注意:DatabaseLoad现在由OnRoutesLoaded事件处理,这里不再处理 LogManager.Warning($"*** OnRouteGenerated收到DatabaseLoad方法,这不应该发生!应该由OnRoutesLoaded处理 ***"); } }, "处理路径生成事件"); } catch (Exception ex) { LogManager.Error($"*** OnRouteGenerated异常: {ex.Message} ***", ex); } } /// /// 处理路径加载完成事件(用于数据库加载) /// private async void OnRoutesLoaded(object sender, RoutesLoadedEventArgs e) { LogManager.Info($"*** OnRoutesLoaded被调用!Sender: {sender?.GetType().Name}, RouteCount: {e?.RouteCount}, LoadSource: {e?.LoadSource} ***"); if (e?.Routes == null) { LogManager.Warning("*** OnRoutesLoaded: RoutesLoadedEventArgs或Routes为null,退出处理 ***"); return; } try { await SafeExecuteAsync(() => { LogManager.Info($"PathEditingViewModel收到路径加载完成事件: 加载了 {e.RouteCount} 条路径,来源: {e.LoadSource}"); // 处理从数据库加载的历史路径 foreach (var coreRoute in e.Routes) { // 创建对应的 WPF ViewModel var dbPathViewModel = new PathRouteViewModel(isFromDatabase: true) { Route = coreRoute, IsActive = false, // 历史路径默认不激活 PathType = coreRoute.PathType }; // 设置时间信息 dbPathViewModel.SetTimeInfo(coreRoute.CreatedTime, coreRoute.LastModified); // 使用统一的辅助方法创建路径点 var pathPoints = CreatePathPointViewModelsFromCoreRoute(coreRoute); foreach (var point in pathPoints) { dbPathViewModel.Points.Add(point); } // 检查是否已存在同名路径,避免重复添加 var existingPath = PathRoutes.FirstOrDefault(p => p.Name == coreRoute.Name); if (existingPath == null) { // 添加到 UI 列表 PathRoutes.Add(dbPathViewModel); } else { LogManager.Debug($"*** 数据库路径已存在,跳过: {coreRoute.Name} ***"); } } // 更新状态消息 string statusMessage = $"✅ 已从{e.LoadSource}加载 {e.RouteCount} 条历史路径"; UpdateMainStatus(statusMessage); // 自动提取并显示所有空轨模型的基准线 ExtractAndRenderRailBaselinePaths(); }, "处理路径加载完成事件"); } catch (Exception ex) { LogManager.Error($"*** OnRoutesLoaded异常: {ex.Message} ***", ex); } } /// /// 处理路径规划错误事件 /// /// /// 处理路径规划错误事件 /// /// /// 处理路径规划错误事件 /// private async void OnPathPlanningError(object sender, PathPlanningErrorOccurredEventArgs e) { LogManager.Info($"*** OnPathPlanningError被调用!Sender: {sender?.GetType().Name}, Error: {e?.ErrorMessage}, Level: {e?.ErrorLevel} ***"); try { await SafeExecuteAsync(async () => { // 根据错误级别决定日志记录方式 if (e.ErrorLevel == PathPlanningErrorLevel.Warning) { LogManager.Warning($"自动路径规划失败: {e.ErrorMessage}"); } else { LogManager.Error($"自动路径规划失败: {e.ErrorMessage}"); } // 使用UIStateManager确保UI更新在主线程执行 await _uiStateManager.ExecuteUIUpdateAsync(() => { // 更新自动路径状态显示错误信息 UpdateMainStatus($"❌ 自动路径规划失败: {e.ErrorMessage}"); LogManager.Info($"*** 状态已更新为: ❌ 自动路径规划失败: {e.ErrorMessage} ***"); }); }, "处理路径规划错误事件"); } catch (Exception ex) { LogManager.Error($"*** OnPathPlanningError异常: {ex.Message} ***", ex); } } #endregion #region 清理资源 /// /// 强制重新初始化ToolPlugin以确保获得鼠标焦点 /// /// 是否订阅鼠标事件,新建路径需要true,自动路径选点需要false /// 初始化是否成功 private bool ForceReinitializeToolPlugin(bool subscribeToEvents = false) { try { LogManager.Info($"开始强制重新初始化ToolPlugin以确保获得鼠标焦点,订阅事件: {subscribeToEvents}"); // 使用PathPlanningManager的公共方法,避免反射调用 if (_pathPlanningManager != null) { bool result = _pathPlanningManager.ForceReinitializeToolPlugin(subscribeToEvents); LogManager.Info($"ToolPlugin重新初始化结果(事件订阅: {subscribeToEvents}): {result}"); if (result) { LogManager.Info("ToolPlugin重新初始化成功,已获得鼠标焦点"); } else { LogManager.Error("ToolPlugin初始化失败"); } return result; } else { LogManager.Error("PathPlanningManager为null,无法重新初始化ToolPlugin"); return false; } } catch (Exception ex) { LogManager.Error($"重新初始化ToolPlugin失败: {ex.Message}", ex); return false; } } public void Cleanup() { try { LogManager.Info("开始清理PathEditingViewModel资源"); // 取消配置变更事件订阅 UnsubscribeFromConfigChanges(); // 取消事件订阅 UnsubscribeFromPathPlanningManager(); // 完整清理自动路径规划相关的事件订阅 try { CleanupAutoPathEventSubscriptions(); } catch (Exception ex) { LogManager.Warning($"清理自动路径事件订阅时发生异常: {ex.Message}"); } try { CleanupAssemblyReferenceSelection(); } catch (Exception ex) { LogManager.Warning($"清理直线装配事件订阅时发生异常: {ex.Message}"); } try { CleanupAssemblyEndFaceSelection(clearVisuals: true); } catch (Exception ex) { LogManager.Warning($"清理端面三点分析事件订阅时发生异常: {ex.Message}"); } try { ClearAssemblyAnchorMarker(); } catch (Exception ex) { LogManager.Warning($"清理直线装配对接点标记时发生异常: {ex.Message}"); } try { ClearAssemblyReferenceLine(); } catch (Exception ex) { LogManager.Warning($"清理直线装配参考线时发生异常: {ex.Message}"); } // 确保停止任何活动的点击工具 try { if (_pathPlanningManager != null) { _pathPlanningManager.StopClickTool(); } } catch (Exception ex) { LogManager.Warning($"停止点击工具时发生异常: {ex.Message}"); } LogManager.Info("PathEditingViewModel资源清理完成"); } catch (Exception ex) { LogManager.Error($"PathEditingViewModel清理失败: {ex.Message}"); } } /// /// 释放资源 (IDisposable implementation) /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// 释放托管和非托管资源 /// /// 是否释放托管资源 protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { try { // 调用现有的清理逻辑 Cleanup(); LogManager.Info("PathEditingViewModel已正确释放资源 (IDisposable)"); } catch (Exception ex) { LogManager.Error($"PathEditingViewModel释放资源时发生异常: {ex.Message}", ex); } } _disposed = true; } } #endregion } /// /// 物体参数类 - 用于传递物体尺寸信息给路径规划算法 /// public class ObjectParameters { public double Length { get; set; } public double Width { get; set; } public double Height { get; set; } public double SafetyMargin { get; set; } } }