refactor: 清理 fragment 参考姿态残留代码(0.18.2 默认轴约定后的死代码)

删除:
- RealObjectReferencePoseResolver: TryResolveFromModelItem / TryResolveFromFragmentMatrices
  (fragment 代表姿态解析链,已无调用者;保留诊断用 TryDetectDefaultUpAxis)
- RealObjectReferencePose.cs(无引用死文件)
- PathAnimationManager: _realObjectReferenceHostSemantic* 字段(无消费)
- FragmentRepresentativePoseHelper: TryGetRepresentativeRotation(ModelItem/矩阵集合)
  与 TryInterpretRepresentativeFrame(仅死链使用;保留诊断用 TryGetRepresentativeFrame)
- 测试:RealObjectReferencePoseResolverTests(测死链)、
  FragmentRepresentativePoseHelperTests 缩减至在用方法

保留(非残留):
- GeometryHelper/GeometryCacheManager fragment(几何三角形生成)
- FragmentDefaultUpContext / RealObjectRailAxisConventionResolver(在用)
- TestAutomationHttpService(集成测试服务)

验证:单元测试 187/187、集成真实物体动画 3/3、F1 Rail+42" Completed
This commit is contained in:
tian 2026-08-05 23:53:39 +08:00
parent 10e93b1c32
commit 7c4783dca6
8 changed files with 22 additions and 767 deletions

View File

@ -103,7 +103,6 @@
<Compile Include="UnitTests\CoordinateSystem\ProjectReferenceFrameTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\RealObjectPlanarPoseSolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\RealObjectProjectedExtentResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\RealObjectReferencePoseResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\RealObjectRailAxisConventionResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\RealObjectRailExtentResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\RotatedObjectExtentHelperTests.cs" />

View File

@ -364,7 +364,6 @@
<Compile Include="src\Utils\CoordinateSystem\CanonicalTrackedPositionResolver.cs" />
<Compile Include="src\Utils\CoordinateSystem\FragmentDefaultUpContext.cs" />
<Compile Include="src\Utils\CoordinateSystem\FragmentRepresentativePoseHelper.cs" />
<Compile Include="src\Utils\CoordinateSystem\RealObjectReferencePose.cs" />
<Compile Include="src\Utils\CoordinateSystem\RealObjectReferencePoseResolver.cs" />
<Compile Include="src\Utils\CoordinateSystem\HoistingRealObjectPoseHelper.cs" />
<Compile Include="src\Utils\CoordinateSystem\PathTargetFrame.cs" />

View File

