refactor: 清理方法5 fragment 矩阵分析依赖链(保留坐标系检测其余方法)

删除(fragment 矩阵分析诊断链):
- SystemManagementViewModel: 方法5 文本块、AnalyzeFragmentOrientations、
  AppendFragmentAnalysis、FragmentOrientationAnalysis 类
- FragmentRepresentativePoseHelper.cs(TryGetRepresentativeFrame 无调用者)
- RealObjectReferencePoseResolver.cs(TryDetectDefaultUpAxis 只被方法5 用)
- FragmentRepresentativePoseHelperTests.cs
- FragmentDefaultUpContext: 删 GetCurrentHostUpAxis / GetCurrentDocumentFragmentDefaultUpAxis /
  IsSupportedAxis(均无调用者)

保留:
- 坐标系检测 方法1/2/3/4/6(文档级检测)
- 真实物体变换诊断(TransformDiagnosticInfo 保留,轴可视化 fallback
  到几何后代/选中节点 Transform,不再用 fragment)
- FragmentDefaultUpContext 改名 DocumentKeyContext(仅文档 key 缓存,
  用于模型根旋转提示;名字不再含 fragment)

验证:单元测试 185/185、集成测试 25/25
This commit is contained in:
tian 2026-08-06 00:09:08 +08:00
parent e0b01247ab
commit 29da68f6d3
8 changed files with 15 additions and 670 deletions

View File

@ -96,7 +96,6 @@
<Compile Include="UnitTests\CoordinateSystem\CanonicalTrackedPositionResolverTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\GroundPathObjectLiftOffsetTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\GroundPassageSpaceOffsetTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\FragmentRepresentativePoseHelperTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\HoistingCoordinateHelperTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\HoistingRealObjectPoseHelperTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\ModelAxisConventionTests.cs" />
@ -112,7 +111,7 @@
<Compile Include="UnitTests\CoordinateSystem\RailAssemblyWorkflowContextTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\ViewpointHelperTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\PathPointVisualizationTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\FragmentDefaultUpContextTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\DocumentKeyContextTests.cs" />
<Compile Include="UnitTests\CoordinateSystem\VirtualGroundPoseCharacterizationTests.cs" />
<Compile Include="UnitTests\Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@ -362,9 +362,7 @@
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailOffsetResolver.cs" />
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailPoseBuilder.cs" />
<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\RealObjectReferencePoseResolver.cs" />
<Compile Include="src\Utils\CoordinateSystem\DocumentKeyContext.cs" />
<Compile Include="src\Utils\CoordinateSystem\HoistingRealObjectPoseHelper.cs" />
<Compile Include="src\Utils\CoordinateSystem\PathTargetFrame.cs" />
<Compile Include="src\Utils\CoordinateSystem\PathTargetFrameResolver.cs" />

View File

