Add rail start point repositioning

This commit is contained in:
tian 2026-03-28 14:45:24 +08:00
parent e4788c17a3
commit 115d70db66
2 changed files with 234 additions and 20 deletions

View File

@ -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));
}

View File

@ -571,9 +571,15 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
Style="{StaticResource ReadOnlyTextBoxStyle}"/>
<Button Content="{Binding AssemblyInstallationPointButtonText}"
Padding="10,2"
Margin="0,0,10,0"
Command="{Binding SelectAssemblyInstallationPointCommand}"
IsEnabled="{Binding CanSelectAssemblyInstallationPoint}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="重选起点"
Padding="10,2"
Command="{Binding RepositionRailStartPointCommand}"
IsEnabled="{Binding CanRepositionRailStartPoint}"
Style="{StaticResource SecondaryButtonStyle}"/>
</WrapPanel>
<Label Grid.Column="0"
Grid.Row="2"