From 1cf4fe59673ec91cb22b4273a6a7da0eb75a8f44 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Mon, 23 Mar 2026 12:25:00 +0800 Subject: [PATCH] Align terminal rail normals and object space rendering --- NavisworksTransport.UnitTests.csproj | 1 + TransportPlugin.csproj | 1 + ...semblyInstallationReferenceBuilderTests.cs | 55 +++++ .../CanonicalRailPoseBuilderTests.cs | 17 ++ .../ObjectSpaceOrientationHelperTests.cs | 40 ++++ src/Core/Animation/PathAnimationManager.cs | 2 + src/Core/PathPlanningModels.cs | 7 + src/Core/PathPointRenderPlugin.cs | 210 +++++------------- src/UI/WPF/ViewModels/PathEditingViewModel.cs | 80 ++++++- .../AssemblyInstallationReferenceBuilder.cs | 67 ++++++ .../CanonicalRailPoseBuilder.cs | 79 ++++++- .../ObjectSpaceOrientationHelper.cs | 92 ++++++++ src/Utils/RailPathPoseHelper.cs | 90 +++++++- 13 files changed, 568 insertions(+), 173 deletions(-) create mode 100644 UnitTests/CoordinateSystem/ObjectSpaceOrientationHelperTests.cs create mode 100644 src/Utils/CoordinateSystem/ObjectSpaceOrientationHelper.cs diff --git a/NavisworksTransport.UnitTests.csproj b/NavisworksTransport.UnitTests.csproj index 6ae959b..d79b769 100644 --- a/NavisworksTransport.UnitTests.csproj +++ b/NavisworksTransport.UnitTests.csproj @@ -56,6 +56,7 @@ + diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj index ce75558..af09c90 100644 --- a/TransportPlugin.csproj +++ b/TransportPlugin.csproj @@ -338,6 +338,7 @@ + diff --git a/UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs b/UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs index 55f7396..a058cdf 100644 --- a/UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs +++ b/UnitTests/CoordinateSystem/AssemblyInstallationReferenceBuilderTests.cs @@ -27,6 +27,61 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem Assert.AreEqual(0f, Vector3.Dot(result.PlaneNormal, result.PlaneSpanDirection), 1e-4f); } + [TestMethod] + public void BuildFromTwoPoints_ShouldCreatePlaneParallelToOpticalAxisAndContainBothPoints() + { + Vector3 axisBase = new Vector3(0f, 0f, 0f); + Vector3 axisDirection = Vector3.UnitX; + Vector3 pickPoint1 = new Vector3(2f, 3f, 4f); + Vector3 pickPoint2 = new Vector3(6f, 3f, 6f); + + AssemblyInstallationReferenceResult result = + AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint1, pickPoint2); + + Assert.AreEqual(3f, result.OffsetDistance, 1e-4f); + Assert.AreEqual(4f, result.AnchorPoint.X, 1e-4f); + Assert.AreEqual(3f, result.AnchorPoint.Y, 1e-4f); + Assert.AreEqual(0f, result.AnchorPoint.Z, 1e-4f); + Assert.AreEqual(0f, Vector3.Dot(result.PlaneNormal, result.OpticalAxisDirection), 1e-4f); + Assert.AreEqual(0f, Vector3.Dot(result.PlaneSpanDirection, result.OpticalAxisDirection), 1e-4f); + Assert.AreEqual(0f, Vector3.Dot(result.PlaneNormal, result.PlaneSpanDirection), 1e-4f); + Assert.AreEqual(0f, Vector3.Dot(pickPoint1 - result.AnchorPoint, result.PlaneNormal), 1e-4f); + Assert.AreEqual(0f, Vector3.Dot(pickPoint2 - result.AnchorPoint, result.PlaneNormal), 1e-4f); + } + + [TestMethod] + public void BuildFromTwoPoints_ShouldUseOpticalAxisProjectionAsInstallationCenterLine() + { + Vector3 axisBase = new Vector3(0f, 0f, 0f); + Vector3 axisDirection = Vector3.UnitX; + Vector3 pickPoint1 = new Vector3(2f, 3f, 4f); + Vector3 pickPoint2 = new Vector3(6f, 3f, 6f); + + AssemblyInstallationReferenceResult result = + AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint1, pickPoint2); + + Assert.AreEqual(0f, result.InstallLineBasePoint.X, 1e-4f); + Assert.AreEqual(3f, result.InstallLineBasePoint.Y, 1e-4f); + Assert.AreEqual(0f, result.InstallLineBasePoint.Z, 1e-4f); + Assert.AreEqual(4f, result.AnchorPoint.X, 1e-4f); + Assert.AreEqual(3f, result.AnchorPoint.Y, 1e-4f); + Assert.AreEqual(0f, result.AnchorPoint.Z, 1e-4f); + } + + [TestMethod] + public void BuildFromTwoPoints_WhenPlanePointsAreNearlyAxisAligned_ShouldThrow() + { + Vector3 axisBase = new Vector3(0f, 0f, 0f); + Vector3 axisDirection = Vector3.UnitX; + Vector3 pickPoint1 = new Vector3(2f, 3f, 4f); + Vector3 pickPoint2 = new Vector3(6f, 3.00001f, 4.00001f); + + InvalidOperationException ex = Assert.ThrowsException( + () => AssemblyInstallationReferenceBuilder.Build(axisBase, axisDirection, pickPoint1, pickPoint2)); + + StringAssert.Contains(ex.Message, "无法确定安装参考面"); + } + [TestMethod] public void Build_WhenPickPointTooCloseToOpticalAxis_ShouldThrow() { diff --git a/UnitTests/CoordinateSystem/CanonicalRailPoseBuilderTests.cs b/UnitTests/CoordinateSystem/CanonicalRailPoseBuilderTests.cs index 0104b64..deef6f7 100644 --- a/UnitTests/CoordinateSystem/CanonicalRailPoseBuilderTests.cs +++ b/UnitTests/CoordinateSystem/CanonicalRailPoseBuilderTests.cs @@ -137,6 +137,23 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem AssertVector(mappedUp, 0, 0, 1); } + [TestMethod] + public void PreferredNormal_ShouldOverrideCanonicalUpForRailFrame() + { + bool ok = CanonicalRailPoseBuilder.TryCreateLocalFrame( + new Vector3(0, 0, 0), + new Vector3(1, 0, 0), + new Vector3(2, 0, 0), + Vector3.UnitZ, + Vector3.UnitY, + out RailLocalFrame frame); + + Assert.IsTrue(ok); + AssertVector(frame.Forward, 1, 0, 0); + AssertVector(frame.Normal, 0, 1, 0); + AssertVector(frame.Lateral, 0, 0, -1); + } + private static void AssertColumn(Matrix4x4 matrix, int column, double x, double y, double z) { switch (column) diff --git a/UnitTests/CoordinateSystem/ObjectSpaceOrientationHelperTests.cs b/UnitTests/CoordinateSystem/ObjectSpaceOrientationHelperTests.cs new file mode 100644 index 0000000..a15baf4 --- /dev/null +++ b/UnitTests/CoordinateSystem/ObjectSpaceOrientationHelperTests.cs @@ -0,0 +1,40 @@ +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NavisworksTransport.Utils.CoordinateSystem; + +namespace NavisworksTransport.UnitTests.CoordinateSystem +{ + [TestClass] + public class ObjectSpaceOrientationHelperTests + { + [TestMethod] + public void CalculateAxes_ShouldHonorProvidedUpReference() + { + var segmentDirection = new Vector3(10f, 0f, 0f); + var upReference = new Vector3(0f, 1f, 0f); + + var (_, up) = ObjectSpaceOrientationHelper.CalculateAxes(segmentDirection, upReference); + + Assert.AreEqual(0.0, up.X, 1e-6); + Assert.AreEqual(1.0, System.Math.Abs(up.Y), 1e-6); + Assert.AreEqual(0.0, up.Z, 1e-6); + } + + [TestMethod] + public void CalculateAxes_ForVerticalSegment_ShouldUseHorizontalDirectionWithProvidedUpReference() + { + var segmentDirection = new Vector3(0f, 10f, 0f); + var upReference = new Vector3(0f, 1f, 0f); + var horizontalDirection = new Vector3(1f, 0f, 0f); + + var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes(segmentDirection, upReference, horizontalDirection); + + Assert.AreEqual(0.0, right.X, 1e-6); + Assert.AreEqual(0.0, right.Y, 1e-6); + Assert.AreEqual(1.0, right.Z, 1e-6); + Assert.AreEqual(1.0, up.X, 1e-6); + Assert.AreEqual(0.0, up.Y, 1e-6); + Assert.AreEqual(0.0, up.Z, 1e-6); + } + } +} diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index 6298b25..e6ef4fa 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -671,6 +671,7 @@ namespace NavisworksTransport.Core.Animation LogManager.Debug($"[移动到起点] Rail路径调整: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), 物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), 物体高度={objectHeight:F2}, 安装={_route.RailMountMode}, 对接={(PathRoute.IsTopPayloadAnchorForMountMode(_route.RailMountMode) ? "顶面对接" : "底面对接")}"); if (RailPathPoseHelper.TryCreateRailRotation( + _route, previousPoint, _pathPoints[0], nextPoint, @@ -1034,6 +1035,7 @@ namespace NavisworksTransport.Core.Animation if (_route.PathType == PathType.Rail && RailPathPoseHelper.TryCreateRailRotation( + _route, previousFramePoint, framePosition, nextFramePoint, diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs index e3ba6bb..1a497b6 100644 --- a/src/Core/PathPlanningModels.cs +++ b/src/Core/PathPlanningModels.cs @@ -737,6 +737,12 @@ namespace NavisworksTransport /// public RailPathDefinitionMode RailPathDefinitionMode { get; set; } + /// + /// Rail 路径优先法向(宿主坐标)。 + /// 仅在存在显式安装面/业务法向时使用;为空时退回当前默认求解。 + /// + public Point3D RailPreferredNormal { get; set; } + // 数据库分析相关属性 /// /// 碰撞数量(从数据库加载) @@ -777,6 +783,7 @@ namespace NavisworksTransport IsCurved = false; RailMountMode = RailMountMode.UnderRail; RailPathDefinitionMode = RailPathDefinitionMode.LegacySuspensionPoint; + RailPreferredNormal = null; } /// diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs index 36b7d80..13983d2 100644 --- a/src/Core/PathPointRenderPlugin.cs +++ b/src/Core/PathPointRenderPlugin.cs @@ -297,6 +297,11 @@ namespace NavisworksTransport /// public Vector3D HorizontalDirection { get; set; } + /// + /// 通行空间的优先 up 方向(例如 Rail 安装面法向) + /// + public Vector3D UpDirection { get; set; } + public override string ToString() { return $"LineMarker[{FromIndex}->{ToIndex}, 类型={SegmentType}, 起点=({StartPoint.X:F2},{StartPoint.Y:F2},{StartPoint.Z:F2}), 终点=({EndPoint.X:F2},{EndPoint.Y:F2},{EndPoint.Z:F2})]"; @@ -1658,7 +1663,8 @@ namespace NavisworksTransport var endPoint = edge.SampledPoints.Last(); // 计算长方体的轴向量(right和up向量) - var (right, up) = CalculateCuboidAxes(startPoint, endPoint); + var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute); + var (right, up) = CalculateCuboidAxes(startPoint, endPoint, preferredUp); // 根据路径类型调整起点和终点位置 // 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移 @@ -1710,7 +1716,8 @@ namespace NavisworksTransport SampledPoints = edge.SampledPoints, Height = height, // 根据模式设置高度 Width = width, // 根据模式设置宽度 - Opacity = lineOpacity // 根据模式设置透明度 + Opacity = lineOpacity, // 根据模式设置透明度 + UpDirection = preferredUp }; visualization.PathLineMarkers.Add(lineMarker); } @@ -1736,7 +1743,8 @@ namespace NavisworksTransport var endPoint = edge.Trajectory.Te; // 计算长方体的轴向量(right和up向量) - var (right, up) = CalculateCuboidAxes(startPoint, endPoint); + var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute); + var (right, up) = CalculateCuboidAxes(startPoint, endPoint, preferredUp); // 根据路径类型调整起点和终点位置 // 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移 @@ -1818,7 +1826,8 @@ namespace NavisworksTransport SampledPoints = adjustedSampledPoints, Height = height, // 根据模式设置高度 Width = width, // 根据模式设置宽度 - Opacity = arcLineOpacity // 根据模式设置透明度 + Opacity = arcLineOpacity, // 根据模式设置透明度 + UpDirection = preferredUp }; visualization.PathLineMarkers.Add(arcMarker); @@ -1838,73 +1847,13 @@ namespace NavisworksTransport /// 路径段终点 /// 水平段方向向量(用于垂直路径渲染时确定通行空间方向) /// 元组(right向量,up向量) - private (Vector3D right, Vector3D up) CalculateCuboidAxes(Point3D startPoint, Point3D endPoint, Vector3D horizontalDirection = null) + private (Vector3D right, Vector3D up) CalculateCuboidAxes(Point3D startPoint, Point3D endPoint, Vector3D upReference = null, Vector3D horizontalDirection = null) { - // 计算路径段的方向向量 - var segmentDirection = new Vector3D( - endPoint.X - startPoint.X, - endPoint.Y - startPoint.Y, - endPoint.Z - startPoint.Z - ); - var segmentLength = Math.Sqrt( - segmentDirection.X * segmentDirection.X + - segmentDirection.Y * segmentDirection.Y + - segmentDirection.Z * segmentDirection.Z - ); - - // 归一化方向向量 - var normalizedDirection = new Vector3D( - segmentDirection.X / segmentLength, - segmentDirection.Y / segmentLength, - segmentDirection.Z / segmentLength - ); - - // 使用宿主 up 方向构造水平 right 向量,避免把世界 Z 写死到 Y-up 项目里。 - var hostUp = GetHostUpVector(); - var right = Cross(normalizedDirection, hostUp); - - // 如果方向向量接近宿主 up(垂直段),则使用水平段方向重新构造 right。 - double parallelToUp = Math.Abs( - normalizedDirection.X * hostUp.X + - normalizedDirection.Y * hostUp.Y + - normalizedDirection.Z * hostUp.Z); - if (parallelToUp > 0.9) - { - if (horizontalDirection != null) - { - right = Cross(horizontalDirection, hostUp); - } - else - { - right = Math.Abs(hostUp.X) < 0.9 - ? Normalize(Cross(hostUp, new Vector3D(1, 0, 0))) - : Normalize(Cross(hostUp, new Vector3D(0, 1, 0))); - } - } - - // 归一化right向量 - var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z); - if (rightLength > 0.001) - { - right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength); - } - - // 计算高度向量(垂直于方向向量和宽度向量)- 这是长方体的轴线方向 - // up = direction × right - var up = new Vector3D( - normalizedDirection.Y * right.Z - normalizedDirection.Z * right.Y, - normalizedDirection.Z * right.X - normalizedDirection.X * right.Z, - normalizedDirection.X * right.Y - normalizedDirection.Y * right.X - ); - - // 归一化up向量 - var upLength = Math.Sqrt(up.X * up.X + up.Y * up.Y + up.Z * up.Z); - if (upLength > 0.001) - { - up = new Vector3D(up.X / upLength, up.Y / upLength, up.Z / upLength); - } - - return (right, up); + return ObjectSpaceOrientationHelper.CalculateAxes( + startPoint, + endPoint, + upReference ?? GetHostUpVector(), + horizontalDirection); } /// @@ -2009,7 +1958,8 @@ namespace NavisworksTransport CalculatePassageSpaceParameters(visualization, isVerticalSegment, isTurn90Horizontal, isFirstOrLastSegment); // 计算长方体的轴向量(right和up向量) - var (right, up) = CalculateCuboidAxes(startPoint.Position, endPoint.Position, horizontalDirection); + var preferredUp = ResolveObjectSpaceUpDirection(visualization.PathRoute); + var (right, up) = CalculateCuboidAxes(startPoint.Position, endPoint.Position, preferredUp, horizontalDirection); // 根据路径类型调整起点和终点位置 // 关键修复:垂直偏移应该沿着长方体的轴线方向(up向量)平移,而不是简单地添加到Z坐标上 @@ -2099,7 +2049,8 @@ namespace NavisworksTransport Height = height, Width = width, Opacity = lineOpacity, - HorizontalDirection = horizontalDirection // 设置水平段方向向量 + HorizontalDirection = horizontalDirection, // 设置水平段方向向量 + UpDirection = preferredUp }; visualization.PathLineMarkers.Add(lineMarker); } @@ -2797,7 +2748,7 @@ namespace NavisworksTransport /// 宽度 /// 高度 /// 水平段方向向量(用于垂直路径渲染时确定通行空间方向) - private void RenderCuboidMarker(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, Vector3D horizontalDirection = null) + private void RenderCuboidMarker(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, Vector3D horizontalDirection = null, Vector3D upReference = null) { try { @@ -2817,47 +2768,11 @@ namespace NavisworksTransport // 归一化方向向量 direction = new Vector3D(direction.X / length, direction.Y / length, direction.Z / length); - var hostUp = GetHostUpVector(); - var right = Cross(direction, hostUp); - - double parallelToUp = Math.Abs( - direction.X * hostUp.X + - direction.Y * hostUp.Y + - direction.Z * hostUp.Z); - if (parallelToUp > 0.9) - { - if (horizontalDirection != null) - { - right = Cross(horizontalDirection, hostUp); - } - else - { - right = Math.Abs(hostUp.X) < 0.9 - ? Normalize(Cross(hostUp, new Vector3D(1, 0, 0))) - : Normalize(Cross(hostUp, new Vector3D(0, 1, 0))); - } - } - - // 归一化right向量 - var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z); - if (rightLength > 0.001) - { - right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength); - } - - // 计算高度向量(垂直于方向向量和宽度向量) - var up = new Vector3D( - direction.Y * right.Z - direction.Z * right.Y, - direction.Z * right.X - direction.X * right.Z, - direction.X * right.Y - direction.Y * right.X - ); - - // 归一化up向量 - var upLength = Math.Sqrt(up.X * up.X + up.Y * up.Y + up.Z * up.Z); - if (upLength > 0.001) - { - up = new Vector3D(up.X / upLength, up.Y / upLength, up.Z / upLength); - } + var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes( + startPoint, + endPoint, + upReference ?? GetHostUpVector(), + horizontalDirection); // 计算长方体的边界框和向量 var halfWidth = width / 2.0; @@ -2894,7 +2809,7 @@ namespace NavisworksTransport /// 高度 /// 指定长度(覆盖默认计算值) /// 水平段方向向量(用于垂直路径渲染时确定通行空间方向) - private void RenderCuboidMarkerWithLength(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, double length, Vector3D horizontalDirection = null) + private void RenderCuboidMarkerWithLength(Graphics graphics, Point3D startPoint, Point3D endPoint, double width, double height, double length, Vector3D horizontalDirection = null, Vector3D upReference = null) { try { @@ -2912,47 +2827,11 @@ namespace NavisworksTransport direction = new Vector3D(direction.X / directionLength, direction.Y / directionLength, direction.Z / directionLength); - var hostUp = GetHostUpVector(); - var right = Cross(direction, hostUp); - - double parallelToUp = Math.Abs( - direction.X * hostUp.X + - direction.Y * hostUp.Y + - direction.Z * hostUp.Z); - if (parallelToUp > 0.9) - { - if (horizontalDirection != null) - { - right = Cross(horizontalDirection, hostUp); - } - else - { - right = Math.Abs(hostUp.X) < 0.9 - ? Normalize(Cross(hostUp, new Vector3D(1, 0, 0))) - : Normalize(Cross(hostUp, new Vector3D(0, 1, 0))); - } - } - - // 归一化right向量 - var rightLength = Math.Sqrt(right.X * right.X + right.Y * right.Y + right.Z * right.Z); - if (rightLength > 0.001) - { - right = new Vector3D(right.X / rightLength, right.Y / rightLength, right.Z / rightLength); - } - - // 计算高度向量(垂直于方向向量和宽度向量) - var up = new Vector3D( - direction.Y * right.Z - direction.Z * right.Y, - direction.Z * right.X - direction.X * right.Z, - direction.X * right.Y - direction.Y * right.X - ); - - // 归一化up向量 - var upLength = Math.Sqrt(up.X * up.X + up.Y * up.Y + up.Z * up.Z); - if (upLength > 0.001) - { - up = new Vector3D(up.X / upLength, up.Y / upLength, up.Z / upLength); - } + var (right, up) = ObjectSpaceOrientationHelper.CalculateAxes( + startPoint, + endPoint, + upReference ?? GetHostUpVector(), + horizontalDirection); // 计算长方体的边界框和向量 var halfWidth = width / 2.0; @@ -3009,7 +2888,8 @@ namespace NavisworksTransport lineMarker.EndPoint, width, lineMarker.Height, - lineMarker.HorizontalDirection + lineMarker.HorizontalDirection, + lineMarker.UpDirection ); } } @@ -3052,11 +2932,25 @@ namespace NavisworksTransport width, lineMarker.Height, _passageAlongPath, - lineMarker.HorizontalDirection + lineMarker.HorizontalDirection, + lineMarker.UpDirection ); } } + private Vector3D ResolveObjectSpaceUpDirection(PathRoute route) + { + if (route?.PathType == NavisworksTransport.PathType.Rail && route.RailPreferredNormal != null) + { + return Normalize(new Vector3D( + route.RailPreferredNormal.X, + route.RailPreferredNormal.Y, + route.RailPreferredNormal.Z)); + } + + return GetHostUpVector(); + } + /// /// 计算采样点处的切线方向 /// diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index 1335e08..05e2cd6 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -154,6 +154,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels private Vector3 _assemblyInstallationPlaneNormal; private Vector3 _assemblyInstallationPlaneSpanDirection; private double _assemblyInstallationOffsetDistanceInMeters; + private readonly List _assemblyInstallationSeedPoints = new List(); private readonly List _assemblyEndFaceSeedPoints = new List(); private const string AssemblyAnchorMarkerPathId = "assembly_anchor_marker"; private const string AssemblyCenterGuideLinePathId = "assembly_center_guide_line"; @@ -1347,6 +1348,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels { await SafeExecuteAsync(() => { + ClearNonGridPathVisualizations("[直线装配] 捕获箱体"); + var document = NavisApplication.ActiveDocument; var selectedItem = document?.CurrentSelection?.SelectedItems?.FirstOrDefault(); if (selectedItem == null) @@ -1379,6 +1382,30 @@ namespace NavisworksTransport.UI.WPF.ViewModels }, "捕获终点箱体"); } + 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 ExecuteGenerateAssemblyReferenceRodAsync() { await SafeExecuteAsync(() => @@ -1546,7 +1573,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels throw new InvalidOperationException("ToolPlugin 初始化失败,请重试。"); } - UpdateMainStatus("请在终点箱体表面点击安装点,系统将按该点计算安装参考面和终点锚点。"); + _assemblyInstallationSeedPoints.Clear(); + UpdateMainStatus("请在终点箱体表面连续点击两个安装面点,系统将按两点和光轴计算安装参考面与安装点。"); LogManager.Info("[直线装配] 已进入安装点拾取模式"); }, "选择安装点"); } @@ -1640,7 +1668,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels return; } - BuildAndRenderAssemblyInstallationReference(pickResult.Point); + _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]); CleanupAssemblyInstallationSelection(); }, "处理安装点拾取"); } @@ -1665,6 +1707,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine }; + if (_hasAssemblyInstallationReference) + { + route.RailPreferredNormal = new Point3D( + _assemblyInstallationPlaneNormal.X, + _assemblyInstallationPlaneNormal.Y, + _assemblyInstallationPlaneNormal.Z); + } + route.AddPoint(new PathPoint(startPoint, "装配起点", PathPointType.StartPoint)); route.AddPoint(new PathPoint(endPoint, "装配终点", PathPointType.EndPoint)); @@ -1706,7 +1756,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels 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})"); + 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 HideAssemblyReferenceVisuals(bool resetStartPointText, string statusMessage, string logMessage) @@ -1882,7 +1934,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels : string.Format("{0}点=未选择", anchorText); string installationText = _hasAssemblyInstallationReference ? string.Format( - "选定安装点=({0:F2}, {1:F2}, {2:F2}),安装点=({3:F2}, {4:F2}, {5:F2}),偏距={6:F3}m", + "安装面点中点=({0:F2}, {1:F2}, {2:F2}),安装点=({3:F2}, {4:F2}, {5:F2}),偏距={6:F3}m", _assemblyInstallationPickPoint.X, _assemblyInstallationPickPoint.Y, _assemblyInstallationPickPoint.Z, @@ -2214,7 +2266,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels RenderStyleName.AssemblyGuideLine); } - private void BuildAndRenderAssemblyInstallationReference(Point3D pickPoint) + private void BuildAndRenderAssemblyInstallationReference(Point3D firstPickPoint, Point3D secondPickPoint) { Point3D opticalAxisReferencePoint = GetAssemblyOpticalAxisReferencePoint(); Point3D sphereCenterPoint = new Point3D( @@ -2230,10 +2282,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels AssemblyInstallationReferenceResult result = AssemblyInstallationReferenceBuilder.Build( new Vector3((float)opticalAxisReferencePoint.X, (float)opticalAxisReferencePoint.Y, (float)opticalAxisReferencePoint.Z), opticalAxisDirection, - new Vector3((float)pickPoint.X, (float)pickPoint.Y, (float)pickPoint.Z)); + 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 = pickPoint; + _assemblyInstallationPickPoint = AssemblyEndFaceAnalyzer.ToPoint3D((result.PickPoint + result.SecondaryPickPoint) * 0.5f); _assemblyInstallationBaseAnchorPoint = AssemblyEndFaceAnalyzer.ToPoint3D(result.AnchorPoint); _assemblyInstallationPlaneNormal = result.PlaneNormal; _assemblyInstallationPlaneSpanDirection = result.PlaneSpanDirection; @@ -2242,7 +2295,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); UpdateAssemblyInstallationAnchorFromVerticalOffset(); - RenderAssemblyInstallationPickPoint(_assemblyInstallationPickPoint); + RenderAssemblyInstallationPickPoints(_assemblyInstallationSeedPoints); RenderAssemblyInstallationCenterLine(AssemblyEndFaceAnalyzer.ToPoint3D(result.InstallLineBasePoint), result.OpticalAxisDirection); RefreshAssemblyTerminalObjectInfo(); @@ -2250,14 +2303,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels UpdateMainStatus( $"安装参考已计算:安装点=({_assemblyInstallationAnchorPoint.X:F2}, {_assemblyInstallationAnchorPoint.Y:F2}, {_assemblyInstallationAnchorPoint.Z:F2}),偏距={_assemblyInstallationOffsetDistanceInMeters:F3}m"); LogManager.Info( - $"[直线装配] 安装参考已计算: 选定安装点=({pickPoint.X:F3}, {pickPoint.Y:F3}, {pickPoint.Z:F3}), " + + $"[直线装配] 安装参考已计算: 安装面点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})"); OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod)); OnPropertyChanged(nameof(AssemblyInstallationPointButtonText)); } - private void RenderAssemblyInstallationPickPoint(Point3D pickPoint) + private void RenderAssemblyInstallationPickPoints(IReadOnlyList pickPoints) { var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) @@ -2271,7 +2325,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels Id = AssemblyInstallationPickPointPathId, Description = "终端安装点" }; - AddVisualizationPoint(markerRoute, pickPoint, "安装点", PathPointType.WayPoint); + for (int i = 0; i < pickPoints.Count; i++) + { + AddVisualizationPoint(markerRoute, pickPoints[i], $"安装面点{i + 1}", PathPointType.WayPoint); + } renderPlugin.RenderPointOnly(markerRoute); } @@ -2462,6 +2519,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels _assemblyInstallationPlaneNormal = default(Vector3); _assemblyInstallationPlaneSpanDirection = default(Vector3); _assemblyInstallationOffsetDistanceInMeters = 0.0; + _assemblyInstallationSeedPoints.Clear(); _assemblyAnchorVerticalOffsetInMeters = DefaultAssemblyAnchorVerticalOffsetInMeters; OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters)); OnPropertyChanged(nameof(CanGenerateAssemblyReferenceRod)); diff --git a/src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs b/src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs index 2167c93..16f01eb 100644 --- a/src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs +++ b/src/Utils/Assembly/AssemblyInstallationReferenceBuilder.cs @@ -8,6 +8,7 @@ namespace NavisworksTransport.Utils.GeometryAnalysis public Vector3 OpticalAxisBasePoint { get; set; } public Vector3 OpticalAxisDirection { get; set; } public Vector3 PickPoint { get; set; } + public Vector3 SecondaryPickPoint { get; set; } public Vector3 PlaneNormal { get; set; } public Vector3 PlaneSpanDirection { get; set; } public float OffsetDistance { get; set; } @@ -57,6 +58,72 @@ namespace NavisworksTransport.Utils.GeometryAnalysis OpticalAxisBasePoint = opticalAxisBasePoint, OpticalAxisDirection = axisDir, PickPoint = pickPoint, + SecondaryPickPoint = pickPoint, + PlaneNormal = planeNormal, + PlaneSpanDirection = planeSpanDirection, + OffsetDistance = offsetDistance, + InstallLineBasePoint = installLineBasePoint, + AnchorPoint = anchorPoint + }; + } + + public static AssemblyInstallationReferenceResult Build( + Vector3 opticalAxisBasePoint, + Vector3 opticalAxisDirection, + Vector3 firstPickPoint, + Vector3 secondPickPoint) + { + float axisLength = opticalAxisDirection.Length(); + if (axisLength < 1e-6f) + { + throw new InvalidOperationException("光轴方向长度过小,无法计算安装参考。"); + } + + Vector3 axisDir = opticalAxisDirection / axisLength; + Vector3 pickSegment = secondPickPoint - firstPickPoint; + Vector3 axisAlignedComponent = axisDir * Vector3.Dot(pickSegment, axisDir); + Vector3 planeSpanDirection = pickSegment - axisAlignedComponent; + float spanLength = planeSpanDirection.Length(); + if (spanLength < MinimumOffsetDistance) + { + throw new InvalidOperationException("两次安装点拾取几乎与光轴平行,无法确定安装参考面,请重新选择。"); + } + + planeSpanDirection /= spanLength; + + Vector3 midpoint = (firstPickPoint + secondPickPoint) * 0.5f; + Vector3 planeNormal = Vector3.Cross(axisDir, planeSpanDirection); + float normalLength = planeNormal.Length(); + if (normalLength < 1e-6f) + { + throw new InvalidOperationException("安装参考面法向计算失败,请重新选择。"); + } + + planeNormal /= normalLength; + + Vector3 midpointOffset = midpoint - opticalAxisBasePoint; + float signedOffset = Vector3.Dot(midpointOffset, planeNormal); + if (Math.Abs(signedOffset) < MinimumOffsetDistance) + { + throw new InvalidOperationException("安装面中点距离光轴过小,无法确定安装参考面,请重新选择。"); + } + + if (signedOffset < 0f) + { + planeNormal = -planeNormal; + signedOffset = -signedOffset; + } + + float offsetDistance = signedOffset; + Vector3 installLineBasePoint = opticalAxisBasePoint + planeNormal * offsetDistance; + Vector3 anchorPoint = ProjectPointToLine(midpoint, installLineBasePoint, axisDir); + + return new AssemblyInstallationReferenceResult + { + OpticalAxisBasePoint = opticalAxisBasePoint, + OpticalAxisDirection = axisDir, + PickPoint = firstPickPoint, + SecondaryPickPoint = secondPickPoint, PlaneNormal = planeNormal, PlaneSpanDirection = planeSpanDirection, OffsetDistance = offsetDistance, diff --git a/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs b/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs index e20e521..f1d5faa 100644 --- a/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs +++ b/src/Utils/CoordinateSystem/CanonicalRailPoseBuilder.cs @@ -17,6 +17,23 @@ namespace NavisworksTransport.Utils.CoordinateSystem Vector3 nextPoint, Vector3 canonicalUp, out RailLocalFrame frame) + { + return TryCreateLocalFrame( + previousPoint, + currentPoint, + nextPoint, + canonicalUp, + null, + out frame); + } + + public static bool TryCreateLocalFrame( + Vector3 previousPoint, + Vector3 currentPoint, + Vector3 nextPoint, + Vector3 canonicalUp, + Vector3? preferredNormal, + out RailLocalFrame frame) { frame = null; @@ -25,6 +42,7 @@ namespace NavisworksTransport.Utils.CoordinateSystem currentPoint, nextPoint, canonicalUp, + preferredNormal, out var forward, out var lateral, out var up)) @@ -44,6 +62,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem out Vector3 forward, out Vector3 lateral, out Vector3 up) + { + return TryCreateBasis( + previousPoint, + currentPoint, + nextPoint, + canonicalUp, + null, + out forward, + out lateral, + out up); + } + + public static bool TryCreateBasis( + Vector3 previousPoint, + Vector3 currentPoint, + Vector3 nextPoint, + Vector3 canonicalUp, + Vector3? preferredNormal, + out Vector3 forward, + out Vector3 lateral, + out Vector3 up) { forward = default; lateral = default; @@ -61,7 +100,13 @@ namespace NavisworksTransport.Utils.CoordinateSystem } forward = Vector3.Normalize(tangent); - Vector3 projectedUp = canonicalUp - Vector3.Dot(canonicalUp, forward) * forward; + Vector3 referenceUp = canonicalUp; + if (preferredNormal.HasValue && preferredNormal.Value.LengthSquared() >= TangentEpsilon) + { + referenceUp = Vector3.Normalize(preferredNormal.Value); + } + + Vector3 projectedUp = referenceUp - Vector3.Dot(referenceUp, forward) * forward; if (projectedUp.LengthSquared() < TangentEpsilon) { projectedUp = Math.Abs(forward.Z) < 0.9f @@ -95,6 +140,7 @@ namespace NavisworksTransport.Utils.CoordinateSystem nextPoint, canonicalUp, convention, + null, 0.0, out rotation); } @@ -107,6 +153,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem ModelAxisConvention convention, double localUpRotationDegrees, out Quaternion rotation) + { + return TryCreateQuaternion( + previousPoint, + currentPoint, + nextPoint, + canonicalUp, + convention, + null, + localUpRotationDegrees, + out rotation); + } + + public static bool TryCreateQuaternion( + Vector3 previousPoint, + Vector3 currentPoint, + Vector3 nextPoint, + Vector3 canonicalUp, + ModelAxisConvention convention, + Vector3? preferredNormal, + double localUpRotationDegrees, + out Quaternion rotation) { rotation = Quaternion.Identity; @@ -115,7 +182,15 @@ namespace NavisworksTransport.Utils.CoordinateSystem throw new ArgumentNullException(nameof(convention)); } - if (!TryCreateBasis(previousPoint, currentPoint, nextPoint, canonicalUp, out var forward, out _, out var up)) + if (!TryCreateBasis( + previousPoint, + currentPoint, + nextPoint, + canonicalUp, + preferredNormal, + out var forward, + out _, + out var up)) { return false; } diff --git a/src/Utils/CoordinateSystem/ObjectSpaceOrientationHelper.cs b/src/Utils/CoordinateSystem/ObjectSpaceOrientationHelper.cs new file mode 100644 index 0000000..65d9e2d --- /dev/null +++ b/src/Utils/CoordinateSystem/ObjectSpaceOrientationHelper.cs @@ -0,0 +1,92 @@ +using System; +using System.Numerics; +using Autodesk.Navisworks.Api; + +namespace NavisworksTransport.Utils.CoordinateSystem +{ + public static class ObjectSpaceOrientationHelper + { + public static (Vector3D right, Vector3D up) CalculateAxes( + Point3D startPoint, + Point3D endPoint, + Vector3D upReference, + Vector3D horizontalDirection = null) + { + var segmentDirection = new Vector3( + (float)(endPoint.X - startPoint.X), + (float)(endPoint.Y - startPoint.Y), + (float)(endPoint.Z - startPoint.Z)); + + var (right, up) = CalculateAxes( + segmentDirection, + ToNumerics(upReference), + horizontalDirection == null ? (Vector3?)null : ToNumerics(horizontalDirection)); + + return (ToNavisworks(right), ToNavisworks(up)); + } + + public static (Vector3 right, Vector3 up) CalculateAxes( + Vector3 segmentDirection, + Vector3 upReference, + Vector3? horizontalDirection = null) + { + double segmentLength = Math.Sqrt( + segmentDirection.X * segmentDirection.X + + segmentDirection.Y * segmentDirection.Y + + segmentDirection.Z * segmentDirection.Z); + + if (segmentLength < 1e-9) + { + throw new InvalidOperationException("路径段长度过小,无法计算通行空间方向。"); + } + + var normalizedDirection = Vector3.Normalize(segmentDirection); + var normalizedUpReference = Normalize(upReference); + var right = Cross(normalizedDirection, normalizedUpReference); + + double parallelToUp = Math.Abs(Vector3.Dot(normalizedDirection, normalizedUpReference)); + if (parallelToUp > 0.9) + { + if (horizontalDirection.HasValue) + { + right = Cross(horizontalDirection.Value, normalizedUpReference); + } + else + { + right = Math.Abs(normalizedUpReference.X) < 0.9f + ? Normalize(Cross(normalizedUpReference, new Vector3(1f, 0f, 0f))) + : Normalize(Cross(normalizedUpReference, new Vector3(0f, 1f, 0f))); + } + } + + right = Normalize(right); + var up = Normalize(Cross(normalizedDirection, right)); + return (right, up); + } + + private static Vector3 Cross(Vector3 a, Vector3 b) + { + return Vector3.Cross(a, b); + } + + private static Vector3 Normalize(Vector3 vector) + { + if (vector.LengthSquared() < 1e-9f) + { + return Vector3.Zero; + } + + return Vector3.Normalize(vector); + } + + private static Vector3 ToNumerics(Vector3D vector) + { + return new Vector3((float)vector.X, (float)vector.Y, (float)vector.Z); + } + + private static Vector3D ToNavisworks(Vector3 vector) + { + return new Vector3D(vector.X, vector.Y, vector.Z); + } + } +} diff --git a/src/Utils/RailPathPoseHelper.cs b/src/Utils/RailPathPoseHelper.cs index 2b74480..8fd306a 100644 --- a/src/Utils/RailPathPoseHelper.cs +++ b/src/Utils/RailPathPoseHelper.cs @@ -135,7 +135,7 @@ namespace NavisworksTransport.Utils var adapterForRail = CoordinateSystemManager.Instance.CreateHostAdapter(); var canonicalReferencePointForRail = adapterForRail.ToCanonicalPoint(referencePoint); - if (!TryCreateCanonicalLocalFrame(previousPoint, referencePoint, nextPoint, out RailLocalFrame frame)) + if (!TryCreateCanonicalLocalFrame(route, previousPoint, referencePoint, nextPoint, out RailLocalFrame frame)) { var canonicalCenterPointForRailFallback = new Point3D( canonicalReferencePointForRail.X, @@ -165,6 +165,22 @@ namespace NavisworksTransport.Utils out Matrix3 linearTransform) { return TryCreateRailLinearTransform( + null, + previousPoint, + currentPoint, + nextPoint, + out linearTransform); + } + + public static bool TryCreateRailLinearTransform( + PathRoute route, + Point3D previousPoint, + Point3D currentPoint, + Point3D nextPoint, + out Matrix3 linearTransform) + { + return TryCreateRailLinearTransform( + route, previousPoint, currentPoint, nextPoint, @@ -177,6 +193,7 @@ namespace NavisworksTransport.Utils /// 通过模型局部轴约定显式指定本地哪个轴代表 forward/up。 /// public static bool TryCreateRailLinearTransform( + PathRoute route, Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, @@ -184,6 +201,7 @@ namespace NavisworksTransport.Utils out Matrix3 linearTransform) { return TryCreateRailLinearTransform( + route, previousPoint, currentPoint, nextPoint, @@ -193,6 +211,7 @@ namespace NavisworksTransport.Utils } public static bool TryCreateRailLinearTransform( + PathRoute route, Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, @@ -217,6 +236,7 @@ namespace NavisworksTransport.Utils canonicalCurrentPoint, canonicalNextPoint, HostCoordinateAdapter.CanonicalUpVector3, + ResolvePreferredNormal(route), out RailLocalFrame frame)) { return false; @@ -231,6 +251,7 @@ namespace NavisworksTransport.Utils canonicalNextPoint, HostCoordinateAdapter.CanonicalUpVector3, axisConvention, + ResolvePreferredNormal(route), localUpRotationDegrees, out var correctedRotation) ? correctedRotation @@ -270,6 +291,22 @@ namespace NavisworksTransport.Utils out Rotation3D rotation) { return TryCreateRailRotation( + null, + previousPoint, + currentPoint, + nextPoint, + out rotation); + } + + public static bool TryCreateRailRotation( + PathRoute route, + Point3D previousPoint, + Point3D currentPoint, + Point3D nextPoint, + out Rotation3D rotation) + { + return TryCreateRailRotation( + route, previousPoint, currentPoint, nextPoint, @@ -285,6 +322,24 @@ namespace NavisworksTransport.Utils out Rotation3D rotation) { return TryCreateRailRotation( + null, + previousPoint, + currentPoint, + nextPoint, + axisConvention, + out rotation); + } + + public static bool TryCreateRailRotation( + PathRoute route, + Point3D previousPoint, + Point3D currentPoint, + Point3D nextPoint, + ModelAxisConvention axisConvention, + out Rotation3D rotation) + { + return TryCreateRailRotation( + route, previousPoint, currentPoint, nextPoint, @@ -294,6 +349,7 @@ namespace NavisworksTransport.Utils } public static bool TryCreateRailRotation( + PathRoute route, Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, @@ -305,6 +361,7 @@ namespace NavisworksTransport.Utils LogRotationConstructorConventionOnce(); if (!TryCreateRailLinearTransform( + route, previousPoint, currentPoint, nextPoint, @@ -446,7 +503,7 @@ namespace NavisworksTransport.Utils private static Vector3D ResolveRailNormal(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint) { - if (TryCreateCanonicalLocalFrame(previousPoint, currentPoint, nextPoint, out RailLocalFrame frame)) + if (TryCreateCanonicalLocalFrame(null, previousPoint, currentPoint, nextPoint, out RailLocalFrame frame)) { return ToNavVector(frame.Normal); } @@ -479,6 +536,7 @@ namespace NavisworksTransport.Utils } private static bool TryCreateCanonicalLocalFrame( + PathRoute route, Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, @@ -490,9 +548,37 @@ namespace NavisworksTransport.Utils ToNumerics(adapter.ToCanonicalPoint(currentPoint)), ToNumerics(adapter.ToCanonicalPoint(nextPoint)), HostCoordinateAdapter.CanonicalUpVector3, + ResolvePreferredNormal(route), out frame); } + private static Vector3? ResolvePreferredNormal(PathRoute route) + { + if (route?.RailPreferredNormal == null) + { + return null; + } + + var preferredNormal = new Vector3( + (float)route.RailPreferredNormal.X, + (float)route.RailPreferredNormal.Y, + (float)route.RailPreferredNormal.Z); + + if (preferredNormal.LengthSquared() < 1e-9f) + { + return null; + } + + var adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); + Vector3 canonicalPreferredNormal = adapter.ToCanonicalVector3(preferredNormal); + if (canonicalPreferredNormal.LengthSquared() < 1e-9f) + { + return null; + } + + return Vector3.Normalize(canonicalPreferredNormal); + } + private static Vector3D Normalize(Vector3D vector) { double lengthSquared = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;