From 115d70db6697ded0191f15ee9d736347c3754b17 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sat, 28 Mar 2026 14:45:24 +0800 Subject: [PATCH] Add rail start point repositioning --- src/UI/WPF/ViewModels/PathEditingViewModel.cs | 248 ++++++++++++++++-- src/UI/WPF/Views/PathEditingView.xaml | 6 + 2 files changed, 234 insertions(+), 20 deletions(-) diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index 8700eed..35213ad 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -142,6 +142,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels private RailMountMode _assemblyMountMode = RailMountMode.UnderRail; private bool _hasAssemblyTerminalObject; private bool _isSelectingAssemblyStartPoint; + private bool _isEditingSelectedRailStartPoint; private bool _isSelectingAssemblyEndFacePoints; private bool _isSelectingAssemblyInstallationPoint; private bool _hasAssemblyEndFaceAnalysis; @@ -214,6 +215,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels { // 路径变更时清除选中的路径点 SelectedPathPoint = null; + ClearAssemblyReferenceLineVisuals(); ClearAssemblyInstallationReferenceVisuals(); // 🔧 修复:同步PathPlanningManager的CurrentRoute @@ -227,6 +229,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (coreRoute.PathType == PathType.Rail) { AssemblyMountMode = coreRoute.RailMountMode; + _assemblyAnchorVerticalOffsetInMeters = UnitsConverter.ConvertToMeters(coreRoute.RailNormalOffset); + OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); } // 🔥 先设置可视化模式(在 SetCurrentRoute 之前,确保事件触发时状态已就绪) @@ -278,6 +282,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels OnPropertyChanged(nameof(IsRailRouteSelected)); OnPropertyChanged(nameof(SelectedRailMountMode)); OnPropertyChanged(nameof(SelectedRailNormalOffsetInMeters)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint)); OnPropertyChanged(nameof(AssemblyInstallationPointButtonText)); if (!CanUsePathLines && ShowPathLines) @@ -388,6 +393,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels return; } + _assemblyAnchorVerticalOffsetInMeters = value; + OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); coreRoute.RailNormalOffset = offsetInModelUnits; if (IsAssemblyInstallationReferenceActiveForRoute(coreRoute)) { @@ -455,8 +462,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels } } RefreshAssemblyTerminalObjectInfo(); - RenderAssemblyAnchorMarker(); - RefreshAssemblyReferenceRodIfNeeded(); + if (coreRoute != null && + coreRoute.PathType == PathType.Rail && + AssemblyReferencePathManager.Instance.HasReferenceLine) + { + RefreshSelectedRailReferenceLineVisuals(); + } + else + { + RenderAssemblyAnchorMarker(); + RefreshAssemblyReferenceRodIfNeeded(); + } } } } @@ -1052,6 +1068,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels public ICommand CaptureAssemblyTerminalObjectCommand { get; private set; } public ICommand GenerateAssemblyReferenceRodCommand { get; private set; } public ICommand SelectAssemblyStartPointCommand { get; private set; } + public ICommand RepositionRailStartPointCommand { get; private set; } public ICommand SelectAssemblyInstallationPointCommand { get; private set; } public ICommand ClearAssemblyReferenceRodCommand { get; private set; } public ICommand AnalyzeAssemblyTerminalFaceCommand { get; private set; } @@ -1100,6 +1117,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels _pathPlanningManager != null && AssemblyReferencePathManager.Instance.HasReferenceLine && !IsSelectingAssemblyStartPoint; + public bool CanRepositionRailStartPoint => HasAssemblyTerminalObject && + IsRailRouteSelected && + SelectedPathRoute != null && + _pathPlanningManager != null && + AssemblyReferencePathManager.Instance.HasReferenceLine && + !IsSelectingAssemblyStartPoint && + !_isSelectingAssemblyEndFacePoints && + !_isSelectingAssemblyInstallationPoint; public bool CanSelectAssemblyInstallationPoint => HasAssemblyTerminalObject && IsRailRouteSelected && SelectedPathRoute != null && @@ -1272,6 +1297,26 @@ namespace NavisworksTransport.UI.WPF.ViewModels 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 && @@ -1284,20 +1329,74 @@ namespace NavisworksTransport.UI.WPF.ViewModels private int GetRailRouteInstallationPointIndex(PathRoute route) { - if (route?.Points == null || route.Points.Count == 0) + 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 -1; + return false; } - for (int i = route.Points.Count - 1; i >= 0; i--) + int startIndex = GetRailRouteStartPointIndex(route); + int endIndex = GetRailRouteInstallationPointIndex(route); + if (startIndex < 0 || endIndex < 0 || startIndex == endIndex) { - if (route.Points[i].Type == PathPointType.EndPoint) - { - return i; - } + return false; } - return route.Points.Count - 1; + 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; + } + + var coreRoute = GetSelectedCoreRoute(); + if (!TryBuildRailRouteReferenceLine(coreRoute, out AssemblyReferenceLine 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); + OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); + + if (!string.IsNullOrWhiteSpace(statusMessage)) + { + UpdateMainStatus(statusMessage); + } } private void PersistAssemblyInstallationReferenceToRailRoute(PathRoute route, string logMessage) @@ -1347,6 +1446,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels OnPropertyChanged(nameof(AssemblyInstallationPointButtonText)); RefreshAssemblyTerminalObjectInfo(); RefreshSelectedRailInstallationReferenceVisuals(); + RefreshSelectedRailReferenceLineVisuals(); } LogManager.Info($"[Rail构型] {route.Name}: {logMessage}"); @@ -1524,6 +1624,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels CaptureAssemblyTerminalObjectCommand = new RelayCommand(async () => await ExecuteCaptureAssemblyTerminalObjectAsync()); GenerateAssemblyReferenceRodCommand = new RelayCommand(async () => await ExecuteGenerateAssemblyReferenceRodAsync(), () => CanGenerateAssemblyReferenceRod); SelectAssemblyStartPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyStartPointAsync(), () => CanSelectAssemblyStartPoint); + RepositionRailStartPointCommand = new RelayCommand(async () => await ExecuteRepositionRailStartPointAsync(), () => CanRepositionRailStartPoint); SelectAssemblyInstallationPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyInstallationPointAsync(), () => CanSelectAssemblyInstallationPoint); ClearAssemblyReferenceRodCommand = new RelayCommand(() => ExecuteClearAssemblyReferenceRod()); AnalyzeAssemblyTerminalFaceCommand = new RelayCommand(async () => await ExecuteAnalyzeAssemblyTerminalFaceAsync(), () => CanAnalyzeAssemblyTerminalFace); @@ -1568,8 +1669,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels ClearAssemblyInstallationReferenceVisuals(); OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod)); OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint)); OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace)); + RefreshSelectedRailReferenceLineVisuals($"已捕获终点箱体: {AssemblyTerminalObjectName}"); UpdateMainStatus($"已捕获终点箱体: {AssemblyTerminalObjectName}"); LogManager.Info($"[直线装配] 已捕获终点箱体: {AssemblyTerminalObjectName}"); }, "捕获终点箱体"); @@ -1625,6 +1728,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels UpdateMainStatus("已生成终端安装辅助线,请在三维视图中确认终点锚点、方向、外端位置和辅助线贴合情况"); OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); LogManager.Info( $"[直线装配] 已生成参考杆: {AssemblyTerminalObjectName}, " + $"终点锚点=({referenceLine.EndPoint.X:F2}, {referenceLine.EndPoint.Y:F2}, {referenceLine.EndPoint.Z:F2}), " + @@ -1674,14 +1778,63 @@ namespace NavisworksTransport.UI.WPF.ViewModels }, "选择终端安装起点"); } + 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 路径。"); + } + + RefreshSelectedRailReferenceLineVisuals(); + if (!AssemblyReferencePathManager.Instance.HasReferenceLine) + { + throw new InvalidOperationException("当前 Rail 路径无法生成辅助线,请先检查路径起点和终点。"); + } + + CleanupAssemblyReferenceSelection(); + + _pathPlanningManager.DisableMouseHandling(); + _isEditingSelectedRailStartPoint = true; + IsSelectingAssemblyStartPoint = true; + OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); + + PathClickToolPlugin.MouseClicked -= OnAssemblyReferenceMouseClicked; + PathClickToolPlugin.MouseClicked += OnAssemblyReferenceMouseClicked; + + if (!ForceReinitializeToolPlugin(subscribeToEvents: false)) + { + CleanupAssemblyReferenceSelection(); + throw new InvalidOperationException("ToolPlugin 初始化失败,请重试。"); + } + + _pathPlanningManager.StartClickTool(PathPointType.StartPoint); + UpdateMainStatus("请在辅助线附近点击新的起点位置,系统会自动吸附到辅助线上并更新当前 Rail 路径。"); + LogManager.Info("[Rail构型] 已进入起点重选模式"); + }, "重选 Rail 路径起点"); + } + private void ExecuteClearAssemblyReferenceRod() { try { - HideAssemblyReferenceVisuals( - resetStartPointText: false, - statusMessage: "已隐藏终端安装辅助线", - logMessage: "[直线装配] 已隐藏辅助线"); + ClearAssemblyReferenceLineVisuals(); + UpdateMainStatus("已隐藏终端安装辅助线"); + LogManager.Info("[直线装配] 已隐藏辅助线"); } catch (Exception ex) { @@ -1724,6 +1877,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels _isSelectingAssemblyEndFacePoints = true; OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace)); OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint)); PathClickToolPlugin.MouseClicked -= OnAssemblyEndFaceMouseClicked; @@ -1763,6 +1917,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels _isSelectingAssemblyInstallationPoint = true; OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace)); OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint)); PathClickToolPlugin.MouseClicked -= OnAssemblyInstallationMouseClicked; @@ -1798,9 +1953,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels await SafeExecuteAsync(() => { - CreateAssemblyLinearRoute(projectedStartPoint); + if (_isEditingSelectedRailStartPoint) + { + UpdateSelectedRailRouteStartPoint(projectedStartPoint); + UpdateMainStatus("已根据辅助线更新当前 Rail 路径起点"); + } + else + { + CreateAssemblyLinearRoute(projectedStartPoint); + UpdateMainStatus("已根据辅助线起点生成终端安装路径"); + } + CleanupAssemblyReferenceSelection(); - UpdateMainStatus("已根据辅助线起点生成终端安装路径"); }, "生成终端安装路径"); } catch (Exception ex) @@ -1970,14 +2134,44 @@ namespace NavisworksTransport.UI.WPF.ViewModels $"{(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); - AssemblyReferencePathManager.Instance.HideReferenceRod(); - ClearAssemblyAnchorMarker(); - ClearAssemblyCenterGuideLine(); - ClearAssemblyReferenceLine(); + ClearAssemblyReferenceLineVisuals(); ClearAssemblyEndFaceAnalysisVisuals(); ClearAssemblyInstallationReferenceVisuals(); if (resetStartPointText) @@ -2717,6 +2911,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels renderPlugin.ClearRailBaseline(AssemblyCenterGuideLinePathId); } + private void ClearAssemblyReferenceLineVisuals() + { + AssemblyReferencePathManager.Instance.HideReferenceRod(); + ClearAssemblyAnchorMarker(); + ClearAssemblyCenterGuideLine(); + ClearAssemblyReferenceLine(); + OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); + } + private void ClearAssemblyEndFaceAnalysisVisuals() { var renderPlugin = PathPointRenderPlugin.Instance; @@ -2775,9 +2979,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels { PathClickToolPlugin.MouseClicked -= OnAssemblyReferenceMouseClicked; IsSelectingAssemblyStartPoint = false; + _isEditingSelectedRailStartPoint = false; _pathPlanningManager?.EnableMouseHandling(); _pathPlanningManager?.StopClickTool(); OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); } catch (Exception ex) { @@ -2796,6 +3002,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels ClearAssemblyInstallationReferenceVisuals(); OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace)); OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint)); OnPropertyChanged(nameof(AssemblyInstallationPointButtonText)); } @@ -2821,6 +3028,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels } OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace)); OnPropertyChanged(nameof(CanSelectAssemblyStartPoint)); + OnPropertyChanged(nameof(CanRepositionRailStartPoint)); OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint)); OnPropertyChanged(nameof(AssemblyInstallationPointButtonText)); } diff --git a/src/UI/WPF/Views/PathEditingView.xaml b/src/UI/WPF/Views/PathEditingView.xaml index b9c1eac..0249bdd 100644 --- a/src/UI/WPF/Views/PathEditingView.xaml +++ b/src/UI/WPF/Views/PathEditingView.xaml @@ -571,9 +571,15 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管 Style="{StaticResource ReadOnlyTextBoxStyle}"/>