diff --git a/NavisworksTransport.UnitTests.csproj b/NavisworksTransport.UnitTests.csproj index 30ef294..0a8140c 100644 --- a/NavisworksTransport.UnitTests.csproj +++ b/NavisworksTransport.UnitTests.csproj @@ -73,6 +73,7 @@ + diff --git a/UnitTests/CoordinateSystem/PathPointVisualizationTests.cs b/UnitTests/CoordinateSystem/PathPointVisualizationTests.cs new file mode 100644 index 0000000..e05657f --- /dev/null +++ b/UnitTests/CoordinateSystem/PathPointVisualizationTests.cs @@ -0,0 +1,32 @@ +using Autodesk.Navisworks.Api; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NavisworksTransport.UnitTests.CoordinateSystem +{ + [TestClass] + public class PathPointVisualizationTests + { + [TestMethod] + public void ResolveVisualizationRadius_WhenVisualizationDiameterIsUnset_ShouldUseDefaultRadius() + { + var point = new PathPoint(new Point3D(1.0, 2.0, 3.0), "测试点", PathPointType.WayPoint); + + double radius = point.ResolveVisualizationRadius(0.25); + + Assert.AreEqual(0.25, radius, 1e-9); + } + + [TestMethod] + public void ResolveVisualizationRadius_WhenVisualizationDiameterIsSpecified_ShouldUseHalfDiameter() + { + var point = new PathPoint(new Point3D(1.0, 2.0, 3.0), "测试点", PathPointType.WayPoint) + { + VisualizationDiameter = 0.10 + }; + + double radius = point.ResolveVisualizationRadius(0.25); + + Assert.AreEqual(0.05, radius, 1e-9); + } + } +} diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index 9af4ca4..c991f10 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -191,6 +191,8 @@ namespace NavisworksTransport.Core.Animation private Vector3D _groundRealObjectStartCompensation = new Vector3D(0, 0, 0); private bool _hasGroundRealObjectStartCompensation = false; private bool _suppressGroundRealObjectCompensation = false; + private Vector3D _railPreservedPoseTrackedCenterOffset = new Vector3D(0, 0, 0); + private bool _hasRailPreservedPoseTrackedCenterOffset = false; // TimeLiner 集成 private TimeLinerIntegrationManager _timeLinerManager; @@ -475,6 +477,8 @@ namespace NavisworksTransport.Core.Animation string objectName = isVirtual ? "虚拟物体" : objectToRestore.DisplayName; _groundRealObjectStartCompensation = new Vector3D(0, 0, 0); _hasGroundRealObjectStartCompensation = false; + _railPreservedPoseTrackedCenterOffset = new Vector3D(0, 0, 0); + _hasRailPreservedPoseTrackedCenterOffset = false; LogManager.Info($"[归位] {objectName} 已彻底恢复到原始位置, yaw={_currentYaw:F3}"); } catch (Exception ex) @@ -743,9 +747,19 @@ namespace NavisworksTransport.Core.Animation { Point3D previousPoint = _pathPoints[0]; Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0]; - double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint); - startPosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight); + if (TryResolveRailPreservedPoseTrackedCenter(startPosition, out Point3D preservedTrackedCenter)) + { + startPosition = preservedTrackedCenter; + LogManager.Debug( + $"[移动到起点] Rail保持姿态补偿: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), " + + $"补偿后中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})"); + } + else + { + double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint); + startPosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight); 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 (TryCreateRailPathRotation( previousPoint, @@ -926,10 +940,16 @@ namespace NavisworksTransport.Core.Animation } else if (_route.PathType == PathType.Rail) { + CaptureRailPreservedPoseTrackedCenterOffset(); Point3D previousPoint = _pathPoints[0]; Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0]; - double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint); - startPosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight); + if (!TryResolveRailPreservedPoseTrackedCenter(startPosition, out Point3D preservedTrackedCenter)) + { + double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint); + preservedTrackedCenter = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight); + } + + startPosition = preservedTrackedCenter; } else { @@ -1204,8 +1224,18 @@ namespace NavisworksTransport.Core.Animation } else if (_route.PathType == PathType.Rail) { - framePosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, framePosition, p1, p2, objectHeight); - LogManager.Debug($"[Rail路径] 调整物体位置: 参考点=({p1.X:F2},{p1.Y:F2},{p1.Z:F2})->({p2.X:F2},{p2.Y:F2},{p2.Z:F2}), 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}), 安装={_route.RailMountMode}, 对接={(PathRoute.IsTopPayloadAnchorForMountMode(_route.RailMountMode) ? "顶面对接" : "底面对接")}"); + if (TryResolveRailPreservedPoseTrackedCenter(framePosition, out Point3D preservedTrackedCenter)) + { + framePosition = preservedTrackedCenter; + LogManager.Debug( + $"[Rail路径] 保持姿态补偿: 参考点=({p1.X:F2},{p1.Y:F2},{p1.Z:F2})->({p2.X:F2},{p2.Y:F2},{p2.Z:F2}), " + + $"物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})"); + } + else + { + framePosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, framePosition, p1, p2, objectHeight); + LogManager.Debug($"[Rail路径] 调整物体位置: 参考点=({p1.X:F2},{p1.Y:F2},{p1.Z:F2})->({p2.X:F2},{p2.Y:F2},{p2.Z:F2}), 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}), 安装={_route.RailMountMode}, 对接={(PathRoute.IsTopPayloadAnchorForMountMode(_route.RailMountMode) ? "顶面对接" : "底面对接")}"); + } } } else @@ -2164,13 +2194,16 @@ namespace NavisworksTransport.Core.Animation Point3D expectedTrackedEndPoint; if (_route.PathType == PathType.Rail) { - double objectHeight = GetAnimatedObjectRailNormalExtent(previousAnchorPoint, terminalAnchorPoint, terminalAnchorPoint); - expectedTrackedEndPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition( - _route, - terminalAnchorPoint, - previousAnchorPoint, - terminalAnchorPoint, - objectHeight); + if (!TryResolveRailPreservedPoseTrackedCenter(terminalAnchorPoint, out expectedTrackedEndPoint)) + { + double objectHeight = GetAnimatedObjectRailNormalExtent(previousAnchorPoint, terminalAnchorPoint, terminalAnchorPoint); + expectedTrackedEndPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition( + _route, + terminalAnchorPoint, + previousAnchorPoint, + terminalAnchorPoint, + objectHeight); + } } else if (_route.PathType == PathType.Hoisting) { @@ -3789,6 +3822,53 @@ namespace NavisworksTransport.Core.Animation return adapter.FromCanonicalPoint(new Point3D(canonicalCenter.X, canonicalCenter.Y, canonicalCenter.Z)); } + private bool TryResolveRailPreservedPoseTrackedCenter(Point3D referencePoint, out Point3D trackedCenter) + { + trackedCenter = referencePoint; + + if (!IsRealObjectMode || + _route?.PathType != PathType.Rail || + _objectStartPlacementMode != ObjectStartPlacementMode.PreserveInitialPose || + !_hasRailPreservedPoseTrackedCenterOffset) + { + return false; + } + + trackedCenter = new Point3D( + referencePoint.X + _railPreservedPoseTrackedCenterOffset.X, + referencePoint.Y + _railPreservedPoseTrackedCenterOffset.Y, + referencePoint.Z + _railPreservedPoseTrackedCenterOffset.Z); + return true; + } + + private void CaptureRailPreservedPoseTrackedCenterOffset() + { + _railPreservedPoseTrackedCenterOffset = new Vector3D(0, 0, 0); + _hasRailPreservedPoseTrackedCenterOffset = false; + + if (!IsRealObjectMode || + _route?.PathType != PathType.Rail || + _objectStartPlacementMode != ObjectStartPlacementMode.PreserveInitialPose || + _pathPoints == null || + _pathPoints.Count == 0) + { + return; + } + + Point3D currentTrackedCenter = GetTrackedObjectPosition(CurrentControlledObject ?? _animatedObject); + Point3D terminalAnchorPoint = _pathPoints[_pathPoints.Count - 1]; + _railPreservedPoseTrackedCenterOffset = new Vector3D( + currentTrackedCenter.X - terminalAnchorPoint.X, + currentTrackedCenter.Y - terminalAnchorPoint.Y, + currentTrackedCenter.Z - terminalAnchorPoint.Z); + _hasRailPreservedPoseTrackedCenterOffset = true; + + LogManager.Info( + $"[Rail保持姿态补偿] 当前跟踪中心=({currentTrackedCenter.X:F3},{currentTrackedCenter.Y:F3},{currentTrackedCenter.Z:F3}), " + + $"终点锚点=({terminalAnchorPoint.X:F3},{terminalAnchorPoint.Y:F3},{terminalAnchorPoint.Z:F3}), " + + $"固定偏移=({_railPreservedPoseTrackedCenterOffset.X:F3},{_railPreservedPoseTrackedCenterOffset.Y:F3},{_railPreservedPoseTrackedCenterOffset.Z:F3})"); + } + private bool TryResolveGroundRealObjectBaseYaw(out double yawRadians) { yawRadians = 0.0; diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs index c99356e..2cbd84b 100644 --- a/src/Core/PathPlanningModels.cs +++ b/src/Core/PathPlanningModels.cs @@ -375,6 +375,13 @@ namespace NavisworksTransport /// public double? CustomTurnRadius { get; set; } + /// + /// 仅用于视图显示的点直径(模型单位)。 + /// 为 null 或非正数时使用默认点类型尺寸。 + /// + [XmlIgnore] + public double? VisualizationDiameter { get; set; } + /// /// 方向类型(仅吊装路径使用) /// 用于标识吊装路径点的移动方向:垂直、纵向(X轴)、横向(Y轴) @@ -395,6 +402,7 @@ namespace NavisworksTransport Notes = string.Empty; SpeedLimit = 0; CustomTurnRadius = null; + VisualizationDiameter = null; Direction = HoistingPointDirection.Vertical; } @@ -415,9 +423,25 @@ namespace NavisworksTransport Notes = string.Empty; SpeedLimit = 0; CustomTurnRadius = null; + VisualizationDiameter = null; Direction = HoistingPointDirection.Vertical; } + /// + /// 解析最终用于渲染的点半径(模型单位)。 + /// + /// 默认点半径(模型单位) + /// 最终半径(模型单位) + public double ResolveVisualizationRadius(double defaultRadius) + { + if (!VisualizationDiameter.HasValue || VisualizationDiameter.Value <= 0) + { + return defaultRadius; + } + + return VisualizationDiameter.Value * 0.5; + } + /// /// 返回路径点描述字符串 /// diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs index e6b5efd..5823d70 100644 --- a/src/Core/PathPointRenderPlugin.cs +++ b/src/Core/PathPointRenderPlugin.cs @@ -2268,7 +2268,8 @@ namespace NavisworksTransport { Center = point.Position, Normal = GetHostUpVector(), - Radius = isGridVisualization ? GetRadiusForGridVisualization() : GetRadiusForPointType(point.Type), + Radius = point.ResolveVisualizationRadius( + isGridVisualization ? GetRadiusForGridVisualization() : GetRadiusForPointType(point.Type)), Color = isGridVisualization ? gridStyle.Color : GetColorForPointType(point.Type), Alpha = isGridVisualization ? gridStyle.Alpha : 1.0, Filled = true, diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index cb660e2..902e4ae 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -125,6 +125,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels private const double DefaultAssemblyReferenceRodLengthInMeters = 20.0; private static readonly double DefaultAssemblyReferenceRodDiameterInMeters = CalculateDefaultAssemblyReferenceRodDiameterInMeters(); private const double DefaultAssemblyAnchorVerticalOffsetInMeters = 0.0; + private const double AssemblyVisualizationPointDiameterInMeters = 0.10; private const double RailNormalOffsetNudgeStepInMeters = 0.1; private const double DefaultAssemblySphereCenterX = 0.0; private const double DefaultAssemblySphereCenterY = 0.0; @@ -2259,7 +2260,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels for (int i = 0; i < _assemblyEndFaceSeedPoints.Count; i++) { - AddVisualizationPoint(markerRoute, _assemblyEndFaceSeedPoints[i], $"端面点{i + 1}", PathPointType.WayPoint); + AddVisualizationPoint( + markerRoute, + _assemblyEndFaceSeedPoints[i], + $"端面点{i + 1}", + PathPointType.WayPoint, + UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); } renderPlugin.RenderPointOnly(markerRoute); @@ -2280,11 +2286,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels Id = AssemblyEndFaceCenterPathId, Description = "终端安装端面中心" }; - AddVisualizationPoint(markerRoute, centerPoint, "端面中心", PathPointType.WayPoint); + AddVisualizationPoint( + markerRoute, + centerPoint, + "端面中心", + PathPointType.WayPoint, + UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); renderPlugin.RenderPointOnly(markerRoute); } - private static void AddVisualizationPoint(PathRoute route, Point3D position, string name, PathPointType type) + private static void AddVisualizationPoint( + PathRoute route, + Point3D position, + string name, + PathPointType type, + double? visualizationDiameter = null) { if (route == null) { @@ -2293,7 +2309,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels route.Points.Add(new PathPoint(position, name, type) { - Index = route.Points.Count + Index = route.Points.Count, + VisualizationDiameter = visualizationDiameter }); } @@ -2379,7 +2396,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels }; for (int i = 0; i < pickPoints.Count; i++) { - AddVisualizationPoint(markerRoute, pickPoints[i], $"安装面点{i + 1}", PathPointType.WayPoint); + AddVisualizationPoint( + markerRoute, + pickPoints[i], + $"安装面点{i + 1}", + PathPointType.WayPoint, + UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); } renderPlugin.RenderPointOnly(markerRoute); } @@ -2398,7 +2420,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels Id = AssemblyInstallationAnchorPointPathId, Description = "终端安装点" }; - AddVisualizationPoint(markerRoute, anchorPoint, "安装点", PathPointType.WayPoint); + AddVisualizationPoint( + markerRoute, + anchorPoint, + "安装点", + PathPointType.WayPoint, + UnitsConverter.ConvertFromMeters(AssemblyVisualizationPointDiameterInMeters)); renderPlugin.RenderPointOnly(markerRoute); }