7117 lines
323 KiB
C#
7117 lines
323 KiB
C#
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; }
|
||
}
|
||
}
|