diff --git a/NavisworksTransport.UnitTests.csproj b/NavisworksTransport.UnitTests.csproj
index b109f54..17555ba 100644
--- a/NavisworksTransport.UnitTests.csproj
+++ b/NavisworksTransport.UnitTests.csproj
@@ -72,6 +72,8 @@
+
+
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
index 1020f6e..0edda3e 100644
--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -31,4 +31,5 @@ using System.Runtime.InteropServices;
//
// 主版本和次版本手动维护,Build和Revision在编译时自动更新
[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: InternalsVisibleTo("NavisworksTransport.UnitTests")]
diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj
index f126d00..63a7404 100644
--- a/TransportPlugin.csproj
+++ b/TransportPlugin.csproj
@@ -522,6 +522,11 @@
PreserveNewest
resources\unit_cube.nwc
+
+
+ PreserveNewest
+ resources\unit_cube_yup.nwc
+
PreserveNewest
diff --git a/UnitTests/CoordinateSystem/FragmentDefaultUpContextTests.cs b/UnitTests/CoordinateSystem/FragmentDefaultUpContextTests.cs
new file mode 100644
index 0000000..17b135b
--- /dev/null
+++ b/UnitTests/CoordinateSystem/FragmentDefaultUpContextTests.cs
@@ -0,0 +1,47 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NavisworksTransport.Utils.CoordinateSystem;
+
+namespace NavisworksTransport.UnitTests.CoordinateSystem
+{
+ [TestClass]
+ public class FragmentDefaultUpContextTests
+ {
+ [TestInitialize]
+ public void SetUp()
+ {
+ FragmentDefaultUpContext.ResetForTests();
+ }
+
+ [TestMethod]
+ public void GetOrCreateDocumentKey_ReusesFirstStableKey_WhenDocumentMetadataTemporarilyChanges()
+ {
+ const int runtimeDocumentId = 42;
+
+ string initialKey = FragmentDefaultUpContext.GetOrCreateDocumentKey(
+ runtimeDocumentId,
+ @"C:\models\floor2.nwd",
+ "Floor2");
+
+ string transientKey = FragmentDefaultUpContext.GetOrCreateDocumentKey(
+ runtimeDocumentId,
+ string.Empty,
+ "无标题");
+
+ Assert.AreEqual(initialKey, transientKey);
+ Assert.AreEqual("file:C:\\models\\floor2.nwd", transientKey);
+ }
+
+ [TestMethod]
+ public void GetOrCreateDocumentKey_UsesRuntimeScopedFallback_WhenFileNameIsUnavailable()
+ {
+ const int runtimeDocumentId = 7;
+
+ string key = FragmentDefaultUpContext.GetOrCreateDocumentKey(
+ runtimeDocumentId,
+ string.Empty,
+ "无标题");
+
+ Assert.AreEqual("runtime:7|title:无标题", key);
+ }
+ }
+}
diff --git a/UnitTests/CoordinateSystem/VirtualGroundPoseCharacterizationTests.cs b/UnitTests/CoordinateSystem/VirtualGroundPoseCharacterizationTests.cs
new file mode 100644
index 0000000..f94d79a
--- /dev/null
+++ b/UnitTests/CoordinateSystem/VirtualGroundPoseCharacterizationTests.cs
@@ -0,0 +1,120 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NavisworksTransport.Utils.CoordinateSystem;
+using System;
+using System.Numerics;
+using Autodesk.Navisworks.Api;
+
+namespace NavisworksTransport.UnitTests.CoordinateSystem
+{
+ [TestClass]
+ public class VirtualGroundPoseCharacterizationTests
+ {
+ [TestMethod]
+ public void YUp_GroundVirtualPose_WithYUpVirtualAsset_ShouldUseHostYAsUp()
+ {
+ var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
+ var convention = ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.YUp);
+
+ Vector3 hostForward = Vector3.Normalize(new Vector3(-0.8987f, 0.0f, 0.4386f));
+ Vector3 canonicalForward = adapter.ToCanonicalVector3(hostForward);
+
+ bool ok = CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
+ canonicalForward,
+ HostCoordinateAdapter.CanonicalUpVector3,
+ convention,
+ out Quaternion canonicalRotation);
+
+ Assert.IsTrue(ok);
+
+ Matrix4x4 hostLinear = adapter.FromCanonicalLinearTransform(
+ Matrix4x4.CreateFromQuaternion(canonicalRotation));
+
+ AssertColumn(hostLinear, 0, -0.8987, 0.0000, 0.4386);
+ AssertColumn(hostLinear, 1, 0.0000, 1.0000, 0.0000);
+ AssertColumn(hostLinear, 2, -0.4386, 0.0000, -0.8987);
+ }
+
+ [TestMethod]
+ public void YUp_GroundVirtualPose_WithYUpVirtualAsset_ShouldMatchHostYUpDefaultPlanarConvention()
+ {
+ var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
+ Vector3 hostForward = Vector3.Normalize(new Vector3(-0.8987f, 0.0f, 0.4386f));
+ Vector3 canonicalForward = adapter.ToCanonicalVector3(hostForward);
+
+ Assert.IsTrue(CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
+ canonicalForward,
+ HostCoordinateAdapter.CanonicalUpVector3,
+ ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.YUp),
+ out Quaternion assetRotation));
+
+ Assert.IsTrue(CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
+ canonicalForward,
+ HostCoordinateAdapter.CanonicalUpVector3,
+ ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.YUp),
+ out Quaternion hostDefaultRotation));
+
+ Matrix4x4 assetHostLinear = adapter.FromCanonicalLinearTransform(
+ Matrix4x4.CreateFromQuaternion(assetRotation));
+ Matrix4x4 hostDefaultLinear = adapter.FromCanonicalLinearTransform(
+ Matrix4x4.CreateFromQuaternion(hostDefaultRotation));
+
+ Assert.AreEqual(1.0, assetHostLinear.M22, 1e-3, "YUp 虚拟物体资源应把 local Y 对到宿主 up。");
+ Assert.AreEqual(hostDefaultLinear.M11, assetHostLinear.M11, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M21, assetHostLinear.M21, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M31, assetHostLinear.M31, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M12, assetHostLinear.M12, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M22, assetHostLinear.M22, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M32, assetHostLinear.M32, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M13, assetHostLinear.M13, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M23, assetHostLinear.M23, 1e-3);
+ Assert.AreEqual(hostDefaultLinear.M33, assetHostLinear.M33, 1e-3);
+ }
+
+ [TestMethod]
+ public void VirtualObjectReferencePose_ShouldMatchSelectedVirtualAssetConvention()
+ {
+ var yUpAdapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
+ var zUpAdapter = new HostCoordinateAdapter(CoordinateSystemType.ZUp);
+ var yUpConvention = ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.YUp);
+ var zUpConvention = ModelAxisConvention.CreateVirtualObjectAssetConvention();
+
+ Matrix4x4 yUpLinear = Matrix4x4.CreateFromQuaternion(
+ yUpConvention.CreateQuaternion(new Vector3(1, 0, 0), yUpAdapter.HostUpVector3));
+ Matrix4x4 zUpLinear = Matrix4x4.CreateFromQuaternion(
+ zUpConvention.CreateQuaternion(new Vector3(1, 0, 0), zUpAdapter.HostUpVector3));
+
+ AssertColumn(yUpLinear, 0, 1.0, 0.0, 0.0);
+ AssertColumn(yUpLinear, 1, 0.0, 1.0, 0.0);
+ AssertColumn(yUpLinear, 2, 0.0, 0.0, 1.0);
+
+ AssertColumn(zUpLinear, 0, 1.0, 0.0, 0.0);
+ AssertColumn(zUpLinear, 1, 0.0, 1.0, 0.0);
+ AssertColumn(zUpLinear, 2, 0.0, 0.0, 1.0);
+ }
+
+ private static void AssertColumn(Matrix4x4 matrix, int column, double x, double y, double z)
+ {
+ switch (column)
+ {
+ case 0:
+ Assert.AreEqual(x, matrix.M11, 1e-3);
+ Assert.AreEqual(y, matrix.M21, 1e-3);
+ Assert.AreEqual(z, matrix.M31, 1e-3);
+ break;
+ case 1:
+ Assert.AreEqual(x, matrix.M12, 1e-3);
+ Assert.AreEqual(y, matrix.M22, 1e-3);
+ Assert.AreEqual(z, matrix.M32, 1e-3);
+ break;
+ case 2:
+ Assert.AreEqual(x, matrix.M13, 1e-3);
+ Assert.AreEqual(y, matrix.M23, 1e-3);
+ Assert.AreEqual(z, matrix.M33, 1e-3);
+ break;
+ default:
+ Assert.Fail("Only first 3 columns are valid.");
+ break;
+ }
+ }
+ }
+}
diff --git a/UnitTests/CoordinateSystem/VirtualObjectModelTransformTests.cs b/UnitTests/CoordinateSystem/VirtualObjectModelTransformTests.cs
new file mode 100644
index 0000000..e571818
--- /dev/null
+++ b/UnitTests/CoordinateSystem/VirtualObjectModelTransformTests.cs
@@ -0,0 +1,100 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NavisworksTransport.Core;
+
+namespace NavisworksTransport.UnitTests.CoordinateSystem
+{
+ [TestClass]
+ public class VirtualObjectModelTransformTests
+ {
+ [TestMethod]
+ public void BuildModelTransformPreservingScale_ShouldKeepExistingScaleAndReplaceRotationTranslation()
+ {
+ const double scaleX = 4.9213;
+ const double scaleY = 3.2808;
+ const double scaleZ = 6.5617;
+ var targetRotation = new QuaternionData(0.0, 0.7071068, 0.0, 0.7071068);
+
+ VirtualObjectManager.ModelTransformComponents result = VirtualObjectManager.CreateModelTransformPreservingScale(
+ scaleX,
+ scaleY,
+ scaleZ,
+ -177.129,
+ 18.114,
+ -2.793,
+ targetRotation.ToRotation3D());
+
+ Assert.AreEqual(scaleX, result.ScaleX, 1e-4);
+ Assert.AreEqual(scaleY, result.ScaleY, 1e-4);
+ Assert.AreEqual(scaleZ, result.ScaleZ, 1e-4);
+ AssertRotationEquivalent(targetRotation, result.Rotation);
+ Assert.AreEqual(-177.129, result.TranslationX, 1e-6);
+ Assert.AreEqual(18.114, result.TranslationY, 1e-6);
+ Assert.AreEqual(-2.793, result.TranslationZ, 1e-6);
+ }
+
+ [TestMethod]
+ public void BuildModelTransformPreservingScale_ResetPose_ShouldOnlyClearRotationAndTranslation()
+ {
+ VirtualObjectManager.ModelTransformComponents result = VirtualObjectManager.CreateModelTransformPreservingScale(
+ 12.0,
+ 5.0,
+ 7.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ QuaternionData.Identity.ToRotation3D());
+
+ Assert.AreEqual(12.0, result.ScaleX, 1e-6);
+ Assert.AreEqual(5.0, result.ScaleY, 1e-6);
+ Assert.AreEqual(7.0, result.ScaleZ, 1e-6);
+ AssertRotationEquivalent(QuaternionData.Identity, result.Rotation);
+ Assert.AreEqual(0.0, result.TranslationX, 1e-6);
+ Assert.AreEqual(0.0, result.TranslationY, 1e-6);
+ Assert.AreEqual(0.0, result.TranslationZ, 1e-6);
+ }
+
+ private static void AssertRotationEquivalent(QuaternionData expected, Autodesk.Navisworks.Api.Rotation3D actual)
+ {
+ bool same =
+ NearlyEqual(expected.X, actual.A) &&
+ NearlyEqual(expected.Y, actual.B) &&
+ NearlyEqual(expected.Z, actual.C) &&
+ NearlyEqual(expected.W, actual.D);
+ bool negatedSame =
+ NearlyEqual(expected.X, -actual.A) &&
+ NearlyEqual(expected.Y, -actual.B) &&
+ NearlyEqual(expected.Z, -actual.C) &&
+ NearlyEqual(expected.W, -actual.D);
+
+ Assert.IsTrue(same || negatedSame, "Rotation3D quaternion should match target rotation up to sign.");
+ }
+
+ private static bool NearlyEqual(double left, double right)
+ {
+ return System.Math.Abs(left - right) < 1e-6;
+ }
+
+ private struct QuaternionData
+ {
+ public QuaternionData(double x, double y, double z, double w)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ W = w;
+ }
+
+ public double X { get; }
+ public double Y { get; }
+ public double Z { get; }
+ public double W { get; }
+
+ public Autodesk.Navisworks.Api.Rotation3D ToRotation3D()
+ {
+ return new Autodesk.Navisworks.Api.Rotation3D(X, Y, Z, W);
+ }
+
+ public static QuaternionData Identity => new QuaternionData(0, 0, 0, 1);
+ }
+ }
+}
diff --git a/resources/unit_cube_yup.nwc b/resources/unit_cube_yup.nwc
new file mode 100644
index 0000000..47289cd
Binary files /dev/null and b/resources/unit_cube_yup.nwc differ
diff --git a/resources/unit_cube_yup.obj b/resources/unit_cube_yup.obj
new file mode 100644
index 0000000..6829a52
--- /dev/null
+++ b/resources/unit_cube_yup.obj
@@ -0,0 +1,26 @@
+# unit cube for virtual object (Y-up)
+# dimensions: 1.0 x 1.0 x 1.0
+# centered at origin
+# local +X = forward, local +Y = up, local +Z = side
+# when Navisworks imports OBJ as centimeters, generated NWC becomes 0.01m base size
+o unit_cube_yup
+v -0.500000 -0.500000 -0.500000
+v 0.500000 -0.500000 -0.500000
+v 0.500000 0.500000 -0.500000
+v -0.500000 0.500000 -0.500000
+v -0.500000 -0.500000 0.500000
+v 0.500000 -0.500000 0.500000
+v 0.500000 0.500000 0.500000
+v -0.500000 0.500000 0.500000
+f 1 2 3
+f 1 3 4
+f 5 8 7
+f 5 7 6
+f 1 5 6
+f 1 6 2
+f 2 6 7
+f 2 7 3
+f 3 7 8
+f 3 8 4
+f 4 8 5
+f 4 5 1
diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs
index da2e878..29a3e05 100644
--- a/src/Core/Animation/PathAnimationManager.cs
+++ b/src/Core/Animation/PathAnimationManager.cs
@@ -449,7 +449,16 @@ namespace NavisworksTransport.Core.Animation
// 2. 重置内部跟踪变量(同步到原始状态)
// 注意:ResetPermanentTransform 后物体的 Transform 属性会自动变回原始值
- SyncTrackedRotationToObjectReference(objectToRestore, isVirtual);
+ if (isVirtual)
+ {
+ _currentYaw = ModelItemTransformHelper.GetYawFromTransform(objectToRestore.Transform);
+ _trackedRotation = objectToRestore.Transform.Factor().Rotation;
+ _hasTrackedRotation = true;
+ }
+ else
+ {
+ SyncTrackedRotationToObjectReference(objectToRestore, isVirtualObject: false);
+ }
var originalBoundingBox = objectToRestore.BoundingBox();
_trackedPosition = GetTrackedObjectPosition(objectToRestore);
@@ -652,7 +661,16 @@ namespace NavisworksTransport.Core.Animation
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_trackedPosition = GetTrackedObjectPosition(animatedObject);
- SyncTrackedRotationToObjectReference(animatedObject, isVirtualObject);
+ if (isVirtualObject)
+ {
+ _currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
+ _trackedRotation = _originalTransform.Factor().Rotation;
+ _hasTrackedRotation = true;
+ }
+ else
+ {
+ SyncTrackedRotationToObjectReference(animatedObject, isVirtualObject: false);
+ }
LogManager.Info(
$"[移动到起点] 更新动画对象内部状态: 对象={animatedObject.DisplayName}, 虚拟物体={IsVirtualObjectMode}");
@@ -2452,75 +2470,89 @@ namespace NavisworksTransport.Core.Animation
{
try
{
- bool isRailRealObject = _route?.PathType == PathType.Rail && IsRealObjectMode && _animatedObject != null;
- if (IsVirtualObjectMode)
- {
- VirtualObjectManager.Instance.MoveVirtualObject(newPosition, newRotation);
- }
- else if (_animatedObject != null)
- {
- Point3D currentPositionForTransform = _trackedPosition;
- Rotation3D currentRotation;
- if (_hasTrackedRotation)
- {
- currentRotation = _trackedRotation;
- }
- else if (IsRealObjectMode && _hasRealObjectReferenceRotation)
- {
- currentRotation = CoordinateSystemManager.Instance
- .CreateHostAdapter()
- .FromHostQuaternion(_realObjectReferenceRotation);
- }
- else
- {
- currentRotation = _animatedObject.Transform.Factor().Rotation;
- }
- try
- {
- var actualHostPosition = GetTrackedObjectPosition(_animatedObject);
- currentPositionForTransform = actualHostPosition;
- LogManager.Info(
- $"[动画姿态入口] {_animatedObject.DisplayName} 宿主即时读回点=({actualHostPosition.X:F3},{actualHostPosition.Y:F3},{actualHostPosition.Z:F3})");
- }
- catch (Exception ex)
- {
- LogManager.Warning($"[动画姿态入口] 读取宿主实际状态失败: {ex.Message}");
- }
- var currentLinear = new Transform3D(currentRotation).Linear;
- var targetLinear = new Transform3D(newRotation).Linear;
- LogManager.Info(
- $"[动画姿态入口] {_animatedObject.DisplayName} 跟踪点=({_trackedPosition.X:F3},{_trackedPosition.Y:F3},{_trackedPosition.Z:F3}), " +
- $"目标点=({newPosition.X:F3},{newPosition.Y:F3},{newPosition.Z:F3})");
- LogManager.Info(
- $"[动画姿态入口] {_animatedObject.DisplayName} 跟踪姿态: " +
- $"X=({currentLinear.Get(0, 0):F4},{currentLinear.Get(1, 0):F4},{currentLinear.Get(2, 0):F4}), " +
- $"Y=({currentLinear.Get(0, 1):F4},{currentLinear.Get(1, 1):F4},{currentLinear.Get(2, 1):F4}), " +
- $"Z=({currentLinear.Get(0, 2):F4},{currentLinear.Get(1, 2):F4},{currentLinear.Get(2, 2):F4})");
- LogManager.Info(
- $"[动画姿态入口] {_animatedObject.DisplayName} 目标姿态: " +
- $"X=({targetLinear.Get(0, 0):F4},{targetLinear.Get(1, 0):F4},{targetLinear.Get(2, 0):F4}), " +
- $"Y=({targetLinear.Get(0, 1):F4},{targetLinear.Get(1, 1):F4},{targetLinear.Get(2, 1):F4}), " +
- $"Z=({targetLinear.Get(0, 2):F4},{targetLinear.Get(1, 2):F4},{targetLinear.Get(2, 2):F4})");
- ModelItemTransformHelper.MoveItemIncrementallyToPositionAndRotation(
- _animatedObject,
- currentPositionForTransform,
- currentRotation,
- newPosition,
- newRotation);
- }
- else
+ ModelItem controlledObject = CurrentControlledObject;
+ bool isRailRealObject = _route?.PathType == PathType.Rail && IsRealObjectMode && controlledObject != null;
+ if (controlledObject == null)
{
return;
}
+ if (IsVirtualObjectMode)
+ {
+ VirtualObjectManager.Instance.MoveVirtualObject(newPosition, newRotation);
+ _trackedPosition = newPosition;
+ _trackedRotation = newRotation;
+ _hasTrackedRotation = true;
+ _currentYaw = ModelItemTransformHelper.GetYawFromRotation(newRotation);
+
+ if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
+ {
+ LogHostActualPoseAxes(CurrentControlledObject, "[平面姿态应用后宿主姿态]", false);
+ }
+
+ return;
+ }
+
+ Point3D currentPositionForTransform = _trackedPosition;
+ Rotation3D currentRotation;
+ Rotation3D appliedTargetRotation = newRotation;
+ if (_hasTrackedRotation)
+ {
+ currentRotation = _trackedRotation;
+ }
+ else if (IsRealObjectMode && _hasRealObjectReferenceRotation)
+ {
+ currentRotation = CoordinateSystemManager.Instance
+ .CreateHostAdapter()
+ .FromHostQuaternionDirect(_realObjectReferenceRotation);
+ }
+ else
+ {
+ currentRotation = controlledObject.Transform.Factor().Rotation;
+ }
+
+ try
+ {
+ var actualHostPosition = GetTrackedObjectPosition(controlledObject);
+ currentPositionForTransform = actualHostPosition;
+ LogManager.Info(
+ $"[动画姿态入口] {controlledObject.DisplayName} 宿主即时读回点=({actualHostPosition.X:F3},{actualHostPosition.Y:F3},{actualHostPosition.Z:F3})");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Warning($"[动画姿态入口] 读取宿主实际状态失败: {ex.Message}");
+ }
+
+ var currentLinear = new Transform3D(currentRotation).Linear;
+ var targetLinear = new Transform3D(appliedTargetRotation).Linear;
+ LogManager.Info(
+ $"[动画姿态入口] {controlledObject.DisplayName} 跟踪点=({_trackedPosition.X:F3},{_trackedPosition.Y:F3},{_trackedPosition.Z:F3}), " +
+ $"目标点=({newPosition.X:F3},{newPosition.Y:F3},{newPosition.Z:F3})");
+ LogManager.Info(
+ $"[动画姿态入口] {controlledObject.DisplayName} 跟踪姿态: " +
+ $"X=({currentLinear.Get(0, 0):F4},{currentLinear.Get(1, 0):F4},{currentLinear.Get(2, 0):F4}), " +
+ $"Y=({currentLinear.Get(0, 1):F4},{currentLinear.Get(1, 1):F4},{currentLinear.Get(2, 1):F4}), " +
+ $"Z=({currentLinear.Get(0, 2):F4},{currentLinear.Get(1, 2):F4},{currentLinear.Get(2, 2):F4})");
+ LogManager.Info(
+ $"[动画姿态入口] {controlledObject.DisplayName} 目标姿态: " +
+ $"X=({targetLinear.Get(0, 0):F4},{targetLinear.Get(1, 0):F4},{targetLinear.Get(2, 0):F4}), " +
+ $"Y=({targetLinear.Get(0, 1):F4},{targetLinear.Get(1, 1):F4},{targetLinear.Get(2, 1):F4}), " +
+ $"Z=({targetLinear.Get(0, 2):F4},{targetLinear.Get(1, 2):F4},{targetLinear.Get(2, 2):F4})");
+ ModelItemTransformHelper.MoveItemIncrementallyToPositionAndRotation(
+ controlledObject,
+ currentPositionForTransform,
+ currentRotation,
+ newPosition,
+ appliedTargetRotation);
+
_trackedPosition = newPosition;
- _trackedRotation = newRotation;
+ _trackedRotation = appliedTargetRotation;
_hasTrackedRotation = true;
- _currentYaw = ModelItemTransformHelper.GetYawFromRotation(newRotation);
+ _currentYaw = ModelItemTransformHelper.GetYawFromRotation(appliedTargetRotation);
if (isRailRealObject)
{
- Point3D hostActualAfter = GetTrackedObjectPosition(_animatedObject);
+ Point3D hostActualAfter = GetTrackedObjectPosition(controlledObject);
LogManager.Debug(
$"[Rail姿态应用] 宿主最终跟踪中心=({hostActualAfter.X:F3},{hostActualAfter.Y:F3},{hostActualAfter.Z:F3}), " +
$"目标跟踪中心=({newPosition.X:F3},{newPosition.Y:F3},{newPosition.Z:F3}), " +
@@ -3782,7 +3814,7 @@ namespace NavisworksTransport.Core.Animation
Quaternion composedHostQuaternion = adapter.ComposeHostQuaternion(
baselineHostQuaternion,
_objectRotationCorrection);
- rotation = adapter.FromHostQuaternion(composedHostQuaternion);
+ rotation = adapter.FromHostQuaternionDirect(composedHostQuaternion);
Matrix4x4 baselineLinear = Matrix4x4.CreateFromQuaternion(baselineHostQuaternion);
Matrix4x4 composedLinear = Matrix4x4.CreateFromQuaternion(composedHostQuaternion);
@@ -3958,6 +3990,7 @@ namespace NavisworksTransport.Core.Animation
return true;
}
+
private bool TryCreateRealObjectPlanarRotationFromHostForward(Vector3 hostForward, out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
@@ -3980,7 +4013,7 @@ namespace NavisworksTransport.Core.Animation
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
Quaternion hostComposedQuaternion = adapter.ComposeHostQuaternion(solution.BaselineRotation, _objectRotationCorrection);
- rotation = adapter.FromHostQuaternion(hostComposedQuaternion);
+ rotation = adapter.FromHostQuaternionDirect(hostComposedQuaternion);
Matrix4x4 baselineLinear = Matrix4x4.CreateFromQuaternion(solution.BaselineRotation);
Matrix4x4 hostComposedLinear = Matrix4x4.CreateFromQuaternion(hostComposedQuaternion);
@@ -4192,10 +4225,25 @@ namespace NavisworksTransport.Core.Animation
return;
}
+ if (isVirtualObject)
+ {
+ _trackedRotation = CreateVirtualObjectReferenceRotation();
+ _hasTrackedRotation = true;
+ _currentYaw = ModelItemTransformHelper.GetYawFromRotation(_trackedRotation);
+
+ var linear = new Transform3D(_trackedRotation).Linear;
+ LogManager.Info(
+ $"[虚拟物体参考姿态] {sourceObject.DisplayName} 使用资产约定基姿态: " +
+ $"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
+ $"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
+ $"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4})");
+ return;
+ }
+
if (!isVirtualObject && TryCaptureRealObjectReferenceRotation(sourceObject, out Quaternion referenceRotation))
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
- _trackedRotation = adapter.FromHostQuaternion(referenceRotation);
+ _trackedRotation = adapter.FromHostQuaternionDirect(referenceRotation);
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(_trackedRotation);
return;
@@ -4206,6 +4254,18 @@ namespace NavisworksTransport.Core.Animation
_hasTrackedRotation = true;
}
+ private static Rotation3D CreateVirtualObjectReferenceRotation()
+ {
+ var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
+ var convention = GetVirtualObjectAssetConvention();
+ return convention.CreateRotation(new Vector3D(1, 0, 0), adapter.HostUpVector);
+ }
+
+ private static ModelAxisConvention GetVirtualObjectAssetConvention()
+ {
+ return ModelAxisConvention.CreateVirtualObjectAssetConvention();
+ }
+
private void ResetRealObjectReferenceRotation()
{
_realObjectReferenceRotation = Quaternion.Identity;
diff --git a/src/Core/VirtualObjectManager.cs b/src/Core/VirtualObjectManager.cs
index 311d93b..dc9ae85 100644
--- a/src/Core/VirtualObjectManager.cs
+++ b/src/Core/VirtualObjectManager.cs
@@ -94,7 +94,6 @@ namespace NavisworksTransport.Core
var doc = Application.ActiveDocument;
var unitCubePath = GetUnitCubeFilePath();
-
if (string.IsNullOrEmpty(unitCubePath) || !File.Exists(unitCubePath))
{
throw new FileNotFoundException($"找不到单位立方体文件: {unitCubePath}");
@@ -213,9 +212,7 @@ namespace NavisworksTransport.Core
try
{
LogVirtualObjectGeometry("[虚拟物体姿态] 应用前");
- // 虚拟物体当前尺寸是通过永久变换中的缩放实现的。
- // 应用完整姿态时必须保留当前缩放,否则会破坏虚拟物体的资产姿态和几何语义。
- ModelItemTransformHelper.MoveItemToPositionAndRotationWithCurrentScale(_virtualObjectModelItem, position, rotation);
+ ModelItemTransformHelper.MoveItemToPositionAndRotation(_virtualObjectModelItem, position, rotation);
var actualBounds = _virtualObjectModelItem.BoundingBox();
Point3D actualCenter = actualBounds?.Center ?? new Point3D(0, 0, 0);
LogManager.Info(
@@ -239,20 +236,15 @@ namespace NavisworksTransport.Core
{
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { _virtualObjectModelItem };
-
- // 获取当前缩放
var currentTransform = _virtualObjectModelItem.Transform;
var currentComponents = currentTransform.Factor();
var currentScale = currentComponents.Scale;
- // 重置到CAD原始状态
doc.Models.ResetPermanentTransform(modelItems);
- // 重新应用缩放
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Scale = currentScale;
-
Transform3D newTransform = components.Combine();
doc.Models.OverridePermanentTransform(modelItems, newTransform, false);
@@ -280,18 +272,15 @@ namespace NavisworksTransport.Core
_currentWidthMeters = widthMeters;
_currentHeightMeters = heightMeters;
- // 获取当前位置和旋转
+ // 应用新缩放
+ ScaleVirtualObject(lengthMeters, widthMeters, heightMeters);
+
var currentTransform = _virtualObjectModelItem.Transform;
var currentComponents = currentTransform.Factor();
var currentTranslation = currentComponents.Translation;
var currentRotation = currentComponents.Rotation;
- // 应用新缩放
- ScaleVirtualObject(lengthMeters, widthMeters, heightMeters);
-
- // 如果之前有位置/旋转,重新应用
bool hasTranslation = currentTranslation.X != 0 || currentTranslation.Y != 0 || currentTranslation.Z != 0;
- // 使用 ToAxisAndAngle 检查是否有旋转
var rotationResult = currentRotation.ToAxisAndAngle();
bool hasRotation = Math.Abs(rotationResult.Angle) > 0.001;
if (hasTranslation || hasRotation)
@@ -300,7 +289,7 @@ namespace NavisworksTransport.Core
var newComponents = newTransform.Factor();
newComponents.Translation = currentTranslation;
newComponents.Rotation = currentRotation;
-
+
Transform3D combinedTransform = newComponents.Combine();
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { _virtualObjectModelItem };
@@ -332,7 +321,6 @@ namespace NavisworksTransport.Core
lengthMeters / baseSizeMeters,
widthMeters / baseSizeMeters,
heightMeters / baseSizeMeters);
-
Transform3D newTransform = transformComponents.Combine();
doc.Models.SetModelUnitsAndTransform(_virtualObjectModel, _virtualObjectModel.Units, newTransform, false);
diff --git a/src/Utils/CoordinateSystem/FragmentDefaultUpContext.cs b/src/Utils/CoordinateSystem/FragmentDefaultUpContext.cs
index 32ab907..c7eadc4 100644
--- a/src/Utils/CoordinateSystem/FragmentDefaultUpContext.cs
+++ b/src/Utils/CoordinateSystem/FragmentDefaultUpContext.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils.CoordinateSystem
@@ -13,6 +14,9 @@ namespace NavisworksTransport.Utils.CoordinateSystem
private static readonly ConcurrentDictionary DocumentFragmentDefaultUpAxes =
new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+ private static readonly ConcurrentDictionary RuntimeDocumentKeys =
+ new ConcurrentDictionary();
+
public static string GetCurrentDocumentKey(Document doc = null)
{
doc = doc ?? Autodesk.Navisworks.Api.Application.ActiveDocument;
@@ -21,12 +25,39 @@ namespace NavisworksTransport.Utils.CoordinateSystem
return string.Empty;
}
- if (!string.IsNullOrWhiteSpace(doc.FileName))
+ int runtimeDocumentId = RuntimeHelpers.GetHashCode(doc);
+ return GetOrCreateDocumentKey(runtimeDocumentId, doc.FileName, doc.Title);
+ }
+
+ internal static string GetOrCreateDocumentKey(int runtimeDocumentId, string fileName, string title)
+ {
+ if (runtimeDocumentId <= 0)
{
- return doc.FileName;
+ return string.Empty;
}
- return doc.Title ?? string.Empty;
+ if (RuntimeDocumentKeys.TryGetValue(runtimeDocumentId, out string existingKey) &&
+ !string.IsNullOrWhiteSpace(existingKey))
+ {
+ return existingKey;
+ }
+
+ string resolvedKey;
+ if (!string.IsNullOrWhiteSpace(fileName))
+ {
+ resolvedKey = $"file:{fileName}";
+ }
+ else if (!string.IsNullOrWhiteSpace(title))
+ {
+ resolvedKey = $"runtime:{runtimeDocumentId}|title:{title}";
+ }
+ else
+ {
+ resolvedKey = $"runtime:{runtimeDocumentId}";
+ }
+
+ RuntimeDocumentKeys[runtimeDocumentId] = resolvedKey;
+ return resolvedKey;
}
public static string GetCurrentHostUpAxis(Document doc = null)
@@ -86,5 +117,11 @@ namespace NavisworksTransport.Utils.CoordinateSystem
return string.Equals(axisName, "Y", StringComparison.OrdinalIgnoreCase) ||
string.Equals(axisName, "Z", StringComparison.OrdinalIgnoreCase);
}
+
+ internal static void ResetForTests()
+ {
+ DocumentFragmentDefaultUpAxes.Clear();
+ RuntimeDocumentKeys.Clear();
+ }
}
}
diff --git a/src/Utils/CoordinateSystem/HostCoordinateAdapter.cs b/src/Utils/CoordinateSystem/HostCoordinateAdapter.cs
index ef2bf29..f95f366 100644
--- a/src/Utils/CoordinateSystem/HostCoordinateAdapter.cs
+++ b/src/Utils/CoordinateSystem/HostCoordinateAdapter.cs
@@ -179,6 +179,21 @@ namespace NavisworksTransport.Utils.CoordinateSystem
}
public Rotation3D FromCanonicalRotation(Quaternion canonicalRotation)
+ {
+ return FromCanonicalRotationLegacy(canonicalRotation);
+ }
+
+ public Rotation3D FromCanonicalRotationLegacy(Quaternion canonicalRotation)
+ {
+ Matrix4x4 canonicalLinear = Matrix4x4.CreateFromQuaternion(canonicalRotation);
+ Matrix4x4 hostLinear = FromCanonicalLinearTransform(canonicalLinear);
+ return CreateRotationFromLinearComponents(
+ hostLinear.M11, hostLinear.M12, hostLinear.M13,
+ hostLinear.M21, hostLinear.M22, hostLinear.M23,
+ hostLinear.M31, hostLinear.M32, hostLinear.M33);
+ }
+
+ public Rotation3D FromCanonicalRotationDirect(Quaternion canonicalRotation)
{
Matrix4x4 canonicalLinear = Matrix4x4.CreateFromQuaternion(canonicalRotation);
Matrix4x4 hostLinear = FromCanonicalLinearTransform(canonicalLinear);
@@ -196,6 +211,20 @@ namespace NavisworksTransport.Utils.CoordinateSystem
/// 这里不做任何轴交换、符号翻转或重新解释,只负责把已经确定好的宿主四元数写入 Navisworks。
///
public Rotation3D FromHostQuaternion(Quaternion hostRotation)
+ {
+ return FromHostQuaternionLegacy(hostRotation);
+ }
+
+ public Rotation3D FromHostQuaternionLegacy(Quaternion hostRotation)
+ {
+ Matrix4x4 hostLinear = Matrix4x4.CreateFromQuaternion(hostRotation);
+ return CreateRotationFromLinearComponents(
+ hostLinear.M11, hostLinear.M12, hostLinear.M13,
+ hostLinear.M21, hostLinear.M22, hostLinear.M23,
+ hostLinear.M31, hostLinear.M32, hostLinear.M33);
+ }
+
+ public Rotation3D FromHostQuaternionDirect(Quaternion hostRotation)
{
Quaternion normalizedHostRotation = Quaternion.Normalize(hostRotation);
return new Rotation3D(
@@ -276,6 +305,53 @@ namespace NavisworksTransport.Utils.CoordinateSystem
return Quaternion.Normalize(qz * qy * qx);
}
+ private static Rotation3D CreateRotationFromLinearComponents(
+ double m00, double m01, double m02,
+ double m10, double m11, double m12,
+ double m20, double m21, double m22)
+ {
+ double qx;
+ double qy;
+ double qz;
+ double qw;
+
+ double trace = m00 + m11 + m22;
+ if (trace > 0.0)
+ {
+ double s = Math.Sqrt(trace + 1.0) * 2.0;
+ qw = 0.25 * s;
+ qx = (m21 - m12) / s;
+ qy = (m02 - m20) / s;
+ qz = (m10 - m01) / s;
+ }
+ else if (m00 > m11 && m00 > m22)
+ {
+ double s = Math.Sqrt(1.0 + m00 - m11 - m22) * 2.0;
+ qw = (m21 - m12) / s;
+ qx = 0.25 * s;
+ qy = (m01 + m10) / s;
+ qz = (m02 + m20) / s;
+ }
+ else if (m11 > m22)
+ {
+ double s = Math.Sqrt(1.0 + m11 - m00 - m22) * 2.0;
+ qw = (m02 - m20) / s;
+ qx = (m01 + m10) / s;
+ qy = 0.25 * s;
+ qz = (m12 + m21) / s;
+ }
+ else
+ {
+ double s = Math.Sqrt(1.0 + m22 - m00 - m11) * 2.0;
+ qw = (m10 - m01) / s;
+ qx = (m02 + m20) / s;
+ qy = (m12 + m21) / s;
+ qz = 0.25 * s;
+ }
+
+ return new Rotation3D(qx, qy, qz, qw);
+ }
+
public CanonicalBounds3 ToCanonicalBounds3(CanonicalBounds3 hostBounds)
{
return RebuildBounds3(hostBounds, ToCanonicalPoint3);
diff --git a/src/Utils/ModelItemTransformHelper.cs b/src/Utils/ModelItemTransformHelper.cs
index d76da64..58460e5 100644
--- a/src/Utils/ModelItemTransformHelper.cs
+++ b/src/Utils/ModelItemTransformHelper.cs
@@ -589,6 +589,41 @@ namespace NavisworksTransport.Utils
LogIncrementalTransformActual(item, targetPosition, targetRotation);
}
+ ///
+ /// 读取物体当前实际几何姿态。
+ /// 优先使用 ModelGeometry.ActiveTransform,因为 ModelItem.Transform 只反映原始设计变换。
+ ///
+ public static bool TryGetCurrentGeometryRotation(ModelItem item, out Rotation3D rotation)
+ {
+ rotation = Rotation3D.Identity;
+ if (item == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ ModelGeometry geometry = item.FindFirstGeometry() ?? item.Geometry;
+ if (geometry?.ActiveTransform == null)
+ {
+ return false;
+ }
+
+ rotation = geometry.ActiveTransform.Factor().Rotation;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Warning($"[当前几何姿态] 读取 ActiveTransform 失败: {ex.Message}");
+ return false;
+ }
+ }
+
+ public static void LogCurrentGeometryTransformsForDebug(ModelItem item, string prefix)
+ {
+ LogGeometryLevelTransforms(item, prefix);
+ }
+
private static void LogIncrementalTransformActual(ModelItem item, Point3D targetPosition, Rotation3D targetRotation)
{
try
@@ -614,6 +649,8 @@ namespace NavisworksTransport.Utils
$"期望X=({targetLinear.Get(0, 0):F4},{targetLinear.Get(1, 0):F4},{targetLinear.Get(2, 0):F4}), " +
$"期望Y=({targetLinear.Get(0, 1):F4},{targetLinear.Get(1, 1):F4},{targetLinear.Get(2, 1):F4}), " +
$"期望Z=({targetLinear.Get(0, 2):F4},{targetLinear.Get(1, 2):F4},{targetLinear.Get(2, 2):F4})");
+
+ LogGeometryLevelTransforms(item, "[模型增量姿态应用后][GeometryAPI]");
}
catch (Exception ex)
{
@@ -792,19 +829,21 @@ namespace NavisworksTransport.Utils
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { item };
Vector3D currentScale = new Vector3D(1, 1, 1);
+ ModelGeometry geometry = item.FindFirstGeometry() ?? item.Geometry;
if (preserveCurrentScale)
{
- var currentComponents = item.Transform.Factor();
+ var currentBaseTransform = geometry?.PermanentOverrideTransform ?? item.Transform;
+ var currentComponents = currentBaseTransform.Factor();
currentScale = currentComponents.Scale;
}
doc.Models.ResetPermanentTransform(modelItems);
- var originalTransform = item.Transform;
+ var originalTransform = geometry?.OriginalTransform ?? item.Transform;
var originalComponents = originalTransform.Factor();
var originalRotation = originalComponents.Rotation;
- var originalBounds = item.BoundingBox();
+ var originalBounds = geometry?.BoundingBox ?? item.BoundingBox();
var originalCenterPos = originalBounds.Center;
Rotation3D deltaRotation = BuildDeltaRotation(originalRotation, targetRotation);
@@ -840,6 +879,7 @@ namespace NavisworksTransport.Utils
LogAbsoluteTransformDiagnostics(item, originalRotation, targetRotation, deltaRotation, originalCenterPos, targetPosition, translation);
doc.Models.OverridePermanentTransform(modelItems, transform, false);
+ LogAbsoluteTransformActual(item, preserveCurrentScale, currentScale, targetPosition, targetRotation);
}
private static Rotation3D BuildDeltaRotation(Rotation3D originalRotation, Rotation3D targetRotation)
@@ -893,6 +933,108 @@ namespace NavisworksTransport.Utils
}
}
+ private static void LogAbsoluteTransformActual(
+ ModelItem item,
+ bool preserveCurrentScale,
+ Vector3D currentScale,
+ Point3D targetPosition,
+ Rotation3D targetRotation)
+ {
+ try
+ {
+ Transform3D actualTransform = item.Transform;
+ var actualComponents = actualTransform.Factor();
+ var actualRotation = actualComponents.Rotation;
+ var actualScale = actualComponents.Scale;
+ var actualLinear = new Transform3D(actualRotation).Linear;
+ var targetLinear = new Transform3D(targetRotation).Linear;
+ var actualBounds = item.BoundingBox();
+ Point3D actualCenter = actualBounds?.Center ?? new Point3D(0, 0, 0);
+
+ LogManager.Info(
+ $"[模型姿态应用后] {item.DisplayName} PreserveScale={preserveCurrentScale}, " +
+ $"输入Scale=({currentScale.X:F4},{currentScale.Y:F4},{currentScale.Z:F4}), " +
+ $"实际Scale=({actualScale.X:F4},{actualScale.Y:F4},{actualScale.Z:F4}), " +
+ $"目标中心=({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3}), " +
+ $"实际中心=({actualCenter.X:F3},{actualCenter.Y:F3},{actualCenter.Z:F3})");
+
+ LogManager.Info(
+ $"[模型姿态应用后] {item.DisplayName} 目标Transform轴: " +
+ $"X=({targetLinear.Get(0, 0):F4},{targetLinear.Get(1, 0):F4},{targetLinear.Get(2, 0):F4}), " +
+ $"Y=({targetLinear.Get(0, 1):F4},{targetLinear.Get(1, 1):F4},{targetLinear.Get(2, 1):F4}), " +
+ $"Z=({targetLinear.Get(0, 2):F4},{targetLinear.Get(1, 2):F4},{targetLinear.Get(2, 2):F4})");
+
+ LogManager.Info(
+ $"[模型姿态应用后] {item.DisplayName} 实际Transform轴: " +
+ $"X=({actualLinear.Get(0, 0):F4},{actualLinear.Get(1, 0):F4},{actualLinear.Get(2, 0):F4}), " +
+ $"Y=({actualLinear.Get(0, 1):F4},{actualLinear.Get(1, 1):F4},{actualLinear.Get(2, 1):F4}), " +
+ $"Z=({actualLinear.Get(0, 2):F4},{actualLinear.Get(1, 2):F4},{actualLinear.Get(2, 2):F4})");
+
+ LogGeometryLevelTransforms(item, "[模型姿态应用后][GeometryAPI]");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Warning($"[模型姿态应用后] 输出诊断日志失败: {ex.Message}");
+ }
+ }
+
+ private static void LogGeometryLevelTransforms(ModelItem item, string prefix)
+ {
+ if (item == null)
+ {
+ return;
+ }
+
+ try
+ {
+ ModelGeometry geometry = item.FindFirstGeometry() ?? item.Geometry;
+ if (geometry == null)
+ {
+ LogManager.Info($"{prefix} Geometry=null");
+ return;
+ }
+
+ LogManager.Info(
+ $"{prefix} GeometryType={geometry.GetType().FullName}, " +
+ $"FragmentCount={geometry.FragmentCount}, " +
+ $"BoundsCenter=({geometry.BoundingBox.Center.X:F3},{geometry.BoundingBox.Center.Y:F3},{geometry.BoundingBox.Center.Z:F3})");
+
+ LogTransformProperty(geometry.OriginalTransform, $"{prefix} OriginalTransform");
+ LogTransformProperty(geometry.PermanentOverrideTransform, $"{prefix} PermanentOverrideTransform");
+ LogTransformProperty(geometry.PermanentTransform, $"{prefix} PermanentTransform");
+ LogTransformProperty(geometry.ActiveTransform, $"{prefix} ActiveTransform");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Warning($"{prefix} 读取几何层当前变换失败: {ex.Message}");
+ }
+ }
+
+ private static void LogTransformProperty(Transform3D transform, string prefix)
+ {
+ if (transform == null)
+ {
+ LogManager.Info($"{prefix}=null");
+ return;
+ }
+
+ try
+ {
+ var linear = transform.Linear;
+ var components = transform.Factor();
+ LogManager.Info(
+ $"{prefix}: " +
+ $"X=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
+ $"Y=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
+ $"Z=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4}), " +
+ $"T=({components.Translation.X:F3},{components.Translation.Y:F3},{components.Translation.Z:F3})");
+ }
+ catch (Exception ex)
+ {
+ LogManager.Warning($"{prefix} 读取失败: {ex.Message}");
+ }
+ }
+
///
/// 将物体移动到指定位置和朝向,同时保持缩放比例
/// 专为虚拟物体设计,避免缩放被覆盖