NavisworksTransport/src/UI/WPF/ViewModels/PathEditingViewModel.cs

7117 lines
323 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

using System;
using System.Collections.Generic;
using System.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
{
/// <summary>
/// 路径策略选项辅助类
/// </summary>
public class PathStrategyOption
{
/// <summary>
/// 策略值
/// </summary>
public PathStrategy Value { get; set; }
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// 详细描述
/// </summary>
public string Description { get; set; }
}
/// <summary>
/// Rail 构型选项辅助类
/// </summary>
/// <typeparam name="T">枚举类型</typeparam>
public class RailConfigOption<T>
{
/// <summary>
/// 配置值
/// </summary>
public T Value { get; set; }
/// <summary>
/// 显示名称
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// 详细描述
/// </summary>
public string Description { get; set; }
}
/// <summary>
/// 路径编辑页面专用ViewModel
/// </summary>
public class PathEditingViewModel : ViewModelBase, IDisposable
{
#region
private PathPlanningManager _pathPlanningManager;
private readonly UIStateManager _uiStateManager;
private readonly PathDataManager _pathDataManager;
// 路径集合
private ObservableCollection<PathRouteViewModel> _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<PathStrategyOption> _pathStrategyOptions;
private ObservableCollection<RailConfigOption<RailMountMode>> _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<Point3D> _assemblyInstallationSeedPoints => _railAssemblyContext.InstallationSeedPoints;
private List<Point3D> _assemblyEndFaceSeedPoints => _railAssemblyContext.EndFaceSeedPoints;
// 自动路径起点和终点的路径对象引用用于正确的ID管理
private PathRoute _autoPathStartPointRoute = null;
private PathRoute _autoPathEndPointRoute = null;
// 资源清理管理
private bool _disposed = false;
// 多层吊装模式
private bool _isMultiLevelHoistingMode = false;
private ObservableCollection<HoistingLevelItem> _multiLevelItems = new ObservableCollection<HoistingLevelItem>();
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<PathRouteViewModel> 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 (_pathPlanningManager.PathEditState == PathEditState.Creating)
{
LogManager.Info($"UI路径切换当前处于新建路径流程跳过自动视角调整: {value.Name}");
}
// 自动调整视角到路径中心(只有路径有点时才调整)
else 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<RailConfigOption<RailMountMode>> 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));
}
}
/// <summary>
/// 更新路径可视化显示:清理其他路径,只显示当前选中的路径
/// </summary>
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<HoistingLevelItem> 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
/// <summary>
/// 是否显示可通行网格点
/// </summary>
public bool ShowWalkableGrid
{
get => _showWalkableGrid;
set
{
if (SetProperty(ref _showWalkableGrid, value))
{
OnGridVisualizationChanged();
}
}
}
/// <summary>
/// 是否显示障碍物网格点
/// </summary>
public bool ShowObstacleGrid
{
get => _showObstacleGrid;
set
{
if (SetProperty(ref _showObstacleGrid, value))
{
OnGridVisualizationChanged();
}
}
}
/// <summary>
/// 是否显示未知区域网格点
/// </summary>
public bool ShowUnknownGrid
{
get => _showUnknownGrid;
set
{
if (SetProperty(ref _showUnknownGrid, value))
{
OnGridVisualizationChanged();
}
}
}
/// <summary>
/// 是否显示门网格点
/// </summary>
public bool ShowDoorGrid
{
get => _showDoorGrid;
set
{
if (SetProperty(ref _showDoorGrid, value))
{
OnGridVisualizationChanged();
}
}
}
/// <summary>
/// 网格点类型
/// </summary>
public GridPointType GridPointType
{
get => _gridPointType;
set
{
if (SetProperty(ref _gridPointType, value))
{
OnPropertyChanged(nameof(IsRectanglePointType));
OnPropertyChanged(nameof(IsCirclePointType));
OnGridPointTypeChanged();
}
}
}
/// <summary>
/// 是否使用立方体点类型
/// </summary>
public bool IsRectanglePointType
{
get => _gridPointType == GridPointType.Rectangle;
set
{
if (value)
{
GridPointType = GridPointType.Rectangle;
}
}
}
/// <summary>
/// 是否使用圆形点类型
/// </summary>
public bool IsCirclePointType
{
get => _gridPointType == GridPointType.Circle;
set
{
if (value)
{
GridPointType = GridPointType.Circle;
}
}
}
/// <summary>
/// 是否显示控制点连线(独立开关)
/// </summary>
public bool ShowControlVisualization
{
get => _showControlVisualization;
set
{
if (SetProperty(ref _showControlVisualization, value))
{
OnControlVisualizationChanged();
}
}
}
/// <summary>
/// 是否显示路径线/地面连线(独立开关)
/// </summary>
public bool ShowPathLines
{
get => _showPathLines;
set
{
// 吊装和空轨路径不能使用路径线
if (value && !CanUsePathLines)
{
LogManager.Warning("[路径可视化] 吊装和空轨路径不能使用路径线");
return;
}
if (SetProperty(ref _showPathLines, value))
{
OnPathVisualizationModeChanged();
}
}
}
/// <summary>
/// 是否显示通行空间(独立开关)
/// </summary>
public bool ShowObjectSpace
{
get => _showObjectSpace;
set
{
if (SetProperty(ref _showObjectSpace, value))
{
OnPathVisualizationModeChanged();
}
}
}
/// <summary>
/// 当前路径是否可以使用路径线
/// 地面路径可以使用路径线,吊装和空轨路径不能使用路径线
/// </summary>
public bool CanUsePathLines
{
get
{
if (SelectedPathRoute == null) return true;
// 只有地面路径可以使用路径线
return SelectedPathRoute.PathType == PathType.Ground;
}
}
/// <summary>
/// 是否使用标准连线模式(向后兼容,根据独立开关计算)
/// </summary>
public bool IsStandardLineMode
{
get => _showPathLines && !_showObjectSpace;
set
{
if (value)
{
ShowPathLines = true;
ShowObjectSpace = false;
}
}
}
/// <summary>
/// 是否使用物体通行空间模式(向后兼容,等同于 ShowObjectSpace
/// </summary>
public bool IsObjectSpaceMode
{
get => _showObjectSpace;
set => ShowObjectSpace = value;
}
/// <summary>
/// 是否使用带状连线模式(向后兼容,等同于 ShowPathLines
/// </summary>
public bool IsRibbonLineMode
{
get => _showPathLines;
set => ShowPathLines = value;
}
#endregion
/// <summary>
/// 路径策略选项集合
/// </summary>
public ObservableCollection<PathStrategyOption> PathStrategyOptions
{
get => _pathStrategyOptions;
}
/// <summary>
/// 当前选择的路径策略
/// </summary>
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
/// <summary>
/// 构造函数需要传入主ViewModel以支持统一状态栏
/// </summary>
/// <param name="mainViewModel">主ViewModel用于统一状态栏</param>
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<PathRouteViewModel>();
// 初始化路径策略选项
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
/// <summary>
/// 初始化路径策略选项
/// </summary>
private void InitializePathStrategyOptions()
{
_pathStrategyOptions = new ObservableCollection<PathStrategyOption>
{
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);
}
/// <summary>
/// 初始化 Rail 构型选项
/// </summary>
private void InitializeRailConfigOptions()
{
_railMountModeOptions = new ObservableCollection<RailConfigOption<RailMountMode>>
{
new RailConfigOption<RailMountMode>
{
Value = RailMountMode.UnderRail,
DisplayName = "轨下安装",
Description = "安装头位于双轨下方,构件从轨道平面下侧连接"
},
new RailConfigOption<RailMountMode>
{
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;
}
/// <summary>
/// 从配置文件加载参数
/// </summary>
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
/// <summary>
/// 订阅配置变更事件
/// </summary>
private void SubscribeToConfigChanges()
{
try
{
// 订阅特定类别的配置变更事件
ConfigManager.Instance.CategoryConfigurationChanged += OnCategoryConfigurationChanged;
LogManager.Info("PathEditingViewModel 已订阅配置变更事件");
}
catch (Exception ex)
{
LogManager.Error($"订阅配置变更事件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 取消订阅配置变更事件
/// </summary>
private void UnsubscribeFromConfigChanges()
{
try
{
ConfigManager.Instance.CategoryConfigurationChanged -= OnCategoryConfigurationChanged;
LogManager.Info("PathEditingViewModel 已取消订阅配置变更事件");
}
catch (Exception ex)
{
LogManager.Warning($"取消订阅配置变更事件时发生异常: {ex.Message}");
}
}
/// <summary>
/// 配置变更事件处理
/// </summary>
private void OnCategoryConfigurationChanged(object sender, Tuple<ConfigCategory, ConfigurationChangedEventArgs> 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<HoistingLevelItem>((item) => ExecuteDeleteMultiLevel(item));
PickMultiLevelTargetCommand = new RelayCommand<HoistingLevelItem>((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<PathPointViewModel>(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<PathPointViewModel>(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);
}
/// <summary>
/// 根据 Rail 路径终点和路径长度计算起点位置。
/// 起点位于从终点沿光轴方向(远离球心方向)延伸路径长度的位置。
/// </summary>
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<Point3D> { 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<Point3D> { 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<Point3D> { 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<Point3D> 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<Point3D> { 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("路径规划管理器未初始化");
}
}, "新建传统空轨路径");
}
/// <summary>
/// 执行新建吊装路径命令
/// </summary>
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
/// <summary>
/// 执行新建多层阶梯式吊装路径命令 - 启用页签模式
/// </summary>
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("[多层吊装] 页签模式已启用");
}, "新建多层吊装路径");
}
/// <summary>
/// 执行选择多层吊装起点命令
/// </summary>
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("[多层吊装] 开始选择起点");
}, "选择多层吊装起点");
}
/// <summary>
/// 执行选择多层吊装终点命令
/// </summary>
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("[多层吊装] 开始选择终点");
}, "选择多层吊装终点");
}
/// <summary>
/// 执行添加层级命令
/// </summary>
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},请设置目标位置");
}
/// <summary>
/// 执行删除层级命令
/// </summary>
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}个层级");
}
/// <summary>
/// 执行拾取层级目标位置命令
/// </summary>
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}的目标位置");
}
/// <summary>
/// 执行创建多层吊装路径命令
/// </summary>
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<HoistingLevel>();
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<PathRoute> 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);
}
}, "创建多层吊装路径");
}
/// <summary>
/// 执行取消多层吊装命令
/// </summary>
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("[多层吊装] 页签模式已取消");
}
/// <summary>
/// 更新多层吊装统计信息
/// </summary>
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();
}
/// <summary>
/// 多层吊装路径鼠标点击处理
/// </summary>
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;
}
}
/// <summary>
/// 渲染层级目标位置标记
/// </summary>
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}目标标记已渲染");
}
/// <summary>
/// 渲染多层吊装路径的点标记
/// </summary>
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
/// <summary>
/// 提取并渲染所有空轨模型的基准路径
/// </summary>
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);
}
}
/// <summary>
/// 从Core路径创建PathPointViewModel列表统一处理吊装路径属性
/// </summary>
private List<PathPointViewModel> CreatePathPointViewModelsFromCoreRoute(PathRoute coreRoute)
{
var points = new List<PathPointViewModel>();
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;
}
/// <summary>
/// 更新吊装路径的关联点UI层
/// </summary>
/// <param name="pathRoute">路径</param>
/// <param name="modifiedPointIndex">修改的点的索引</param>
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;
}
}
/// <summary>
/// 更新吊装路径的关联点Core层
/// </summary>
/// <param name="coreRoute">Core路径</param>
/// <param name="modifiedPointIndex">修改的点的索引</param>
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为空");
}
}
});
}, "修改路径点");
}
/// <summary>
/// 取消修改路径点
/// </summary>
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<ImportPathResult>)?.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<ExportPathResult>)?.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<PathRoute> { 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<ExportPathResult>)?.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}");
}
}, "导出选中路径");
}
/// <summary>
/// 生成导航地图的纯业务逻辑方法
/// </summary>
/// <param name="renderStyle">渲染样式</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="format">图像格式</param>
/// <returns>生成的图像数据</returns>
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;
}
}
/// <summary>
/// 刷新路径集合显示
/// </summary>
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
/// <summary>
/// 处理自动路径规划的鼠标点击事件
/// </summary>
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}");
}
}
}
/// <summary>
/// 设置自动路径规划的起点
/// </summary>
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})");
}, "设置起点");
}
/// <summary>
/// 设置自动路径规划的终点
/// </summary>
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
/// <summary>
/// 同步物体参数到渲染插件
/// </summary>
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);
}
}
/// <summary>
/// 使用动画页签的物体参数同步到渲染插件
/// </summary>
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
/// <summary>
/// 安全执行异步操作
/// </summary>
private async Task SafeExecuteAsync(Func<Task> action, string operationName = "未知操作")
{
try
{
await action();
}
catch (Exception ex)
{
LogManager.Error($"{operationName}发生异常: {ex.Message}", ex);
UpdateMainStatus($"{operationName}失败: {ex.Message}");
}
}
/// <summary>
/// 网格可视化设置变更事件处理
/// </summary>
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("网格可视化设置应用失败");
}
}
/// <summary>
/// 网格点类型变更事件处理
/// </summary>
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("网格点类型应用失败");
}
}
/// <summary>
/// 根据路径类型自动设置可视化模式
/// - 地面路径:控制点连线 + 路径线
/// - 吊装和空轨:控制点连线 + 通行空间(不能使用路径线)
/// </summary>
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);
}
}
/// <summary>
/// 路径可视化模式变更事件处理
/// </summary>
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("路径可视化模式更新失败");
}
}
/// <summary>
/// 控制点可视化变更事件处理
/// </summary>
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("控制点可视化设置失败");
}
}
/// <summary>
/// 清理自动路径相关的事件订阅
/// </summary>
private void CleanupAutoPathEventSubscriptions()
{
try
{
// 简单安全地移除事件订阅
// 由于C#事件处理机制,多次取消订阅同一个处理程序是安全的
// 即使处理程序未订阅,取消操作也不会抛出异常
PathClickToolPlugin.MouseClicked -= OnAutoPathMouseClicked;
}
catch (Exception ex)
{
LogManager.Error($"[事件清理] 清理自动路径事件订阅失败: {ex.Message}", ex);
// 记录异常但不抛出,避免影响主流程
}
}
/// <summary>
/// 清理临时的自动路径起点和终点标记
/// </summary>
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事件处理
/// <summary>
/// 订阅PathPlanningManager事件
/// </summary>
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);
}
}
/// <summary>
/// 取消订阅PathPlanningManager事件
/// </summary>
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);
}
}
/// <summary>
/// 处理路径点操作事件(手工路径编辑的核心)
/// </summary>
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);
}
}
/// <summary>
/// 处理路径点列表更新事件
/// </summary>
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);
}
}
/// <summary>
/// 处理当前路径变更事件
/// </summary>
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);
}
}
/// <summary>
/// 处理编辑状态变更事件
/// </summary>
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);
}
}
/// <summary>
/// 处理路径生成事件(用于自动路径)
/// </summary>
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);
}
}
/// <summary>
/// 处理路径加载完成事件(用于数据库加载)
/// </summary>
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);
}
}
/// <summary>
/// 处理路径规划错误事件
/// </summary>
/// <summary>
/// 处理路径规划错误事件
/// </summary>
/// <summary>
/// 处理路径规划错误事件
/// </summary>
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
/// <summary>
/// 强制重新初始化ToolPlugin以确保获得鼠标焦点
/// </summary>
/// <param name="subscribeToEvents">是否订阅鼠标事件新建路径需要true自动路径选点需要false</param>
/// <returns>初始化是否成功</returns>
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}");
}
}
/// <summary>
/// 释放资源 (IDisposable implementation)
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// 释放托管和非托管资源
/// </summary>
/// <param name="disposing">是否释放托管资源</param>
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
}
/// <summary>
/// 物体参数类 - 用于传递物体尺寸信息给路径规划算法
/// </summary>
public class ObjectParameters
{
public double Length { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public double SafetyMargin { get; set; }
}
}