@ -1,6 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
using System;
using System.Numerics;
namespace NavisworksTransport.UnitTests.CoordinateSystem
@ -8,71 +7,6 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
[TestClass]
public class FragmentRepresentativePoseHelperTests
{
[TestMethod]
public void ShouldAverageFragmentMatrices_AndIgnoreTranslation()
{
Quaternion expectedRotation = Quaternion.Normalize(
Quaternion.CreateFromAxisAngle(Vector3.UnitZ, (float)(Math.PI / 6.0)));
double[] fragmentMatrix1 = CreateFragmentMatrix(expectedRotation, new Vector3(10.0f, 20.0f, 30.0f));
double[] fragmentMatrix2 = CreateFragmentMatrix(expectedRotation, new Vector3(-3.0f, 8.0f, 1.0f));
bool ok = FragmentRepresentativePoseHelper.TryGetRepresentativeRotation(
new[] { fragmentMatrix1, fragmentMatrix2 },
out Quaternion representativeRotation);
Assert.IsTrue(ok);
AssertVector(
Vector3.Transform(Vector3.UnitX, representativeRotation),
Vector3.Transform(Vector3.UnitX, expectedRotation),
1e-4);
AssertVector(
Vector3.Transform(Vector3.UnitY, representativeRotation),
Vector3.Transform(Vector3.UnitY, expectedRotation),
1e-4);
AssertVector(
Vector3.Transform(Vector3.UnitZ, representativeRotation),
Vector3.Transform(Vector3.UnitZ, expectedRotation),
1e-4);
}
[TestMethod]
public void ShouldIgnoreQuaternionSignWhenAveragingFragments()
{
Quaternion expectedRotation = Quaternion.Normalize(
Quaternion.CreateFromAxisAngle(Vector3.UnitX, (float)(Math.PI / 4.0)));
Quaternion oppositeSignRotation = new Quaternion(
-expectedRotation.X,
-expectedRotation.Y,
-expectedRotation.Z,
-expectedRotation.W);
bool ok = FragmentRepresentativePoseHelper.TryGetRepresentativeRotation(
new[] { expectedRotation, oppositeSignRotation },
out Quaternion representativeRotation);
Assert.IsTrue(ok);
AssertVector(
Vector3.Transform(Vector3.UnitY, representativeRotation),
Vector3.Transform(Vector3.UnitY, expectedRotation),
1e-4);
AssertVector(
Vector3.Transform(Vector3.UnitZ, representativeRotation),
Vector3.Transform(Vector3.UnitZ, expectedRotation),
1e-4);
}
[TestMethod]
public void ShouldFail_WhenNoFragmentRotationsAreAvailable()
{
bool ok = FragmentRepresentativePoseHelper.TryGetRepresentativeRotation(
Array.Empty<double[]>(),
out Quaternion representativeRotation);
Assert.IsFalse(ok);
Assert.AreEqual(Quaternion.Identity, representativeRotation);
}
[TestMethod]
public void ShouldPreserveAxes_ForObservedYUpRealObjectFragmentBasis()
{
@ -126,47 +60,11 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
AssertVector(Vector3.Transform(Vector3.UnitZ, frame.Rotation), axisZ, 1e-4);
}
[TestMethod]
public void TryInterpretRepresentativeFrame_YUpHost_WithFragmentDefaultUpZ_ShouldMapZToHostY()
{
var raw = new FragmentRepresentativePoseHelper.RepresentativeFrame(
new Vector3(1.0f, 0.0f, 0.0f),
new Vector3(0.0f, 0.0f, -1.0f),
new Vector3(0.0f, 1.0f, 0.0f),
Quaternion.Identity);
bool ok = FragmentRepresentativePoseHelper.TryInterpretRepresentativeFrame(
raw,
"Z",
"Y",
out var interpreted);
Assert.IsTrue(ok);
AssertVector(interpreted.AxisX, new Vector3(1.0f, 0.0f, 0.0f), 1e-4);
AssertVector(interpreted.AxisY, new Vector3(0.0f, 1.0f, 0.0f), 1e-4);
AssertVector(interpreted.AxisZ, new Vector3(0.0f, 0.0f, 1.0f), 1e-4);
}
private static double[] CreateFragmentMatrix(Quaternion rotation, Vector3 translation)
{
Vector3 axisX = Vector3.Transform(Vector3.UnitX, rotation);
Vector3 axisY = Vector3.Transform(Vector3.UnitY, rotation);
Vector3 axisZ = Vector3.Transform(Vector3.UnitZ, rotation);
return new[]
{
(double)axisX.X, (double)axisX.Y, (double)axisX.Z, 0.0,
(double)axisY.X, (double)axisY.Y, (double)axisY.Z, 0.0,
(double)axisZ.X, (double)axisZ.Y, (double)axisZ.Z, 0.0,
(double)translation.X, (double)translation.Y, (double)translation.Z, 1.0
};
}
private static void AssertVector(Vector3 actual, Vector3 expected, double tolerance)
{
Assert.AreEqual(expected.X, actual.X, tolerance);
Assert.AreEqual(expected.Y, actual.Y, tolerance);
Assert.AreEqual(expected.Z, actual.Z, tolerance);
Assert.AreEqual(expected.X, actual.X, tolerance, "X 分量");
Assert.AreEqual(expected.Y, actual.Y, tolerance, "Y 分量");
Assert.AreEqual(expected.Z, actual.Z, tolerance, "Z 分量");
}
}
}

View File

