From 1d71d36e5fb1195ada95add7d2777d787ad5eda4 Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Mon, 30 Mar 2026 22:13:16 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Drail=E8=B7=AF=E5=BE=84?=
=?UTF-8?q?=E7=9A=84=E7=BC=96=E8=BE=91=E7=8A=B6=E6=80=81=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
AGENTS.md | 44 ++++++
src/UI/WPF/ViewModels/PathEditingViewModel.cs | 138 ++++++++++++++++--
src/UI/WPF/Views/PathEditingView.xaml | 4 +-
src/Utils/ModelItemTransformHelper.cs | 4 +-
4 files changed, 173 insertions(+), 17 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index b58d67a..0bc6113 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -133,6 +133,50 @@
禁止再使用“本地坐标系”这种含糊说法。
+### 4.1.2 坐标命名规则
+
+以后凡是变量名、字段名、日志名里出现 `X/Y/Z`、正负轴、forward/up/side 等方向语义,必须在名字上直接带出所属坐标系,避免只看名字时无法判断语义层。
+
+允许的前缀示例:
+
+- `Host...`
+ - 宿主坐标系
+- `Canonical...`
+ - 内部坐标系
+- `Asset...`
+ - 资产坐标系
+- `Local...`
+ - 仅当这里明确表示“对象自身局部轴”时才允许使用
+
+正负轴命名示例:
+
+- `LocalPositiveX`
+- `LocalNegativeY`
+- `HostPositiveZ`
+- `CanonicalPositiveX`
+- `AssetPositiveY`
+
+方向/向量命名示例:
+
+- `hostForward`
+- `hostUp`
+- `canonicalForward`
+- `assetUp`
+- `localXAxis`
+- `localZAxis`
+
+禁止继续使用这类脱离坐标系语义的名字:
+
+- `PositiveX`
+- `NegativeZ`
+- `xAxis`
+- `upAxis`
+- `forwardAxis`
+
+除非变量名里已经明确出现了所属坐标系前缀。
+
+目标是做到:只看名字,就能知道这个方向、轴、向量到底属于宿主、内部、资产,还是对象自身局部轴。
+
### 4.1.1 Quaternion / Rotation3D 固定定义
这是项目级硬约束,不允许在任何修复中重新猜测、重新验证或临时改口:
diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
index 35213ad..a650edd 100644
--- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs
+++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
@@ -11,7 +11,6 @@ using System.IO;
using System.Numerics;
using Microsoft.Win32;
using Autodesk.Navisworks.Api;
-using Autodesk.Navisworks.Api.Plugins;
using NavisApplication = Autodesk.Navisworks.Api.Application;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Config;
@@ -21,7 +20,6 @@ using NavisworksTransport.UI.WPF.Models;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
using NavisworksTransport.Utils.GeometryAnalysis;
-using NavisworksTransport;
namespace NavisworksTransport.UI.WPF.ViewModels
{
@@ -145,6 +143,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private bool _isEditingSelectedRailStartPoint;
private bool _isSelectingAssemblyEndFacePoints;
private bool _isSelectingAssemblyInstallationPoint;
+ private bool _isEditingSelectedRailInstallationPoint;
+ private bool _isEditingSelectedRail;
private bool _hasAssemblyEndFaceAnalysis;
private bool _hasAssemblyInstallationReference;
private ModelItem _assemblyTerminalObject;
@@ -218,6 +218,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
ClearAssemblyReferenceLineVisuals();
ClearAssemblyInstallationReferenceVisuals();
+ // 重置 Rail 编辑状态
+ _isEditingSelectedRail = false;
+ OnPropertyChanged(nameof(AssemblyInstallationPointButtonText));
+
// 🔧 修复:同步PathPlanningManager的CurrentRoute
if (_pathPlanningManager != null && value != null)
{
@@ -1066,6 +1070,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand ExportPathCommand { get; private set; }
public ICommand SaveAsPathCommand { get; private set; }
public ICommand CaptureAssemblyTerminalObjectCommand { get; private set; }
+ public ICommand ReCaptureAssemblyTerminalObjectCommand { get; private set; }
public ICommand GenerateAssemblyReferenceRodCommand { get; private set; }
public ICommand SelectAssemblyStartPointCommand { get; private set; }
public ICommand RepositionRailStartPointCommand { get; private set; }
@@ -1291,6 +1296,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private bool HasSelectedRailInstallationReference()
{
+ // 只有在点击"重选箱体"进入编辑状态后才显示"重选安装点"
+ if (!_isEditingSelectedRail)
+ {
+ return false;
+ }
+
var coreRoute = GetSelectedCoreRoute();
return coreRoute != null &&
coreRoute.PathType == PathType.Rail &&
@@ -1376,11 +1387,30 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return;
}
- var coreRoute = GetSelectedCoreRoute();
- if (!TryBuildRailRouteReferenceLine(coreRoute, out AssemblyReferenceLine referenceLine))
+ AssemblyReferenceLine referenceLine;
+
+ if (_isEditingSelectedRail)
{
- ClearAssemblyReferenceLineVisuals();
- return;
+ // 编辑状态:使用默认杆长度构建参考线,方便选择更远的起点
+ try
+ {
+ referenceLine = BuildAssemblyReferenceLine();
+ }
+ catch
+ {
+ ClearAssemblyReferenceLineVisuals();
+ return;
+ }
+ }
+ else
+ {
+ // 非编辑状态:使用路径实际长度
+ var coreRoute = GetSelectedCoreRoute();
+ if (!TryBuildRailRouteReferenceLine(coreRoute, out referenceLine))
+ {
+ ClearAssemblyReferenceLineVisuals();
+ return;
+ }
}
AssemblyReferencePathManager.Instance.CreateOrUpdateReferenceRod(
@@ -1422,11 +1452,31 @@ namespace NavisworksTransport.UI.WPF.ViewModels
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 = new Point3D(
_assemblyInstallationPlaneNormal.X,
_assemblyInstallationPlaneNormal.Y,
@@ -1449,7 +1499,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
RefreshSelectedRailReferenceLineVisuals();
}
- LogManager.Info($"[Rail构型] {route.Name}: {logMessage}");
+ LogManager.Info($"[Rail构型] {route.Name}: {logMessage},起点已同步更新为 ({newStartPoint.X:F3}, {newStartPoint.Y:F3}, {newStartPoint.Z:F3})");
}
private void RefreshSelectedRailInstallationReferenceVisuals()
@@ -1622,6 +1672,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
ExportPathCommand = new RelayCommand(async () => await ExecuteExportPathAsync(), () => CanExecuteExportPath);
SaveAsPathCommand = new RelayCommand(async () => await ExecuteSaveAsPathAsync(), () => CanExecuteSaveAsPath);
CaptureAssemblyTerminalObjectCommand = new RelayCommand(async () => await ExecuteCaptureAssemblyTerminalObjectAsync());
+ ReCaptureAssemblyTerminalObjectCommand = new RelayCommand(async () => await ExecuteReCaptureAssemblyTerminalObjectAsync());
GenerateAssemblyReferenceRodCommand = new RelayCommand(async () => await ExecuteGenerateAssemblyReferenceRodAsync(), () => CanGenerateAssemblyReferenceRod);
SelectAssemblyStartPointCommand = new RelayCommand(async () => await ExecuteSelectAssemblyStartPointAsync(), () => CanSelectAssemblyStartPoint);
RepositionRailStartPointCommand = new RelayCommand(async () => await ExecuteRepositionRailStartPointAsync(), () => CanRepositionRailStartPoint);
@@ -1656,6 +1707,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
throw new InvalidOperationException("当前选择对象无效,请重新选择终点箱体");
}
+ // 注意:不在这里设置 _isEditingSelectedRail
+ // 新建路径时保持 false,只有点击"重选箱体"时才设置为 true
+
_assemblyTerminalObject = selectedItem;
_assemblyStartPoint = Point3D.Origin;
HasAssemblyTerminalObject = true;
@@ -1672,12 +1726,24 @@ namespace NavisworksTransport.UI.WPF.ViewModels
OnPropertyChanged(nameof(CanRepositionRailStartPoint));
OnPropertyChanged(nameof(CanSelectAssemblyInstallationPoint));
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
- RefreshSelectedRailReferenceLineVisuals($"已捕获终点箱体: {AssemblyTerminalObjectName}");
+ OnPropertyChanged(nameof(AssemblyInstallationPointButtonText));
+
+ // 新建路径时隐藏其他路径的辅助线
+ ClearAssemblyReferenceLineVisuals();
+
UpdateMainStatus($"已捕获终点箱体: {AssemblyTerminalObjectName}");
LogManager.Info($"[直线装配] 已捕获终点箱体: {AssemblyTerminalObjectName}");
}, "捕获终点箱体");
}
+ private async Task ExecuteReCaptureAssemblyTerminalObjectAsync()
+ {
+ // 先进入编辑状态,再执行捕获
+ _isEditingSelectedRail = true;
+ OnPropertyChanged(nameof(AssemblyInstallationPointButtonText));
+ await ExecuteCaptureAssemblyTerminalObjectAsync();
+ }
+
private void ClearNonGridPathVisualizations(string context)
{
if (PathPointRenderPlugin.Instance == null)
@@ -1915,6 +1981,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
_pathPlanningManager.DisableMouseHandling();
_isSelectingAssemblyInstallationPoint = true;
+
+ // 判断是编辑现有路径还是创建新路径
+ var selectedRailRoute = GetSelectedCoreRoute();
+ _isEditingSelectedRailInstallationPoint = selectedRailRoute != null && selectedRailRoute.PathType == PathType.Rail;
+
OnPropertyChanged(nameof(CanAnalyzeAssemblyTerminalFace));
OnPropertyChanged(nameof(CanSelectAssemblyStartPoint));
OnPropertyChanged(nameof(CanRepositionRailStartPoint));
@@ -2048,12 +2119,22 @@ namespace NavisworksTransport.UI.WPF.ViewModels
BuildAndRenderAssemblyInstallationReference(
_assemblyInstallationSeedPoints[0],
_assemblyInstallationSeedPoints[1]);
- var selectedRailRoute = GetSelectedCoreRoute();
- if (selectedRailRoute != null && selectedRailRoute.PathType == PathType.Rail)
+
+ if (_isEditingSelectedRailInstallationPoint)
{
- PersistAssemblyInstallationReferenceToRailRoute(
- selectedRailRoute,
- "已重选安装点并同步更新 Rail 路径安装参数");
+ // 编辑现有路径:更新安装点并同步起点
+ var selectedRailRoute = GetSelectedCoreRoute();
+ if (selectedRailRoute != null && selectedRailRoute.PathType == PathType.Rail)
+ {
+ PersistAssemblyInstallationReferenceToRailRoute(
+ selectedRailRoute,
+ "已重选安装点并同步更新 Rail 路径安装参数");
+ }
+ }
+ else
+ {
+ // 创建新路径:生成参考线,等待用户选择起点
+ LogManager.Info("[直线装配] 安装点已确定,请继续选择起点以生成新路径");
}
CleanupAssemblyInstallationSelection();
}, "处理安装点拾取");
@@ -2224,6 +2305,36 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return adapter.FromCanonicalPoint(canonicalAnchorPoint);
}
+ ///
+ /// 根据 Rail 路径终点和路径长度计算起点位置。
+ /// 起点位于从终点沿光轴方向(远离球心方向)延伸路径长度的位置。
+ ///
+ 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 ||
@@ -2997,6 +3108,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
PathClickToolPlugin.MouseClicked -= OnAssemblyInstallationMouseClicked;
_isSelectingAssemblyInstallationPoint = false;
+ _isEditingSelectedRailInstallationPoint = false;
_pathPlanningManager?.EnableMouseHandling();
_pathPlanningManager?.StopClickTool();
ClearAssemblyInstallationReferenceVisuals();
diff --git a/src/UI/WPF/Views/PathEditingView.xaml b/src/UI/WPF/Views/PathEditingView.xaml
index 0249bdd..b17ac0d 100644
--- a/src/UI/WPF/Views/PathEditingView.xaml
+++ b/src/UI/WPF/Views/PathEditingView.xaml
@@ -560,10 +560,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
Orientation="Horizontal"
VerticalAlignment="Center"
ItemHeight="28">
-