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}"/>
+