@ -1,160 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
using System.Collections.Generic;
using System.Numerics;
namespace NavisworksTransport.UnitTests.CoordinateSystem
{
[TestClass]
public class RealObjectReferencePoseResolverTests
{
[TestMethod]
public void YUp_HostWithFragmentDefaultZ_ShouldInterpretRepresentativeFrameToHostSemantics()
{
var fragmentMatrices = new List<double[]>
{
CreateMatrix(
axisX: new Vector3(1.0f, 0.0f, 0.0f),
axisY: new Vector3(0.0f, 1.0f, 0.0f),
axisZ: new Vector3(0.0f, 0.0f, 1.0f))
};
bool ok = RealObjectReferencePoseResolver.TryResolveFromFragmentMatrices(
fragmentMatrices,
fragmentDefaultUpAxis: "Z",
hostUpAxis: "Y",
out RealObjectReferencePose pose);
Assert.IsTrue(ok);
AssertVector(pose.RawAxisX, 1.0, 0.0, 0.0);
AssertVector(pose.RawAxisY, 0.0, 1.0, 0.0);
AssertVector(pose.RawAxisZ, 0.0, 0.0, 1.0);
AssertVector(pose.AxisX, 1.0, 0.0, 0.0);
AssertVector(pose.AxisY, 0.0, 0.0, 1.0);
AssertVector(pose.AxisZ, 0.0, -1.0, 0.0);
Assert.AreEqual(LocalAxisDirection.PositiveX, pose.HostWorldXAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveY, pose.HostWorldYAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveZ, pose.HostWorldZAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveX, pose.HostSemanticXAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveZ, pose.HostSemanticYAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.NegativeY, pose.HostSemanticZAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveZ, pose.HostUpLocalAxis);
}
[TestMethod]
public void YUp_WorldAxisMapping_ShouldFollowRawRepresentativeFrame()
{
var fragmentMatrices = new List<double[]>
{
CreateMatrix(
axisX: new Vector3(0.0f, 0.0f, 1.0f),
axisY: new Vector3(1.0f, 0.0f, 0.0f),
axisZ: new Vector3(0.0f, 1.0f, 0.0f))
};
bool ok = RealObjectReferencePoseResolver.TryResolveFromFragmentMatrices(
fragmentMatrices,
fragmentDefaultUpAxis: "Z",
hostUpAxis: "Y",
out RealObjectReferencePose pose);
Assert.IsTrue(ok);
Assert.AreEqual(LocalAxisDirection.PositiveY, pose.HostWorldXAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveZ, pose.HostWorldYAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveX, pose.HostWorldZAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveX, pose.HostSemanticXAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveZ, pose.HostSemanticYAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.NegativeY, pose.HostSemanticZAxisLocalAxis);
}
[TestMethod]
public void DetectDefaultUpAxis_ShouldReturnZ_WhenRepresentativeZMatchesHostUp()
{
var fragmentMatrices = new List<double[]>
{
CreateMatrix(
axisX: new Vector3(1.0f, 0.0f, 0.0f),
axisY: new Vector3(0.0f, 0.0f, -1.0f),
axisZ: new Vector3(0.0f, 1.0f, 0.0f))
};
bool ok = RealObjectReferencePoseResolver.TryDetectDefaultUpAxis(
fragmentMatrices,
"Y",
out string detectedAxis,
out float yAlignment,
out float zAlignment);
Assert.IsTrue(ok);
Assert.AreEqual("Z", detectedAxis);
Assert.AreEqual(0.0, yAlignment, 1e-6);
Assert.AreEqual(1.0, zAlignment, 1e-6);
}
[TestMethod]
public void YUp_WorldAxisMapping_ShouldAcceptSlightlyRotatedRawFragmentFrame()
{
var fragmentMatrices = new List<double[]>
{
CreateMatrix(
axisX: Vector3.Normalize(new Vector3(0.9980f, 0.0638f, 0.0f)),
axisY: new Vector3(0.0f, 0.0f, -1.0f),
axisZ: Vector3.Normalize(new Vector3(-0.0638f, 0.9980f, 0.0f)))
};
bool ok = RealObjectReferencePoseResolver.TryResolveFromFragmentMatrices(
fragmentMatrices,
fragmentDefaultUpAxis: "Y",
hostUpAxis: "Y",
out RealObjectReferencePose pose);
Assert.IsTrue(ok);
Assert.AreEqual(LocalAxisDirection.PositiveX, pose.HostWorldXAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveZ, pose.HostWorldYAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.NegativeY, pose.HostWorldZAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveX, pose.HostSemanticXAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveY, pose.HostSemanticYAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveZ, pose.HostSemanticZAxisLocalAxis);
Assert.AreEqual(LocalAxisDirection.PositiveY, pose.HostUpLocalAxis);
}
[TestMethod]
public void YUp_WorldAxisMapping_ShouldRejectAmbiguousRawFragmentFrame()
{
var fragmentMatrices = new List<double[]>
{
CreateMatrix(
axisX: Vector3.Normalize(new Vector3(0.80f, 0.60f, 0.0f)),
axisY: new Vector3(0.0f, 0.0f, -1.0f),
axisZ: Vector3.Normalize(new Vector3(-0.60f, 0.80f, 0.0f)))
};
bool ok = RealObjectReferencePoseResolver.TryResolveFromFragmentMatrices(
fragmentMatrices,
fragmentDefaultUpAxis: "Y",
hostUpAxis: "Y",
out RealObjectReferencePose pose);
Assert.IsFalse(ok);
Assert.IsNull(pose);
}
private static double[] CreateMatrix(Vector3 axisX, Vector3 axisY, Vector3 axisZ)
{
return new double[]
{
axisX.X, axisX.Y, axisX.Z, 0.0,
axisY.X, axisY.Y, axisY.Z, 0.0,
axisZ.X, axisZ.Y, axisZ.Z, 0.0,
0.0, 0.0, 0.0, 1.0
};
}
private static void AssertVector(Vector3 actual, double x, double y, double z, double tolerance = 1e-6)
{
Assert.AreEqual(x, actual.X, tolerance);
Assert.AreEqual(y, actual.Y, tolerance);
Assert.AreEqual(z, actual.Z, tolerance);
}
}
}

