From 953306fdb18730c30a64a09ee637c428d036a081 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Wed, 1 Apr 2026 23:53:11 +0800 Subject: [PATCH] Stabilize hoisting pose flow and bounds inspection --- .../RailPreservedPoseTests.cs | 45 ++++++ src/Core/Animation/PathAnimationManager.cs | 47 ++++--- src/UI/WPF/Views/ModelItemBoundsWindow.xaml | 128 ++++++++++++++++-- .../WPF/Views/ModelItemBoundsWindow.xaml.cs | 105 ++++++++++---- 4 files changed, 270 insertions(+), 55 deletions(-) diff --git a/UnitTests/CoordinateSystem/RailPreservedPoseTests.cs b/UnitTests/CoordinateSystem/RailPreservedPoseTests.cs index b4212e1..afac4e6 100644 --- a/UnitTests/CoordinateSystem/RailPreservedPoseTests.cs +++ b/UnitTests/CoordinateSystem/RailPreservedPoseTests.cs @@ -1,6 +1,7 @@ using Autodesk.Navisworks.Api; using Microsoft.VisualStudio.TestTools.UnitTesting; using NavisworksTransport.Core.Animation; +using NavisworksTransport.Utils.CoordinateSystem; namespace NavisworksTransport.UnitTests.CoordinateSystem { @@ -42,5 +43,49 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem Assert.AreEqual(fallbackRotation.C, resolved.C, 1e-9); Assert.AreEqual(fallbackRotation.D, resolved.D, 1e-9); } + + [TestMethod] + public void ShouldPreservePathRotationForFrames_ShouldEnableHoistingWhenTranslationModeHasLockedRotation() + { + bool shouldPreserve = PathAnimationManager.ShouldPreservePathRotationForFrames( + PathType.Hoisting, + ObjectStartPlacementMode.PreserveInitialPose, + hasPreservedRotation: true); + + Assert.IsTrue(shouldPreserve); + } + + [TestMethod] + public void ShouldPreservePathRotationForFrames_ShouldEnableRailWhenTranslationModeHasLockedRotation() + { + bool shouldPreserve = PathAnimationManager.ShouldPreservePathRotationForFrames( + PathType.Rail, + ObjectStartPlacementMode.PreserveInitialPose, + hasPreservedRotation: true); + + Assert.IsTrue(shouldPreserve); + } + + [TestMethod] + public void ShouldPreservePathRotationForFrames_ShouldDisableGroundEvenInTranslationMode() + { + bool shouldPreserve = PathAnimationManager.ShouldPreservePathRotationForFrames( + PathType.Ground, + ObjectStartPlacementMode.PreserveInitialPose, + hasPreservedRotation: true); + + Assert.IsFalse(shouldPreserve); + } + + [TestMethod] + public void ShouldPreservePathRotationForFrames_ShouldDisableWhenLockedRotationMissing() + { + bool shouldPreserve = PathAnimationManager.ShouldPreservePathRotationForFrames( + PathType.Hoisting, + ObjectStartPlacementMode.PreserveInitialPose, + hasPreservedRotation: false); + + Assert.IsFalse(shouldPreserve); + } } } diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index 0f62d66..ad1507f 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -218,8 +218,8 @@ namespace NavisworksTransport.Core.Animation private bool _savedObjectHasCustomRotation = false; private bool _hasSavedObjectState = false; private ObjectStartPlacementMode _objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose; - private Rotation3D _railPreservedPoseRotation = Rotation3D.Identity; - private bool _hasRailPreservedPoseRotation = false; + private Rotation3D _pathPreservedPoseRotation = Rotation3D.Identity; + private bool _hasPathPreservedPoseRotation = false; private bool IsVirtualObjectMode => _animatedObjectMode == AnimatedObjectMode.VirtualObject; private bool IsRealObjectMode => _animatedObjectMode == AnimatedObjectMode.RealObject; @@ -1022,18 +1022,19 @@ namespace NavisworksTransport.Core.Animation { var pathPoints = _route.Points.Select(p => p.Position).ToList(); MoveObjectToPathStartUsingCurrentPlacementMode(_animatedObject, pathPoints); - if (_route.PathType == PathType.Rail && - _objectStartPlacementMode == ObjectStartPlacementMode.PreserveInitialPose && - _hasTrackedRotation) + if (ShouldPreservePathRotationForFrames( + _route.PathType, + _objectStartPlacementMode, + _hasTrackedRotation)) { - _railPreservedPoseRotation = _trackedRotation; - _hasRailPreservedPoseRotation = true; - LogManager.Info("[预计算] Rail平移模式已锁定起点姿态为整段动画旋转基线"); + _pathPreservedPoseRotation = _trackedRotation; + _hasPathPreservedPoseRotation = true; + LogManager.Info($"[预计算] {_route.PathType.GetDisplayName()}平移模式已锁定起点姿态为整段动画旋转基线"); } else { - _railPreservedPoseRotation = Rotation3D.Identity; - _hasRailPreservedPoseRotation = false; + _pathPreservedPoseRotation = Rotation3D.Identity; + _hasPathPreservedPoseRotation = false; } LogManager.Info("[预计算] 物体已移动到路径起点"); } @@ -1340,11 +1341,12 @@ namespace NavisworksTransport.Core.Animation Collisions = new List() }; - if (_route.PathType == PathType.Rail && - _objectStartPlacementMode == ObjectStartPlacementMode.PreserveInitialPose && - _hasRailPreservedPoseRotation) + if (ShouldPreservePathRotationForFrames( + _route.PathType, + _objectStartPlacementMode, + _hasPathPreservedPoseRotation)) { - frame.Rotation = _railPreservedPoseRotation; + frame.Rotation = _pathPreservedPoseRotation; frame.HasCustomRotation = true; } else if (_route.PathType == PathType.Rail && @@ -3972,6 +3974,19 @@ namespace NavisworksTransport.Core.Animation : fallbackRotation; } + internal static bool ShouldPreservePathRotationForFrames( + PathType pathType, + ObjectStartPlacementMode placementMode, + bool hasPreservedRotation) + { + if (!hasPreservedRotation || placementMode != ObjectStartPlacementMode.PreserveInitialPose) + { + return false; + } + + return pathType == PathType.Rail || pathType == PathType.Hoisting; + } + private void SyncTrackedRotationToDisplayedPose(ModelItem sourceObject) { if (sourceObject == null) @@ -5181,7 +5196,7 @@ namespace NavisworksTransport.Core.Animation public void SetObjectRotationCorrection(LocalEulerRotationCorrection rotationCorrection) { _objectStartPlacementMode = ObjectStartPlacementMode.AlignToPathPose; - _hasRailPreservedPoseRotation = false; + _hasPathPreservedPoseRotation = false; _objectRotationCorrection = rotationCorrection; ResetPlanarRealObjectBasePoseCache(); @@ -5224,7 +5239,7 @@ namespace NavisworksTransport.Core.Animation _objectStartPlacementMode = placementMode; if (placementMode != ObjectStartPlacementMode.PreserveInitialPose) { - _hasRailPreservedPoseRotation = false; + _hasPathPreservedPoseRotation = false; } LogManager.Debug($"[起点摆放] 当前模式已设置为: {_objectStartPlacementMode}"); } diff --git a/src/UI/WPF/Views/ModelItemBoundsWindow.xaml b/src/UI/WPF/Views/ModelItemBoundsWindow.xaml index 2a8ff5d..1389a4b 100644 --- a/src/UI/WPF/Views/ModelItemBoundsWindow.xaml +++ b/src/UI/WPF/Views/ModelItemBoundsWindow.xaml @@ -1,7 +1,7 @@  - + @@ -138,8 +138,8 @@ - - + + @@ -155,7 +155,7 @@ - + @@ -165,7 +165,7 @@ - + @@ -175,7 +175,7 @@ - + @@ -183,7 +183,7 @@ x:Name="CopyTopButton" Style="{StaticResource IconButtonStyle}" Click="OnCopyTopCenterClick" - ToolTip="复制顶面中心点坐标" + ToolTip="复制宿主包围盒顶面中心点坐标" Margin="3,0,0,0"> - - - + + + @@ -208,7 +208,7 @@ - + @@ -218,7 +218,7 @@ - + @@ -228,7 +228,7 @@ - + @@ -236,13 +236,111 @@ x:Name="CopyBottomButton" Style="{StaticResource IconButtonStyle}" Click="OnCopyBottomCenterClick" - ToolTip="复制底面中心点坐标" + ToolTip="复制宿主包围盒底面中心点坐标" Margin="3,0,0,0"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/UI/WPF/Views/ModelItemBoundsWindow.xaml.cs b/src/UI/WPF/Views/ModelItemBoundsWindow.xaml.cs index f6591a6..b833bb8 100644 --- a/src/UI/WPF/Views/ModelItemBoundsWindow.xaml.cs +++ b/src/UI/WPF/Views/ModelItemBoundsWindow.xaml.cs @@ -4,6 +4,8 @@ using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; using Autodesk.Navisworks.Api; +using NavisworksTransport.Utils; +using NavisworksTransport.Utils.CoordinateSystem; namespace NavisworksTransport.UI.WPF.Views { @@ -35,23 +37,41 @@ namespace NavisworksTransport.UI.WPF.Views /// 中心点Z坐标 public double CenterZ { get; set; } - /// 顶面中心点X坐标 - public double TopCenterX { get; set; } + /// 宿主包围盒顶面中心点X坐标 + public double HostTopCenterX { get; set; } - /// 顶面中心点Y坐标 - public double TopCenterY { get; set; } + /// 宿主包围盒顶面中心点Y坐标 + public double HostTopCenterY { get; set; } - /// 顶面中心点Z坐标 - public double TopCenterZ { get; set; } + /// 宿主包围盒顶面中心点Z坐标 + public double HostTopCenterZ { get; set; } - /// 底面中心点X坐标 - public double BottomCenterX { get; set; } + /// 宿主包围盒底面中心点X坐标 + public double HostBottomCenterX { get; set; } - /// 底面中心点Y坐标 - public double BottomCenterY { get; set; } + /// 宿主包围盒底面中心点Y坐标 + public double HostBottomCenterY { get; set; } - /// 底面中心点Z坐标 - public double BottomCenterZ { get; set; } + /// 宿主包围盒底面中心点Z坐标 + public double HostBottomCenterZ { get; set; } + + /// 对象姿态语义顶面中心点X坐标 + public double OrientedTopCenterX { get; set; } + + /// 对象姿态语义顶面中心点Y坐标 + public double OrientedTopCenterY { get; set; } + + /// 对象姿态语义顶面中心点Z坐标 + public double OrientedTopCenterZ { get; set; } + + /// 对象姿态语义底面中心点X坐标 + public double OrientedBottomCenterX { get; set; } + + /// 对象姿态语义底面中心点Y坐标 + public double OrientedBottomCenterY { get; set; } + + /// 对象姿态语义底面中心点Z坐标 + public double OrientedBottomCenterZ { get; set; } #endregion @@ -127,15 +147,36 @@ namespace NavisworksTransport.UI.WPF.Views CenterY = (bounds.Min.Y + bounds.Max.Y) / 2.0; CenterZ = (bounds.Min.Z + bounds.Max.Z) / 2.0; - // 计算顶面中心点(Z最大) - TopCenterX = CenterX; - TopCenterY = CenterY; - TopCenterZ = bounds.Max.Z; + HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); - // 计算底面中心点(Z最小) - BottomCenterX = CenterX; - BottomCenterY = CenterY; - BottomCenterZ = bounds.Min.Z; + Point3D hostBottomCenter = ModelItemTransformHelper.GetHostBottomAnchorPoint(bounds, adapter); + double hostHeight = ModelItemTransformHelper.GetHostHeight(bounds, adapter); + Point3D hostTopCenter = new Point3D( + hostBottomCenter.X + adapter.HostUpVector.X * hostHeight, + hostBottomCenter.Y + adapter.HostUpVector.Y * hostHeight, + hostBottomCenter.Z + adapter.HostUpVector.Z * hostHeight); + + HostBottomCenterX = hostBottomCenter.X; + HostBottomCenterY = hostBottomCenter.Y; + HostBottomCenterZ = hostBottomCenter.Z; + HostTopCenterX = hostTopCenter.X; + HostTopCenterY = hostTopCenter.Y; + HostTopCenterZ = hostTopCenter.Z; + + Point3D orientedBottomCenter = ModelItemTransformHelper.GetStableBottomAnchorPoint(bounds, item.Transform); + Vector3D objectUp = ModelItemTransformHelper.GetUpDirectionFromTransform(item.Transform); + Vector3D localSize = ModelItemTransformHelper.EstimateLocalBoxSize(bounds, item.Transform); + Point3D orientedTopCenter = new Point3D( + orientedBottomCenter.X + objectUp.X * localSize.Z, + orientedBottomCenter.Y + objectUp.Y * localSize.Z, + orientedBottomCenter.Z + objectUp.Z * localSize.Z); + + OrientedBottomCenterX = orientedBottomCenter.X; + OrientedBottomCenterY = orientedBottomCenter.Y; + OrientedBottomCenterZ = orientedBottomCenter.Z; + OrientedTopCenterX = orientedTopCenter.X; + OrientedTopCenterY = orientedTopCenter.Y; + OrientedTopCenterZ = orientedTopCenter.Z; } catch (Exception ex) { @@ -157,8 +198,10 @@ namespace NavisworksTransport.UI.WPF.Views { SizeX = SizeY = SizeZ = 0; CenterX = CenterY = CenterZ = 0; - TopCenterX = TopCenterY = TopCenterZ = 0; - BottomCenterX = BottomCenterY = BottomCenterZ = 0; + HostTopCenterX = HostTopCenterY = HostTopCenterZ = 0; + HostBottomCenterX = HostBottomCenterY = HostBottomCenterZ = 0; + OrientedTopCenterX = OrientedTopCenterY = OrientedTopCenterZ = 0; + OrientedBottomCenterX = OrientedBottomCenterY = OrientedBottomCenterZ = 0; } /// @@ -202,7 +245,7 @@ namespace NavisworksTransport.UI.WPF.Views /// private void OnCopyTopCenterClick(object sender, RoutedEventArgs e) { - string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", TopCenterX, TopCenterY, TopCenterZ); + string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", HostTopCenterX, HostTopCenterY, HostTopCenterZ); bool success = ClipboardHelper.TrySetText(coordinate, "元素包围盒信息"); ShowButtonFeedback(CopyTopButton, success); } @@ -212,11 +255,25 @@ namespace NavisworksTransport.UI.WPF.Views /// private void OnCopyBottomCenterClick(object sender, RoutedEventArgs e) { - string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", BottomCenterX, BottomCenterY, BottomCenterZ); + string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", HostBottomCenterX, HostBottomCenterY, HostBottomCenterZ); bool success = ClipboardHelper.TrySetText(coordinate, "元素包围盒信息"); ShowButtonFeedback(CopyBottomButton, success); } + private void OnCopyOrientedTopCenterClick(object sender, RoutedEventArgs e) + { + string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", OrientedTopCenterX, OrientedTopCenterY, OrientedTopCenterZ); + bool success = ClipboardHelper.TrySetText(coordinate, "元素包围盒信息"); + ShowButtonFeedback(CopyOrientedTopButton, success); + } + + private void OnCopyOrientedBottomCenterClick(object sender, RoutedEventArgs e) + { + string coordinate = string.Format("{0:0.000}, {1:0.000}, {2:0.000}", OrientedBottomCenterX, OrientedBottomCenterY, OrientedBottomCenterZ); + bool success = ClipboardHelper.TrySetText(coordinate, "元素包围盒信息"); + ShowButtonFeedback(CopyOrientedBottomButton, success); + } + /// /// 显示按钮点击反馈 ///