@ -4,12 +4,12 @@ using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.UnitTests.CoordinateSystem
{
[TestClass]
public class FragmentDefaultUpContextTests
public class DocumentKeyContextTests
{
[TestInitialize]
public void SetUp()
{
FragmentDefaultUpContext.ResetForTests();
DocumentKeyContext.ResetForTests();
}
[TestMethod]
@ -17,12 +17,12 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
{
const int runtimeDocumentId = 42;
string initialKey = FragmentDefaultUpContext.GetOrCreateDocumentKey(
string initialKey = DocumentKeyContext.GetOrCreateDocumentKey(
runtimeDocumentId,
@"C:\models\floor2.nwd",
"Floor2");
string transientKey = FragmentDefaultUpContext.GetOrCreateDocumentKey(
string transientKey = DocumentKeyContext.GetOrCreateDocumentKey(
runtimeDocumentId,
string.Empty,
"无标题");
@ -36,7 +36,7 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
{
const int runtimeDocumentId = 7;
string key = FragmentDefaultUpContext.GetOrCreateDocumentKey(
string key = DocumentKeyContext.GetOrCreateDocumentKey(
runtimeDocumentId,
string.Empty,
"无标题");

View File

@ -1,70 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
using System.Numerics;
namespace NavisworksTransport.UnitTests.CoordinateSystem
{
[TestClass]
public class FragmentRepresentativePoseHelperTests
{
[TestMethod]
public void ShouldPreserveAxes_ForObservedYUpRealObjectFragmentBasis()
{
Vector3 axisX = new Vector3(1.0f, 0.0f, 0.0f);
Vector3 axisY = new Vector3(0.0f, 0.0f, 1.0f);
Vector3 axisZ = new Vector3(0.0f, -1.0f, 0.0f);
double[] fragmentMatrix =
{
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
};
bool ok = FragmentRepresentativePoseHelper.TryExtractRotationFromFragmentMatrix(
fragmentMatrix,
out Quaternion rotation);
Assert.IsTrue(ok);
AssertVector(Vector3.Transform(Vector3.UnitX, rotation), axisX, 1e-4);
AssertVector(Vector3.Transform(Vector3.UnitY, rotation), axisY, 1e-4);
AssertVector(Vector3.Transform(Vector3.UnitZ, rotation), axisZ, 1e-4);
}
[TestMethod]
public void RepresentativeFrame_ShouldExposeSameAxes_AsRepresentativeRotation()
{
Vector3 axisX = new Vector3(1.0f, 0.0f, 0.0f);
Vector3 axisY = new Vector3(0.0f, 0.0f, 1.0f);
Vector3 axisZ = new Vector3(0.0f, -1.0f, 0.0f);
double[] fragmentMatrix =
{
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
};
bool ok = FragmentRepresentativePoseHelper.TryGetRepresentativeFrame(
new[] { fragmentMatrix },
out FragmentRepresentativePoseHelper.RepresentativeFrame frame);
Assert.IsTrue(ok);
AssertVector(frame.AxisX, axisX, 1e-4);
AssertVector(frame.AxisY, axisY, 1e-4);
AssertVector(frame.AxisZ, axisZ, 1e-4);
AssertVector(Vector3.Transform(Vector3.UnitX, frame.Rotation), axisX, 1e-4);
AssertVector(Vector3.Transform(Vector3.UnitY, frame.Rotation), axisY, 1e-4);
AssertVector(Vector3.Transform(Vector3.UnitZ, frame.Rotation), axisZ, 1e-4);
}
private static void AssertVector(Vector3 actual, Vector3 expected, double 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

@ -454,7 +454,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private string GetCurrentDocumentKey()
{
return FragmentDefaultUpContext.GetCurrentDocumentKey();
return DocumentKeyContext.GetCurrentDocumentKey();
}
private void ShowRootModelRotationHintIfNeeded()
@ -1463,11 +1463,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
TransformDiagnosticInfo geometryInfo = geometryDescendants.Count > 0
? CreateTransformDiagnosticInfo(geometryDescendants[0], "首个几何后代")
: null;
FragmentOrientationAnalysis fragmentAnalysis = AnalyzeFragmentOrientations(item);
Quaternion visualizationQuaternion = fragmentAnalysis != null && fragmentAnalysis.HasRepresentativeOrientation
? fragmentAnalysis.RepresentativeRotation
: geometryInfo?.RotationQuaternion ?? selectedInfo.RotationQuaternion;
Quaternion visualizationQuaternion = geometryInfo?.RotationQuaternion ?? selectedInfo.RotationQuaternion;
double axisLength = GetTransformAxisVisualizationLength(bounds);
RenderCapturedRealObjectTransformAxes(boundsCenter, visualizationQuaternion, axisLength);
@ -1499,12 +1495,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
info.AppendLine();
}
AppendFragmentAnalysis(info, fragmentAnalysis);
info.AppendLine();
info.AppendLine("【可视化说明】");
info.AppendLine($"三轴原点使用包围盒中心: ({boundsCenter.X:F3}, {boundsCenter.Y:F3}, {boundsCenter.Z:F3})");
info.AppendLine($"轴长度 = {axisLength:F3}");
info.AppendLine($"三轴方向来源 = {(fragmentAnalysis != null && fragmentAnalysis.HasRepresentativeOrientation ? "fragment代表姿态" : geometryInfo != null ? "Transform" : "Transform")}");
info.AppendLine($"三轴方向来源 = {(geometryInfo != null ? "Transform" : "Transform")}");
info.AppendLine("红色=X轴绿色=Y轴蓝色=Z轴");
string message = info.ToString();
@ -1667,196 +1662,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
private static FragmentOrientationAnalysis AnalyzeFragmentOrientations(ModelItem item)
{
if (item == null)
{
return null;
}
Autodesk.Navisworks.Api.ModelItemCollection collection = new Autodesk.Navisworks.Api.ModelItemCollection { item };
var comSelection = ComApiBridge.ToInwOpSelection(collection);
List<FragmentInfo> fragments = null;
try
{
fragments = GeometryHelper.GetAllFragments(comSelection);
var validMatrices = fragments
.Where(f => f.TransformMatrix != null && f.TransformMatrix.Length == 16)
.Select(f => f.TransformMatrix)
.ToList();
var analysis = new FragmentOrientationAnalysis
{
FragmentCount = fragments.Count,
ValidTransformCount = validMatrices.Count,
ValidMatrices = validMatrices
};
if (validMatrices.Count == 0)
{
return analysis;
}
Vector3 referenceX = NormalizeOrUnitX(GetMatrixAxis(validMatrices[0], 0));
Vector3 referenceY = NormalizeOrUnitY(GetMatrixAxis(validMatrices[0], 1));
Vector3 referenceZ = NormalizeOrUnitZ(GetMatrixAxis(validMatrices[0], 2));
Vector3 sumX = referenceX;
Vector3 sumY = referenceY;
Vector3 sumZ = referenceZ;
double xDotSum = 1.0;
double yDotSum = 1.0;
double zDotSum = 1.0;
for (int i = 1; i < validMatrices.Count; i++)
{
Vector3 axisX = NormalizeWithReference(GetMatrixAxis(validMatrices[i], 0), referenceX);
Vector3 axisY = NormalizeWithReference(GetMatrixAxis(validMatrices[i], 1), referenceY);
Vector3 axisZ = NormalizeWithReference(GetMatrixAxis(validMatrices[i], 2), referenceZ);
xDotSum += Math.Abs(Vector3.Dot(axisX, referenceX));
yDotSum += Math.Abs(Vector3.Dot(axisY, referenceY));
zDotSum += Math.Abs(Vector3.Dot(axisZ, referenceZ));
sumX += axisX;
sumY += axisY;
sumZ += axisZ;
}
Vector3 representativeX = NormalizeOrUnitX(sumX);
Vector3 representativeY = sumY - Vector3.Dot(sumY, representativeX) * representativeX;
representativeY = NormalizeOrUnitY(representativeY);
Vector3 representativeZ = Vector3.Normalize(Vector3.Cross(representativeX, representativeY));
if (Vector3.Dot(representativeZ, NormalizeOrUnitZ(sumZ)) < 0)
{
representativeZ = -representativeZ;
}
representativeY = Vector3.Normalize(Vector3.Cross(representativeZ, representativeX));
Matrix4x4 linear = new Matrix4x4(
representativeX.X, representativeY.X, representativeZ.X, 0f,
representativeX.Y, representativeY.Y, representativeZ.Y, 0f,
representativeX.Z, representativeY.Z, representativeZ.Z, 0f,
0f, 0f, 0f, 1f);
analysis.HasRepresentativeOrientation = true;
analysis.RepresentativeRotation = Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(linear));
analysis.RepresentativeXAxis = representativeX;
analysis.RepresentativeYAxis = representativeY;
analysis.RepresentativeZAxis = representativeZ;
analysis.AverageXAxisConsistency = xDotSum / validMatrices.Count;
analysis.AverageYAxisConsistency = yDotSum / validMatrices.Count;
analysis.AverageZAxisConsistency = zDotSum / validMatrices.Count;
analysis.SampleTranslation = GetMatrixTranslation(validMatrices[0]);
return analysis;
}
finally
{
if (fragments != null)
{
foreach (var fragment in fragments)
{
if (fragment?.Fragment != null)
{
try
{
Marshal.ReleaseComObject(fragment.Fragment);
}
catch
{
}
}
}
}
if (comSelection != null)
{
try
{
Marshal.ReleaseComObject(comSelection);
}
catch
{
}
}
}
}
private static void AppendFragmentAnalysis(StringBuilder info, FragmentOrientationAnalysis analysis)
{
info.AppendLine("【Fragment 姿态分析】");
if (analysis == null)
{
info.AppendLine("未能执行 fragment 分析");
info.AppendLine();
return;
}
info.AppendLine($"Fragment 总数 = {analysis.FragmentCount}");
info.AppendLine($"有效变换矩阵数 = {analysis.ValidTransformCount}");
if (!analysis.HasRepresentativeOrientation)
{
info.AppendLine("未得到可用的 fragment 代表姿态");
info.AppendLine();
return;
}
info.AppendLine($"示例平移 = ({analysis.SampleTranslation.X:F3}, {analysis.SampleTranslation.Y:F3}, {analysis.SampleTranslation.Z:F3})");
info.AppendLine($"代表X轴 = ({analysis.RepresentativeXAxis.X:F4}, {analysis.RepresentativeXAxis.Y:F4}, {analysis.RepresentativeXAxis.Z:F4})");
info.AppendLine($"代表Y轴 = ({analysis.RepresentativeYAxis.X:F4}, {analysis.RepresentativeYAxis.Y:F4}, {analysis.RepresentativeYAxis.Z:F4})");
info.AppendLine($"代表Z轴 = ({analysis.RepresentativeZAxis.X:F4}, {analysis.RepresentativeZAxis.Y:F4}, {analysis.RepresentativeZAxis.Z:F4})");
info.AppendLine($"X轴一致性 = {analysis.AverageXAxisConsistency:F4}");
info.AppendLine($"Y轴一致性 = {analysis.AverageYAxisConsistency:F4}");
info.AppendLine($"Z轴一致性 = {analysis.AverageZAxisConsistency:F4}");
info.AppendLine();
}
private static Vector3 GetMatrixAxis(double[] matrix, int axisIndex)
{
switch (axisIndex)
{
case 0:
return new Vector3((float)matrix[0], (float)matrix[1], (float)matrix[2]);
case 1:
return new Vector3((float)matrix[4], (float)matrix[5], (float)matrix[6]);
case 2:
return new Vector3((float)matrix[8], (float)matrix[9], (float)matrix[10]);
default:
throw new ArgumentOutOfRangeException(nameof(axisIndex));
}
}
private static Vector3 GetMatrixTranslation(double[] matrix)
{
return new Vector3((float)matrix[12], (float)matrix[13], (float)matrix[14]);
}
private static Vector3 NormalizeWithReference(Vector3 axis, Vector3 reference)
{
Vector3 normalized = NormalizeOrFallback(axis, reference);
return Vector3.Dot(normalized, reference) < 0 ? -normalized : normalized;
}
private static Vector3 NormalizeOrUnitX(Vector3 value)
{
return NormalizeOrFallback(value, Vector3.UnitX);
}
private static Vector3 NormalizeOrUnitY(Vector3 value)
{
return NormalizeOrFallback(value, Vector3.UnitY);
}
private static Vector3 NormalizeOrUnitZ(Vector3 value)
{
return NormalizeOrFallback(value, Vector3.UnitZ);
}
private static Vector3 NormalizeOrFallback(Vector3 value, Vector3 fallback)
{
return value.LengthSquared() < 1e-8f ? fallback : Vector3.Normalize(value);
}
private sealed class TransformDiagnosticInfo
{
public string Label { get; set; }
@ -1873,22 +1678,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public Vector3 HostZAxis { get; set; }
}
private sealed class FragmentOrientationAnalysis
{
public int FragmentCount { get; set; }
public int ValidTransformCount { get; set; }
public List<double[]> ValidMatrices { get; set; }
public bool HasRepresentativeOrientation { get; set; }
public Quaternion RepresentativeRotation { get; set; }
public Vector3 RepresentativeXAxis { get; set; }
public Vector3 RepresentativeYAxis { get; set; }
public Vector3 RepresentativeZAxis { get; set; }
public double AverageXAxisConsistency { get; set; }
public double AverageYAxisConsistency { get; set; }
public double AverageZAxisConsistency { get; set; }
public Vector3 SampleTranslation { get; set; }
}
/// <summary>
/// 快速取面测试 — 激活 FaceInferToolPlugin用户在模型上点击后显示面法向量
/// </summary>
@ -2284,44 +2073,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
sb.AppendLine();
// 方法5: Fragment 矩阵分析(选一个水平物体后更精确)
sb.AppendLine($"方法5 - Fragment 矩阵分析 (选择物体后启用):");
var selection = doc.CurrentSelection.SelectedItems;
if (selection.Count > 0)
{
ModelItem selectedItem = selection[0];
FragmentOrientationAnalysis fragAnalysis = AnalyzeFragmentOrientations(selectedItem);
if (fragAnalysis != null && fragAnalysis.HasRepresentativeOrientation)
{
string hostUpAxis = FragmentDefaultUpContext.GetCurrentHostUpAxis(doc);
if (RealObjectReferencePoseResolver.TryDetectDefaultUpAxis(
fragAnalysis.ValidMatrices,
hostUpAxis,
out string fragDetectedAxis,
out float yAlignment,
out float zAlignment))
{
sb.AppendLine($" 对象: {selectedItem.DisplayName}");
sb.AppendLine($" Fragment Y 对齐度: {yAlignment:F4}");
sb.AppendLine($" Fragment Z 对齐度: {zAlignment:F4}");
sb.AppendLine($" 判定: Fragment 默认 Up = {fragDetectedAxis} ({(zAlignment > yAlignment ? "Z更贴近" : "Y更贴近")}向上轴)");
}
else
{
sb.AppendLine($" 未能从 fragment 姿态中检测默认Up。");
}
}
else
{
sb.AppendLine($" 选中物体 ({selectedItem.DisplayName}) 无有效 fragment 姿态。");
}
}
else
{
sb.AppendLine($" (未选择对象,跳过此方法。请先选中一个水平放置的物体)");
}
sb.AppendLine();
// 最终建议
sb.AppendLine($"【最终建议】");
if (detectedCoordinateSystem == "YUp")

View File

@ -6,10 +6,12 @@ using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 当前文档级的 fragment 默认 Up 运行时状态。
/// 自动从 CoordinateSystemManager 派生,不暴露独立设置。
/// 当前文档运行时 key 缓存。
/// 用于"模型根旋转提示"等按文档去重的交互(同一文档只提示一次)。
/// 原名 FragmentDefaultUpContext曾承担 fragment 默认 Up 运行时状态),
/// 0.18.2 去掉 fragment 参考姿态依赖后,仅保留文档 key 功能。
/// </summary>
internal static class FragmentDefaultUpContext
internal static class DocumentKeyContext
{
private static readonly ConcurrentDictionary<int, string> RuntimeDocumentKeys =
new ConcurrentDictionary<int, string>();
@ -57,43 +59,6 @@ namespace NavisworksTransport.Utils.CoordinateSystem
return resolvedKey;
}
public static string GetCurrentHostUpAxis(Document doc = null)
{
doc = doc ?? Autodesk.Navisworks.Api.Application.ActiveDocument;
if (doc != null && !doc.IsClear)
{
UnitVector3D worldUp = doc.CurrentViewpoint.Value.WorldUpVector;
if (!worldUp.IsZero)
{
return Math.Abs(worldUp.Y) >= Math.Abs(worldUp.Z) ? "Y" : "Z";
}
Vector3D documentUp = doc.UpVector;
if (!documentUp.IsZero)
{
return Math.Abs(documentUp.Y) >= Math.Abs(documentUp.Z) ? "Y" : "Z";
}
}
return "Z";
}
/// <summary>
/// 获取当前文档的 Fragment Default Up 轴。
/// 自动从 CoordinateSystemManager 的 ResolvedType 派生。
/// YUp → "Y", ZUp → "Z"。
/// </summary>
public static string GetCurrentDocumentFragmentDefaultUpAxis(Document doc = null)
{
return CoordinateSystemManager.Instance.ResolvedType == CoordinateSystemType.YUp ? "Y" : "Z";
}
public static bool IsSupportedAxis(string axisName)
{
return string.Equals(axisName, "Y", StringComparison.OrdinalIgnoreCase) ||
string.Equals(axisName, "Z", StringComparison.OrdinalIgnoreCase);
}
internal static void ResetForTests()
{
RuntimeDocumentKeys.Clear();

View File

@ -1,246 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// Fragment 代表帧辅助工具(诊断用途)。
///
/// 说明:真实物体姿态链已不依赖 fragment0.18.2 起使用默认轴约定,
/// forward=+X、up=宿主 up。本工具仅保留系统诊断面板
/// SystemManagementViewModel 方法5Fragment 矩阵分析)所需的
/// TryGetRepresentativeFrame / TryExtractRotationFromFragmentMatrix。
/// </summary>
public static class FragmentRepresentativePoseHelper
{
private const float AxisEpsilon = 1e-6f;
public readonly struct RepresentativeFrame
{
public Vector3 AxisX { get; }
public Vector3 AxisY { get; }
public Vector3 AxisZ { get; }
public Quaternion Rotation { get; }
public RepresentativeFrame(Vector3 axisX, Vector3 axisY, Vector3 axisZ, Quaternion rotation)
{
AxisX = axisX;
AxisY = axisY;
AxisZ = axisZ;
Rotation = rotation;
}
}
/// <summary>
/// 从 fragment 矩阵集合中提取代表帧。
/// </summary>
public static bool TryGetRepresentativeFrame(
IEnumerable<double[]> fragmentMatrices,
out RepresentativeFrame representativeFrame)
{
representativeFrame = default;
if (fragmentMatrices == null)
{
return false;
}
var rotations = new List<Quaternion>();
foreach (double[] fragmentMatrix in fragmentMatrices)
{
if (TryExtractRotationFromFragmentMatrix(fragmentMatrix, out Quaternion fragmentRotation))
{
rotations.Add(fragmentRotation);
}
}
if (!TryGetRepresentativeRotation(rotations, out Quaternion representativeRotation))
{
return false;
}
Vector3 axisX = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, representativeRotation));
Vector3 axisY = Vector3.Normalize(Vector3.Transform(Vector3.UnitY, representativeRotation));
Vector3 axisZ = Vector3.Normalize(Vector3.Transform(Vector3.UnitZ, representativeRotation));
representativeFrame = new RepresentativeFrame(axisX, axisY, axisZ, representativeRotation);
return true;
}
/// <summary>
/// 从 fragment 旋转集合中提取代表旋转(同半球加权平均)。
/// </summary>
private static bool TryGetRepresentativeRotation(
IEnumerable<Quaternion> fragmentRotations,
out Quaternion representativeRotation)
{
representativeRotation = Quaternion.Identity;
if (fragmentRotations == null)
{
return false;
}
var rotations = fragmentRotations
.Where(rotation => TryNormalize(rotation, out _))
.Select(rotation => Quaternion.Normalize(rotation))
.ToList();
if (rotations.Count == 0)
{
return false;
}
Quaternion seed = rotations[0];
Vector4 sum = Vector4.Zero;
foreach (Quaternion rotation in rotations)
{
Quaternion aligned = Quaternion.Dot(seed, rotation) < 0.0f
? new Quaternion(-rotation.X, -rotation.Y, -rotation.Z, -rotation.W)
: rotation;
sum += new Vector4(aligned.X, aligned.Y, aligned.Z, aligned.W);
}
if (sum.LengthSquared() < AxisEpsilon)
{
return false;
}
representativeRotation = Quaternion.Normalize(new Quaternion(sum.X, sum.Y, sum.Z, sum.W));
return true;
}
/// <summary>
/// 从 fragment 的 4x4 world matrix 中提取旋转。
/// 仅使用旋转部分,忽略平移。
/// </summary>
public static bool TryExtractRotationFromFragmentMatrix(double[] fragmentMatrix, out Quaternion rotation)
{
rotation = Quaternion.Identity;
if (fragmentMatrix == null || fragmentMatrix.Length != 16)
{
return false;
}
Vector3 axisX = new Vector3(
(float)fragmentMatrix[0],
(float)fragmentMatrix[1],
(float)fragmentMatrix[2]);
Vector3 axisY = new Vector3(
(float)fragmentMatrix[4],
(float)fragmentMatrix[5],
(float)fragmentMatrix[6]);
Vector3 axisZ = new Vector3(
(float)fragmentMatrix[8],
(float)fragmentMatrix[9],
(float)fragmentMatrix[10]);
if (!TryNormalize(axisX, out axisX) ||
!TryNormalize(axisY, out axisY) ||
!TryNormalize(axisZ, out axisZ))
{
return false;
}
// 先将三个轴正交化,避免 fragment 矩阵里存在轻微数值误差或尺度干扰。
Vector3 orthogonalY = axisY - Vector3.Dot(axisY, axisX) * axisX;
if (!TryNormalize(orthogonalY, out orthogonalY))
{
orthogonalY = Vector3.Normalize(Vector3.Cross(axisZ, axisX));
}
Vector3 orthogonalZ = Vector3.Cross(axisX, orthogonalY);
if (!TryNormalize(orthogonalZ, out orthogonalZ))
{
return false;
}
orthogonalY = Vector3.Normalize(Vector3.Cross(orthogonalZ, axisX));
rotation = Quaternion.Normalize(CreateQuaternionFromBasis(axisX, orthogonalY, orthogonalZ));
return true;
}
private static Quaternion CreateQuaternionFromBasis(Vector3 axisX, Vector3 axisY, Vector3 axisZ)
{
float m00 = axisX.X;
float m01 = axisY.X;
float m02 = axisZ.X;
float m10 = axisX.Y;
float m11 = axisY.Y;
float m12 = axisZ.Y;
float m20 = axisX.Z;
float m21 = axisY.Z;
float m22 = axisZ.Z;
float trace = m00 + m11 + m22;
float qx;
float qy;
float qz;
float qw;
if (trace > 0.0f)
{
float s = (float)Math.Sqrt(trace + 1.0f) * 2.0f;
qw = 0.25f * s;
qx = (m21 - m12) / s;
qy = (m02 - m20) / s;
qz = (m10 - m01) / s;
}
else if (m00 > m11 && m00 > m22)
{
float s = (float)Math.Sqrt(1.0f + m00 - m11 - m22) * 2.0f;
qw = (m21 - m12) / s;
qx = 0.25f * s;
qy = (m01 + m10) / s;
qz = (m02 + m20) / s;
}
else if (m11 > m22)
{
float s = (float)Math.Sqrt(1.0f + m11 - m00 - m22) * 2.0f;
qw = (m02 - m20) / s;
qx = (m01 + m10) / s;
qy = 0.25f * s;
qz = (m12 + m21) / s;
}
else
{
float s = (float)Math.Sqrt(1.0f + m22 - m00 - m11) * 2.0f;
qw = (m10 - m01) / s;
qx = (m02 + m20) / s;
qy = (m12 + m21) / s;
qz = 0.25f * s;
}
return new Quaternion(qx, qy, qz, qw);
}
private static bool TryNormalize(Quaternion value, out Quaternion normalized)
{
float lengthSquared = value.X * value.X + value.Y * value.Y + value.Z * value.Z + value.W * value.W;
if (lengthSquared < AxisEpsilon)
{
normalized = Quaternion.Identity;
return false;
}
normalized = Quaternion.Normalize(value);
return true;
}
private static bool TryNormalize(Vector3 value, out Vector3 normalized)
{
if (value.LengthSquared() < AxisEpsilon)
{
normalized = Vector3.Zero;
return false;
}
normalized = Vector3.Normalize(value);
return true;
}
}
}

View File

@ -1,52 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
namespace NavisworksTransport.Utils.CoordinateSystem
{
/// <summary>
/// 真实物体 fragment 姿态解析入口(仅保留诊断用途)。
///
/// 说明:真实物体姿态链已不依赖 fragment0.18.2 起使用默认轴约定,
/// forward=+X、up=宿主 up。原 TryResolveFromModelItem /
/// TryResolveFromFragmentMatricesfragment 代表姿态解析链)已删除。
/// 本类仅保留系统诊断面板SystemManagementViewModel 方法5使用的
/// TryDetectDefaultUpAxis。
/// </summary>
public static class RealObjectReferencePoseResolver
{
/// <summary>
/// 从 fragment 矩阵检测物体的默认 Up 轴Y 或 Z 更贴近宿主 up
/// </summary>
public static bool TryDetectDefaultUpAxis(
IReadOnlyCollection<double[]> fragmentMatrices,
string hostUpAxis,
out string detectedAxis,
out float yAlignment,
out float zAlignment)
{
detectedAxis = "Y";
yAlignment = 0.0f;
zAlignment = 0.0f;
if (fragmentMatrices == null || fragmentMatrices.Count == 0)
{
return false;
}
if (!FragmentRepresentativePoseHelper.TryGetRepresentativeFrame(fragmentMatrices, out var rawFrame))
{
return false;
}
Vector3 hostUpVector = string.Equals(hostUpAxis, "Y", StringComparison.OrdinalIgnoreCase)
? Vector3.UnitY
: Vector3.UnitZ;
yAlignment = Math.Abs(Vector3.Dot(Vector3.Normalize(rawFrame.AxisY), hostUpVector));
zAlignment = Math.Abs(Vector3.Dot(Vector3.Normalize(rawFrame.AxisZ), hostUpVector));
detectedAxis = zAlignment > yAlignment ? "Z" : "Y";
return true;
}
}
}