View File

@ -119,9 +119,6 @@ namespace NavisworksTransport.Core.Animation
private LocalAxisDirection _realObjectReferenceHostWorldXAxisLocalAxis = LocalAxisDirection.PositiveX;
private LocalAxisDirection _realObjectReferenceHostWorldYAxisLocalAxis = LocalAxisDirection.PositiveY;
private LocalAxisDirection _realObjectReferenceHostWorldZAxisLocalAxis = LocalAxisDirection.PositiveZ;
private LocalAxisDirection _realObjectReferenceHostSemanticXAxisLocalAxis = LocalAxisDirection.PositiveX;
private LocalAxisDirection _realObjectReferenceHostSemanticYAxisLocalAxis = LocalAxisDirection.PositiveY;
private LocalAxisDirection _realObjectReferenceHostSemanticZAxisLocalAxis = LocalAxisDirection.PositiveZ;
private LocalAxisDirection _realObjectReferenceHostUpLocalAxis = LocalAxisDirection.PositiveY;
private bool _hasRealObjectReferenceRotation = false;
private LocalAxisDirection _realObjectPlanarSelectedForwardAxis = LocalAxisDirection.PositiveX;
@ -5411,9 +5408,6 @@ namespace NavisworksTransport.Core.Animation
_realObjectReferenceHostWorldXAxisLocalAxis = LocalAxisDirection.PositiveX;
_realObjectReferenceHostWorldYAxisLocalAxis = LocalAxisDirection.PositiveY;
_realObjectReferenceHostWorldZAxisLocalAxis = LocalAxisDirection.PositiveZ;
_realObjectReferenceHostSemanticXAxisLocalAxis = LocalAxisDirection.PositiveX;
_realObjectReferenceHostSemanticYAxisLocalAxis = hostUpLocal;
_realObjectReferenceHostSemanticZAxisLocalAxis = LocalAxisDirection.PositiveZ;
_hasRealObjectReferenceRotation = true;
LogManager.Info(
@ -5513,9 +5507,6 @@ namespace NavisworksTransport.Core.Animation
_realObjectReferenceHostWorldXAxisLocalAxis = LocalAxisDirection.PositiveX;
_realObjectReferenceHostWorldYAxisLocalAxis = LocalAxisDirection.PositiveY;
_realObjectReferenceHostWorldZAxisLocalAxis = LocalAxisDirection.PositiveZ;
_realObjectReferenceHostSemanticXAxisLocalAxis = LocalAxisDirection.PositiveX;
_realObjectReferenceHostSemanticYAxisLocalAxis = LocalAxisDirection.PositiveY;
_realObjectReferenceHostSemanticZAxisLocalAxis = LocalAxisDirection.PositiveZ;
_realObjectReferenceHostUpLocalAxis = LocalAxisDirection.PositiveY;
_hasRealObjectReferenceRotation = false;
_realObjectPlanarSelectedForwardAxis = LocalAxisDirection.PositiveX;

View File

@ -2,19 +2,16 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Autodesk.Navisworks.Api;
using ComApi = Autodesk.Navisworks.Api.Interop.ComApi;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// Fragment 代表姿态辅助工具
///
/// 语义:
/// - 从 fragment 的 world 矩阵中提取旋转
/// - 对多个 fragment 旋转做同半球加权平均
/// - 返回一个可作为真实物体参考位姿的代表旋转
/// Fragment 代表帧辅助工具(诊断用途)
///
/// 说明:真实物体姿态链已不依赖 fragment0.18.2 起使用默认轴约定,
/// forward=+X、up=宿主 up。本工具仅保留系统诊断面板
/// SystemManagementViewModel 方法5Fragment 矩阵分析)所需的
/// TryGetRepresentativeFrame / TryExtractRotationFromFragmentMatrix。
/// </summary>
public static class FragmentRepresentativePoseHelper
{
@ -37,58 +34,8 @@ namespace NavisworksTransport.Utils.CoordinateSystem
}
/// <summary>
/// 从 ModelItem 的 fragment 几何中提取代表旋转
/// 从 fragment 矩阵集合中提取代表帧
/// </summary>
public static bool TryGetRepresentativeRotation(ModelItem item, out Quaternion representativeRotation)
{
representativeRotation = Quaternion.Identity;
if (item == null)
{
return false;
}
try
{
var selection = ComApiBridge.ToInwOpSelection(new ModelItemCollection { item });
var fragmentInfos = GeometryHelper.GetAllFragments(selection);
return TryGetRepresentativeRotation(
fragmentInfos.Select(fragmentInfo => fragmentInfo.TransformMatrix),
out representativeRotation);
}
catch (Exception ex)
{
LogManager.Warning($"[fragment代表姿态] 从ModelItem提取失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 从 fragment 矩阵集合中提取代表旋转。
/// </summary>
public static bool TryGetRepresentativeRotation(
IEnumerable<double[]> fragmentMatrices,
out Quaternion representativeRotation)
{
representativeRotation = Quaternion.Identity;
if (fragmentMatrices == null)
{
return false;
}
var fragmentRotations = new List<Quaternion>();
foreach (double[] fragmentMatrix in fragmentMatrices)
{
if (TryExtractRotationFromFragmentMatrix(fragmentMatrix, out Quaternion fragmentRotation))
{
fragmentRotations.Add(fragmentRotation);
}
}
return TryGetRepresentativeRotation(fragmentRotations, out representativeRotation);
}
public static bool TryGetRepresentativeFrame(
IEnumerable<double[]> fragmentMatrices,
out RepresentativeFrame representativeFrame)
@ -121,80 +68,10 @@ namespace NavisworksTransport.Utils.CoordinateSystem
return true;
}
public static bool TryInterpretRepresentativeFrame(
RepresentativeFrame rawFrame,
string fragmentDefaultUpAxis,
string hostUpAxis,
out RepresentativeFrame interpretedFrame)
{
interpretedFrame = default;
bool hostIsYUp = string.Equals(hostUpAxis, "Y", StringComparison.OrdinalIgnoreCase);
bool fragmentIsYUp = string.Equals(fragmentDefaultUpAxis, "Y", StringComparison.OrdinalIgnoreCase);
Vector3 semanticAxisX = rawFrame.AxisX;
Vector3 semanticAxisY;
Vector3 semanticAxisZ;
if (hostIsYUp == fragmentIsYUp)
{
semanticAxisY = rawFrame.AxisY;
semanticAxisZ = rawFrame.AxisZ;
}
else if (hostIsYUp)
{
// fragment 按 ZUp 解释,宿主按 YUp 解释X 保持Y 取原始 ZZ 由右手系重建。
semanticAxisY = rawFrame.AxisZ;
semanticAxisZ = Vector3.Normalize(Vector3.Cross(semanticAxisX, semanticAxisY));
}
else
{
// fragment 按 YUp 解释,宿主按 ZUp 解释X 保持Z 取原始 YY 由右手系重建。
semanticAxisZ = rawFrame.AxisY;
semanticAxisY = Vector3.Normalize(Vector3.Cross(semanticAxisZ, semanticAxisX));
}
if (!TryNormalize(semanticAxisX, out semanticAxisX) ||
!TryNormalize(semanticAxisY, out semanticAxisY) ||
!TryNormalize(semanticAxisZ, out semanticAxisZ))
{
return false;
}
// 用解释后的三轴重新正交化,保证右手系稳定。
Vector3 axisX = semanticAxisX;
Vector3 axisY = semanticAxisY - Vector3.Dot(semanticAxisY, axisX) * axisX;
if (!TryNormalize(axisY, out axisY))
{
return false;
}
Vector3 axisZ = Vector3.Cross(axisX, axisY);
if (!TryNormalize(axisZ, out axisZ))
{
return false;
}
if (hostIsYUp && Vector3.Dot(axisY, semanticAxisY) < 0.0f)
{
axisY = -axisY;
axisZ = -axisZ;
}
else if (!hostIsYUp && Vector3.Dot(axisZ, semanticAxisZ) < 0.0f)
{
axisY = -axisY;
axisZ = -axisZ;
}
Quaternion rotation = Quaternion.Normalize(CreateQuaternionFromBasis(axisX, axisY, axisZ));
interpretedFrame = new RepresentativeFrame(axisX, axisY, axisZ, rotation);
return true;
}
/// <summary>
/// 从 fragment 旋转集合中提取代表旋转
/// 从 fragment 旋转集合中提取代表旋转(同半球加权平均)。
/// </summary>
public static bool TryGetRepresentativeRotation(
private static bool TryGetRepresentativeRotation(
IEnumerable<Quaternion> fragmentRotations,
out Quaternion representativeRotation)
{

View File

@ -1,67 +0,0 @@
using System.Numerics;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 真实物体在当前宿主语义下解释后的参考姿态。
/// 这不是 fragment 原始几何框架,而是已经结合 Fragment默认Up 解释后的结果。
/// </summary>
public sealed class RealObjectReferencePose
{
public RealObjectReferencePose(
Quaternion rotation,
Vector3 rawAxisX,
Vector3 rawAxisY,
Vector3 rawAxisZ,
Vector3 axisX,
Vector3 axisY,
Vector3 axisZ,
LocalAxisDirection hostWorldXAxisLocalAxis,
LocalAxisDirection hostWorldYAxisLocalAxis,
LocalAxisDirection hostWorldZAxisLocalAxis,
LocalAxisDirection hostSemanticXAxisLocalAxis,
LocalAxisDirection hostSemanticYAxisLocalAxis,
LocalAxisDirection hostSemanticZAxisLocalAxis,
LocalAxisDirection hostUpLocalAxis,
int fragmentCount,
string fragmentDefaultUpAxis,
string hostUpAxis)
{
Rotation = rotation;
RawAxisX = rawAxisX;
RawAxisY = rawAxisY;
RawAxisZ = rawAxisZ;
AxisX = axisX;
AxisY = axisY;
AxisZ = axisZ;
HostWorldXAxisLocalAxis = hostWorldXAxisLocalAxis;
HostWorldYAxisLocalAxis = hostWorldYAxisLocalAxis;
HostWorldZAxisLocalAxis = hostWorldZAxisLocalAxis;
HostSemanticXAxisLocalAxis = hostSemanticXAxisLocalAxis;
HostSemanticYAxisLocalAxis = hostSemanticYAxisLocalAxis;
HostSemanticZAxisLocalAxis = hostSemanticZAxisLocalAxis;
HostUpLocalAxis = hostUpLocalAxis;
FragmentCount = fragmentCount;
FragmentDefaultUpAxis = fragmentDefaultUpAxis;
HostUpAxis = hostUpAxis;
}
public Quaternion Rotation { get; }
public Vector3 RawAxisX { get; }
public Vector3 RawAxisY { get; }
public Vector3 RawAxisZ { get; }
public Vector3 AxisX { get; }
public Vector3 AxisY { get; }
public Vector3 AxisZ { get; }
public LocalAxisDirection HostWorldXAxisLocalAxis { get; }
public LocalAxisDirection HostWorldYAxisLocalAxis { get; }
public LocalAxisDirection HostWorldZAxisLocalAxis { get; }
public LocalAxisDirection HostSemanticXAxisLocalAxis { get; }
public LocalAxisDirection HostSemanticYAxisLocalAxis { get; }
public LocalAxisDirection HostSemanticZAxisLocalAxis { get; }
public LocalAxisDirection HostUpLocalAxis { get; }
public int FragmentCount { get; }
public string FragmentDefaultUpAxis { get; }
public string HostUpAxis { get; }
}
}

View File

@ -1,305 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Autodesk.Navisworks.Api;
using ComApiBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 统一的真实物体参考姿态解析入口。
/// 负责:
/// 1. 从 fragment 提取代表姿态
/// 2. 按 Fragment默认Up 解释成当前宿主语义下的真实姿态
/// 3. 为 Ground / Hoisting / Rail 提供统一参考姿态来源
/// 真实物体 fragment 姿态解析入口(仅保留诊断用途)。
///
/// 说明:真实物体姿态链已不依赖 fragment0.18.2 起使用默认轴约定,
/// forward=+X、up=宿主 up。原 TryResolveFromModelItem /
/// TryResolveFromFragmentMatricesfragment 代表姿态解析链)已删除。
/// 本类仅保留系统诊断面板SystemManagementViewModel 方法5使用的
/// TryDetectDefaultUpAxis。
/// </summary>
public static class RealObjectReferencePoseResolver
{
private const float WorldAxisMinimumAlignment = 0.98f;
private const float SemanticAxisMinimumAlignment = 0.985f;
private const float MinimumBestToSecondGap = 0.02f;
public static bool TryResolveFromModelItem(
ModelItem sourceObject,
Document doc,
out RealObjectReferencePose referencePose)
{
referencePose = null;
if (sourceObject == null)
{
return false;
}
try
{
var selection = ComApiBridge.ToInwOpSelection(new ModelItemCollection { sourceObject });
var fragmentInfos = GeometryHelper.GetAllFragments(selection);
try
{
var fragmentMatrices = fragmentInfos
.Where(fragmentInfo => fragmentInfo.TransformMatrix != null && fragmentInfo.TransformMatrix.Length == 16)
.Select(fragmentInfo => fragmentInfo.TransformMatrix)
.ToList();
return TryResolveFromFragmentMatrices(fragmentMatrices, doc, out referencePose);
}
finally
{
if (fragmentInfos != null)
{
foreach (var fragmentInfo in fragmentInfos)
{
if (fragmentInfo?.Fragment != null)
{
try
{
Marshal.ReleaseComObject(fragmentInfo.Fragment);
}
catch
{
}
}
}
}
if (selection != null)
{
try
{
Marshal.ReleaseComObject(selection);
}
catch
{
}
}
}
}
catch (Exception ex)
{
LogManager.Warning($"[真实物体参考姿态] 从 fragment 提取代表姿态失败: {ex.Message}");
return false;
}
}
public static bool TryResolveFromFragmentMatrices(
IReadOnlyCollection<double[]> fragmentMatrices,
Document doc,
out RealObjectReferencePose referencePose)
{
string fragmentDefaultUpAxis = FragmentDefaultUpContext.GetCurrentDocumentFragmentDefaultUpAxis(doc);
string hostUpAxis = FragmentDefaultUpContext.GetCurrentHostUpAxis(doc);
return TryResolveFromFragmentMatrices(fragmentMatrices, fragmentDefaultUpAxis, hostUpAxis, out referencePose);
}
public static bool TryResolveFromFragmentMatrices(
IReadOnlyCollection<double[]> fragmentMatrices,
string fragmentDefaultUpAxis,
string hostUpAxis,
out RealObjectReferencePose referencePose)
{
referencePose = null;
if (fragmentMatrices == null || fragmentMatrices.Count == 0)
{
return false;
}
if (!FragmentRepresentativePoseHelper.TryGetRepresentativeFrame(fragmentMatrices, out var rawFrame))
{
return false;
}
if (!FragmentRepresentativePoseHelper.TryInterpretRepresentativeFrame(
rawFrame,
fragmentDefaultUpAxis,
hostUpAxis,
out var interpretedFrame))
{
return false;
}
if (!TryResolveSemanticLocalAxes(
rawFrame,
interpretedFrame,
out var hostSemanticXAxisLocalAxis,
out var hostSemanticYAxisLocalAxis,
out var hostSemanticZAxisLocalAxis))
{
return false;
}
if (!TryResolveWorldLocalAxes(
rawFrame,
out var hostWorldXAxisLocalAxis,
out var hostWorldYAxisLocalAxis,
out var hostWorldZAxisLocalAxis))
{
return false;
}
referencePose = new RealObjectReferencePose(
interpretedFrame.Rotation,
rawFrame.AxisX,
rawFrame.AxisY,
rawFrame.AxisZ,
interpretedFrame.AxisX,
interpretedFrame.AxisY,
interpretedFrame.AxisZ,
hostWorldXAxisLocalAxis,
hostWorldYAxisLocalAxis,
hostWorldZAxisLocalAxis,
hostSemanticXAxisLocalAxis,
hostSemanticYAxisLocalAxis,
hostSemanticZAxisLocalAxis,
string.Equals(hostUpAxis, "Y", StringComparison.OrdinalIgnoreCase)
? hostSemanticYAxisLocalAxis
: hostSemanticZAxisLocalAxis,
fragmentMatrices.Count,
fragmentDefaultUpAxis,
hostUpAxis);
return true;
}
private static bool TryResolveWorldLocalAxes(
FragmentRepresentativePoseHelper.RepresentativeFrame rawFrame,
out LocalAxisDirection hostWorldXAxisLocalAxis,
out LocalAxisDirection hostWorldYAxisLocalAxis,
out LocalAxisDirection hostWorldZAxisLocalAxis)
{
hostWorldXAxisLocalAxis = LocalAxisDirection.PositiveX;
hostWorldYAxisLocalAxis = LocalAxisDirection.PositiveY;
hostWorldZAxisLocalAxis = LocalAxisDirection.PositiveZ;
if (!TryMatchRawAxisDirection(
Vector3.UnitX,
rawFrame.AxisX,
rawFrame.AxisY,
rawFrame.AxisZ,
WorldAxisMinimumAlignment,
out hostWorldXAxisLocalAxis))
{
return false;
}
if (!TryMatchRawAxisDirection(
Vector3.UnitY,
rawFrame.AxisX,
rawFrame.AxisY,
rawFrame.AxisZ,
WorldAxisMinimumAlignment,
out hostWorldYAxisLocalAxis))
{
return false;
}
if (!TryMatchRawAxisDirection(
Vector3.UnitZ,
rawFrame.AxisX,
rawFrame.AxisY,
rawFrame.AxisZ,
WorldAxisMinimumAlignment,
out hostWorldZAxisLocalAxis))
{
return false;
}
return true;
}
private static bool TryResolveSemanticLocalAxes(
FragmentRepresentativePoseHelper.RepresentativeFrame rawFrame,
FragmentRepresentativePoseHelper.RepresentativeFrame interpretedFrame,
out LocalAxisDirection hostSemanticXAxisLocalAxis,
out LocalAxisDirection hostSemanticYAxisLocalAxis,
out LocalAxisDirection hostSemanticZAxisLocalAxis)
{
hostSemanticXAxisLocalAxis = LocalAxisDirection.PositiveX;
hostSemanticYAxisLocalAxis = LocalAxisDirection.PositiveY;
hostSemanticZAxisLocalAxis = LocalAxisDirection.PositiveZ;
if (!TryMatchRawAxisDirection(
interpretedFrame.AxisX,
rawFrame.AxisX,
rawFrame.AxisY,
rawFrame.AxisZ,
SemanticAxisMinimumAlignment,
out hostSemanticXAxisLocalAxis))
{
return false;
}
if (!TryMatchRawAxisDirection(
interpretedFrame.AxisY,
rawFrame.AxisX,
rawFrame.AxisY,
rawFrame.AxisZ,
SemanticAxisMinimumAlignment,
out hostSemanticYAxisLocalAxis))
{
return false;
}
if (!TryMatchRawAxisDirection(
interpretedFrame.AxisZ,
rawFrame.AxisX,
rawFrame.AxisY,
rawFrame.AxisZ,
SemanticAxisMinimumAlignment,
out hostSemanticZAxisLocalAxis))
{
return false;
}
return true;
}
private static bool TryMatchRawAxisDirection(
Vector3 targetAxis,
Vector3 rawAxisX,
Vector3 rawAxisY,
Vector3 rawAxisZ,
float minimumAlignment,
out LocalAxisDirection axisDirection)
{
axisDirection = LocalAxisDirection.PositiveX;
float bestAlignment = float.NegativeInfinity;
float secondBestAlignment = float.NegativeInfinity;
TryUpdateBestMatch(targetAxis, rawAxisX, LocalAxisDirection.PositiveX, ref bestAlignment, ref secondBestAlignment, ref axisDirection);
TryUpdateBestMatch(targetAxis, -rawAxisX, LocalAxisDirection.NegativeX, ref bestAlignment, ref secondBestAlignment, ref axisDirection);
TryUpdateBestMatch(targetAxis, rawAxisY, LocalAxisDirection.PositiveY, ref bestAlignment, ref secondBestAlignment, ref axisDirection);
TryUpdateBestMatch(targetAxis, -rawAxisY, LocalAxisDirection.NegativeY, ref bestAlignment, ref secondBestAlignment, ref axisDirection);
TryUpdateBestMatch(targetAxis, rawAxisZ, LocalAxisDirection.PositiveZ, ref bestAlignment, ref secondBestAlignment, ref axisDirection);
TryUpdateBestMatch(targetAxis, -rawAxisZ, LocalAxisDirection.NegativeZ, ref bestAlignment, ref secondBestAlignment, ref axisDirection);
return bestAlignment >= minimumAlignment
&& bestAlignment - secondBestAlignment >= MinimumBestToSecondGap;
}
private static void TryUpdateBestMatch(
Vector3 targetAxis,
Vector3 candidateAxis,
LocalAxisDirection candidateDirection,
ref float bestAlignment,
ref float secondBestAlignment,
ref LocalAxisDirection bestDirection)
{
float alignment = Vector3.Dot(Vector3.Normalize(targetAxis), Vector3.Normalize(candidateAxis));
if (alignment > bestAlignment)
{
secondBestAlignment = bestAlignment;
bestAlignment = alignment;
bestDirection = candidateDirection;
}
else if (alignment > secondBestAlignment)
{
secondBestAlignment = alignment;
}
}
/// <summary>
/// 从 fragment 矩阵检测物体的默认 Up 轴Y 或 Z 更贴近宿主 up
/// </summary>
public static bool TryDetectDefaultUpAxis(
IReadOnlyCollection<double[]> fragmentMatrices,
string hostUpAxis,