Unify real-object rail pose interpretation
This commit is contained in:
parent
6c74ea1319
commit
138eb43a67
27
AGENTS.md
27
AGENTS.md
@ -172,6 +172,20 @@ var rotation = new Rotation3D(qw, qx, qy, qz); // 错误
|
||||
- 可以视为直接生活在宿主坐标系里
|
||||
- 角度调整对话框里的 `X/Y/Z`,对真实物体应按**宿主坐标系**消费
|
||||
|
||||
#### 真实物体参考姿态
|
||||
|
||||
- 真实物体不要再直接依赖 `ModelItem.Transform.Factor().Rotation`
|
||||
- 对很多 Revit 导入件,这个值是单位旋转,不代表真实显示姿态
|
||||
- 真实物体参考姿态应优先来自 fragment 代表姿态
|
||||
- `Fragment默认Up` 的用途是:
|
||||
- 把 fragment 参考框架解释成当前宿主坐标语义下的真实姿态
|
||||
- 不是仅仅挑一个“竖直候选轴”
|
||||
- 在真实物体链路里,必须先完成“fragment 姿态解释”,再谈:
|
||||
- 前进方向
|
||||
- 路径对齐
|
||||
- 角度调整
|
||||
- `_trackedRotation` 对真实物体必须优先跟踪“解释后的真实参考姿态”,不能回退成 `Transform` 的单位旋转
|
||||
|
||||
#### 虚拟物体
|
||||
|
||||
- 有明确资产坐标系
|
||||
@ -194,6 +208,15 @@ var rotation = new Rotation3D(qw, qx, qy, qz); // 错误
|
||||
- 不能像平面路径那样在宿主空间随意补旋转
|
||||
- 必须并入 `canonical -> rail pose` 链
|
||||
- `Rail 0°` 基线必须稳定,不能被角度修正逻辑污染
|
||||
- `Rail` 真实物体不应再退回默认 `PositiveX / PositiveY`
|
||||
- 三类路径都必须先复用同一个“对象姿态解释层”:
|
||||
- `Ground / Hoisting`
|
||||
- `forward = 路径方向`
|
||||
- `up = 宿主 up`
|
||||
- `Rail`
|
||||
- `forward = rail 切向`
|
||||
- `up = rail 法向 / preferred normal`
|
||||
- 统一的是“对象参考姿态来源与解释方式”,不是把三类路径都强行改成同一个 `up`
|
||||
|
||||
### 4.4 通行空间与物体姿态的关系
|
||||
|
||||
@ -209,6 +232,10 @@ var rotation = new Rotation3D(qw, qx, qy, qz); // 错误
|
||||
|
||||
- 是否一条链用了“原始高度”
|
||||
- 另一条链用了“旋转后法线尺寸”
|
||||
- `Rail` 真实物体尤其要检查:
|
||||
- 起点/逐帧中心偏移是否仍在用原始 `objectHeight`
|
||||
- 通行空间是否仍在用旧 `RailAssetConvention`
|
||||
- 通行空间、路径偏移、最终姿态是否共享同一套“最终姿态尺寸语义”
|
||||
|
||||
### 4.5 终端安装仿真
|
||||
|
||||
|
||||
@ -64,6 +64,8 @@
|
||||
<Compile Include="UnitTests\CoordinateSystem\ModelAxisConventionTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\ProjectReferenceFrameTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\RealObjectPlanarPoseSolverTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\RealObjectRailAxisConventionResolverTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\RealObjectRailExtentResolverTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\AssemblyEndFaceAnalyzerTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\AssemblyInstallationReferenceBuilderTests.cs" />
|
||||
<Compile Include="UnitTests\Properties\AssemblyInfo.cs" />
|
||||
|
||||
@ -343,6 +343,8 @@
|
||||
<Compile Include="src\Utils\CoordinateSystem\FragmentDefaultUpContext.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\FragmentRepresentativePoseHelper.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\RealObjectPlanarPoseSolver.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\RealObjectRailAxisConventionResolver.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\RealObjectRailExtentResolver.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\ObjectSpaceOrientationHelper.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\RotatedObjectExtentHelper.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\HostCoordinateAdapter.cs" />
|
||||
|
||||
@ -305,6 +305,63 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
AssertAxis(linear, 3, Vector3.Transform(baselineZ, hostCorrection));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void YUp_ComposeHostQuaternion_ForRailBaseline_ShouldRotateAxesAroundHostXAxis()
|
||||
{
|
||||
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
|
||||
Vector3 baselineX = new Vector3(-0.9900f, 0.1403f, -0.0163f);
|
||||
Vector3 baselineY = new Vector3(0.1403f, 0.9901f, 0.0000f);
|
||||
Vector3 baselineZ = new Vector3(0.0161f, -0.0023f, -0.9999f);
|
||||
Quaternion baseline = CreateQuaternionFromAxes(baselineX, baselineY, baselineZ);
|
||||
|
||||
var correction = new LocalEulerRotationCorrection(90.0, 0.0, 0.0);
|
||||
Quaternion composed = adapter.ComposeHostQuaternion(baseline, correction);
|
||||
Quaternion hostCorrection = adapter.CreateHostRotationCorrection(correction);
|
||||
Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(composed);
|
||||
|
||||
AssertAxis(linear, 1, Vector3.Transform(baselineX, hostCorrection));
|
||||
AssertAxis(linear, 2, Vector3.Transform(baselineY, hostCorrection));
|
||||
AssertAxis(linear, 3, Vector3.Transform(baselineZ, hostCorrection));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void YUp_ComposeHostQuaternion_ForRailBaseline_ShouldRotateAxesAroundHostYAxis()
|
||||
{
|
||||
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
|
||||
Vector3 baselineX = new Vector3(-0.9900f, 0.1403f, -0.0163f);
|
||||
Vector3 baselineY = new Vector3(0.1403f, 0.9901f, 0.0000f);
|
||||
Vector3 baselineZ = new Vector3(0.0161f, -0.0023f, -0.9999f);
|
||||
Quaternion baseline = CreateQuaternionFromAxes(baselineX, baselineY, baselineZ);
|
||||
|
||||
var correction = new LocalEulerRotationCorrection(0.0, 90.0, 0.0);
|
||||
Quaternion composed = adapter.ComposeHostQuaternion(baseline, correction);
|
||||
Quaternion hostCorrection = adapter.CreateHostRotationCorrection(correction);
|
||||
Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(composed);
|
||||
|
||||
AssertAxis(linear, 1, Vector3.Transform(baselineX, hostCorrection));
|
||||
AssertAxis(linear, 2, Vector3.Transform(baselineY, hostCorrection));
|
||||
AssertAxis(linear, 3, Vector3.Transform(baselineZ, hostCorrection));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void YUp_ComposeHostQuaternion_ForRailBaseline_ShouldRotateAxesAroundHostZAxis()
|
||||
{
|
||||
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
|
||||
Vector3 baselineX = new Vector3(-0.9900f, 0.1403f, -0.0163f);
|
||||
Vector3 baselineY = new Vector3(0.1403f, 0.9901f, 0.0000f);
|
||||
Vector3 baselineZ = new Vector3(0.0161f, -0.0023f, -0.9999f);
|
||||
Quaternion baseline = CreateQuaternionFromAxes(baselineX, baselineY, baselineZ);
|
||||
|
||||
var correction = new LocalEulerRotationCorrection(0.0, 0.0, 90.0);
|
||||
Quaternion composed = adapter.ComposeHostQuaternion(baseline, correction);
|
||||
Quaternion hostCorrection = adapter.CreateHostRotationCorrection(correction);
|
||||
Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(composed);
|
||||
|
||||
AssertAxis(linear, 1, Vector3.Transform(baselineX, hostCorrection));
|
||||
AssertAxis(linear, 2, Vector3.Transform(baselineY, hostCorrection));
|
||||
AssertAxis(linear, 3, Vector3.Transform(baselineZ, hostCorrection));
|
||||
}
|
||||
|
||||
private static void AssertPoint(Vector3 point, double x, double y, double z)
|
||||
{
|
||||
Assert.AreEqual(x, point.X, 1e-9);
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
{
|
||||
[TestClass]
|
||||
public class RealObjectRailAxisConventionResolverTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void YUp_InterpretedReferencePose_ShouldChoosePositiveXAndPositiveYForRail()
|
||||
{
|
||||
Vector3 referenceAxisX = new Vector3(-1.0f, 0.0f, 0.0f);
|
||||
Vector3 referenceAxisY = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
Vector3 referenceAxisZ = new Vector3(0.0f, 0.0f, -1.0f);
|
||||
Vector3 desiredForward = Vector3.Normalize(new Vector3(-0.8987f, 0.0f, 0.4386f));
|
||||
|
||||
bool ok = RealObjectRailAxisConventionResolver.TryResolve(
|
||||
referenceAxisX,
|
||||
referenceAxisY,
|
||||
referenceAxisZ,
|
||||
desiredForward,
|
||||
CoordinateSystemType.YUp,
|
||||
out ModelAxisConvention convention,
|
||||
out LocalAxisDirection selectedForwardAxis,
|
||||
out Vector3 selectedForwardWorldAxis);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveX, selectedForwardAxis);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveX, convention.ForwardAxis);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveY, convention.UpAxis);
|
||||
AssertVector(selectedForwardWorldAxis, -1.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZUp_InterpretedReferencePose_ShouldChoosePositiveXAndPositiveZForRail()
|
||||
{
|
||||
Vector3 referenceAxisX = new Vector3(-1.0f, 0.0f, 0.0f);
|
||||
Vector3 referenceAxisY = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
Vector3 referenceAxisZ = new Vector3(0.0f, 0.0f, 1.0f);
|
||||
Vector3 desiredForward = Vector3.Normalize(new Vector3(-0.9f, 0.3f, 0.0f));
|
||||
|
||||
bool ok = RealObjectRailAxisConventionResolver.TryResolve(
|
||||
referenceAxisX,
|
||||
referenceAxisY,
|
||||
referenceAxisZ,
|
||||
desiredForward,
|
||||
CoordinateSystemType.ZUp,
|
||||
out ModelAxisConvention convention,
|
||||
out LocalAxisDirection selectedForwardAxis,
|
||||
out _);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveX, selectedForwardAxis);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveX, convention.ForwardAxis);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveZ, convention.UpAxis);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void YUp_ShouldNotSelectYAxisFamilyAsForwardCandidate()
|
||||
{
|
||||
Vector3 referenceAxisX = new Vector3(1.0f, 0.0f, 0.0f);
|
||||
Vector3 referenceAxisY = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
Vector3 referenceAxisZ = new Vector3(0.0f, 0.0f, 1.0f);
|
||||
Vector3 desiredForward = Vector3.Normalize(new Vector3(0.0f, 1.0f, 0.01f));
|
||||
|
||||
bool ok = RealObjectRailAxisConventionResolver.TryResolve(
|
||||
referenceAxisX,
|
||||
referenceAxisY,
|
||||
referenceAxisZ,
|
||||
desiredForward,
|
||||
CoordinateSystemType.YUp,
|
||||
out _,
|
||||
out LocalAxisDirection selectedForwardAxis,
|
||||
out _);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreNotEqual(LocalAxisDirection.PositiveY, selectedForwardAxis);
|
||||
Assert.AreNotEqual(LocalAxisDirection.NegativeY, selectedForwardAxis);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
UnitTests/CoordinateSystem/RealObjectRailExtentResolverTests.cs
Normal file
142
UnitTests/CoordinateSystem/RealObjectRailExtentResolverTests.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
{
|
||||
[TestClass]
|
||||
public class RealObjectRailExtentResolverTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void YUp_ZeroCorrection_ShouldKeepResolvedRailUpExtent()
|
||||
{
|
||||
Vector3 referenceAxisX = new Vector3(-1.0f, 0.0f, 0.0f);
|
||||
Vector3 referenceAxisY = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
Vector3 referenceAxisZ = new Vector3(0.0f, 0.0f, -1.0f);
|
||||
Vector3 desiredForward = Vector3.Normalize(new Vector3(-0.8987f, 0.0f, 0.4386f));
|
||||
Quaternion baseline = Quaternion.Identity;
|
||||
|
||||
bool ok = RealObjectRailExtentResolver.TryResolveProjectedSemanticExtents(
|
||||
referenceAxisX,
|
||||
referenceAxisY,
|
||||
referenceAxisZ,
|
||||
desiredForward,
|
||||
CoordinateSystemType.YUp,
|
||||
forwardSize: 6.0,
|
||||
sideSize: 2.0,
|
||||
upSize: 4.0,
|
||||
baseline,
|
||||
baseline,
|
||||
out ModelAxisConvention convention,
|
||||
out var extents);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveX, convention.ForwardAxis);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveY, convention.UpAxis);
|
||||
Assert.AreEqual(6.0, extents.forwardExtent, 1e-6);
|
||||
Assert.AreEqual(2.0, extents.sideExtent, 1e-6);
|
||||
Assert.AreEqual(4.0, extents.upExtent, 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void YUp_HostY90_ShouldKeepResolvedRailUpExtentAndSwapForwardSide()
|
||||
{
|
||||
Vector3 referenceAxisX = new Vector3(-1.0f, 0.0f, 0.0f);
|
||||
Vector3 referenceAxisY = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
Vector3 referenceAxisZ = new Vector3(0.0f, 0.0f, -1.0f);
|
||||
Vector3 desiredForward = Vector3.Normalize(new Vector3(-0.8987f, 0.0f, 0.4386f));
|
||||
Quaternion baseline = Quaternion.Identity;
|
||||
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
|
||||
Quaternion final = adapter.ComposeHostQuaternion(
|
||||
baseline,
|
||||
new LocalEulerRotationCorrection(0.0, 90.0, 0.0));
|
||||
|
||||
bool ok = RealObjectRailExtentResolver.TryResolveProjectedSemanticExtents(
|
||||
referenceAxisX,
|
||||
referenceAxisY,
|
||||
referenceAxisZ,
|
||||
desiredForward,
|
||||
CoordinateSystemType.YUp,
|
||||
forwardSize: 6.0,
|
||||
sideSize: 2.0,
|
||||
upSize: 4.0,
|
||||
baseline,
|
||||
final,
|
||||
out _,
|
||||
out var extents);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreEqual(2.0, extents.forwardExtent, 1e-6);
|
||||
Assert.AreEqual(6.0, extents.sideExtent, 1e-6);
|
||||
Assert.AreEqual(4.0, extents.upExtent, 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void YUp_DualAxisCorrection_WithRailBaseline_ShouldProjectAgainstFinalRailPose()
|
||||
{
|
||||
Vector3 referenceAxisX = new Vector3(-1.0f, 0.0f, 0.0f);
|
||||
Vector3 referenceAxisY = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
Vector3 referenceAxisZ = new Vector3(0.0f, 0.0f, -1.0f);
|
||||
Vector3 desiredForward = Vector3.Normalize(new Vector3(-0.8987f, 0.0f, 0.4386f));
|
||||
|
||||
Quaternion baseline = Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(new Matrix4x4(
|
||||
0.9900f, -0.1403f, -0.0161f, 0f,
|
||||
0.1403f, 0.9901f, -0.0023f, 0f,
|
||||
0.0163f, 0.0000f, 0.9999f, 0f,
|
||||
0f, 0f, 0f, 1f)));
|
||||
|
||||
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
|
||||
Quaternion final = adapter.ComposeHostQuaternion(
|
||||
baseline,
|
||||
new LocalEulerRotationCorrection(0.0, 90.0, 90.0));
|
||||
|
||||
bool ok = RealObjectRailExtentResolver.TryResolveProjectedSemanticExtents(
|
||||
referenceAxisX,
|
||||
referenceAxisY,
|
||||
referenceAxisZ,
|
||||
desiredForward,
|
||||
CoordinateSystemType.YUp,
|
||||
forwardSize: 6.0,
|
||||
sideSize: 2.0,
|
||||
upSize: 4.0,
|
||||
baseline,
|
||||
final,
|
||||
out ModelAxisConvention convention,
|
||||
out var extents);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveX, convention.ForwardAxis);
|
||||
Assert.AreEqual(LocalAxisDirection.PositiveY, convention.UpAxis);
|
||||
|
||||
Vector3 localSize = convention.CreateScaleVector3(6.0, 2.0, 4.0);
|
||||
Vector3 rotatedLocalX = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, final));
|
||||
Vector3 rotatedLocalY = Vector3.Normalize(Vector3.Transform(Vector3.UnitY, final));
|
||||
Vector3 rotatedLocalZ = Vector3.Normalize(Vector3.Transform(Vector3.UnitZ, final));
|
||||
Vector3 targetForward = Vector3.Normalize(Vector3.Transform(convention.ForwardUnitVector, baseline));
|
||||
Vector3 targetUp = Vector3.Normalize(Vector3.Transform(convention.UpUnitVector, baseline));
|
||||
Vector3 targetSide = Vector3.Normalize(Vector3.Cross(targetForward, targetUp));
|
||||
|
||||
double expectedForward = ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetForward);
|
||||
double expectedSide = ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetSide);
|
||||
double expectedUp = ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetUp);
|
||||
|
||||
Assert.AreEqual(expectedForward, extents.forwardExtent, 1e-5);
|
||||
Assert.AreEqual(expectedSide, extents.sideExtent, 1e-5);
|
||||
Assert.AreEqual(expectedUp, extents.upExtent, 1e-5);
|
||||
Assert.AreNotEqual(4.0, extents.upExtent, 1e-3, "双轴旋转后,法向尺寸不应仍停留在单轴结果。");
|
||||
}
|
||||
|
||||
private static double ProjectExtent(
|
||||
Vector3 localSize,
|
||||
Vector3 rotatedLocalX,
|
||||
Vector3 rotatedLocalY,
|
||||
Vector3 rotatedLocalZ,
|
||||
Vector3 targetAxis)
|
||||
{
|
||||
return Math.Abs(Vector3.Dot(rotatedLocalX, targetAxis)) * localSize.X +
|
||||
Math.Abs(Vector3.Dot(rotatedLocalY, targetAxis)) * localSize.Y +
|
||||
Math.Abs(Vector3.Dot(rotatedLocalZ, targetAxis)) * localSize.Z;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
# 当前工程状态
|
||||
|
||||
更新时间:2026-03-23
|
||||
更新时间:2026-03-25
|
||||
|
||||
## 1. 当前稳定状态
|
||||
|
||||
@ -46,6 +46,24 @@
|
||||
|
||||
## 4. 当前必须记住的根因与规则
|
||||
|
||||
### 4.0 真实物体参考姿态与 Fragment默认Up
|
||||
|
||||
- 真实物体不能再默认使用 `ModelItem.Transform` 或包围盒正交框架当“原始姿态”。
|
||||
- 当前稳定做法是:
|
||||
- 先从 fragment 统计得到代表姿态
|
||||
- 再用当前文档级 `Fragment默认Up` 解释这组 fragment 参考轴
|
||||
- 最终得到当前宿主语义下的真实参考姿态
|
||||
- `Fragment默认Up` 的真正用途不是“找一个竖直候选轴”,而是:
|
||||
- 解释 fragment 参考姿态的原始 `Y/Z` 语义
|
||||
- 把 fragment 参考姿态转换成当前宿主坐标语义下可用的真实姿态
|
||||
- 当前文档如果 `Fragment默认Up` 设错,会直接导致:
|
||||
- 真实物体起点姿态错误
|
||||
- Ground / Hoisting / Rail 的参考姿态解释错误
|
||||
- 后续角度修正、通行空间、路径贴合全部建立在错误姿态上
|
||||
- 当前规则:
|
||||
- 先解释真实物体参考姿态,再做路径对齐
|
||||
- 不能跳过这一步,直接拿 fragment 世界轴去猜业务姿态
|
||||
|
||||
### 4.1 Rotation / 矩阵语义
|
||||
|
||||
- `Rotation3D(a, b, c, d)` 的参数顺序是四元数 `x, y, z, w`。
|
||||
@ -162,6 +180,37 @@
|
||||
2. 再验证修正后 `forward` 是否仍沿 rail 切向
|
||||
3. 最后再看 Navisworks 应用层是否正确按三步增量姿态落位
|
||||
|
||||
### 4.8 Rail 真实物体必须复用统一姿态解释层
|
||||
|
||||
- `Rail` 不应再单独退回默认 `PositiveX / PositiveY` 轴约定。
|
||||
- 当前稳定原则:
|
||||
- `Ground / Hoisting / Rail` 三类路径的真实物体,必须先共用同一个“真实参考姿态解释层”
|
||||
- 路径类型的差异只体现在目标路径框架:
|
||||
- `Ground / Hoisting`:`up = 宿主 up`
|
||||
- `Rail`:`up = rail 法向 / preferred normal`
|
||||
- 当前 `Rail` 真实物体稳定链路:
|
||||
1. fragment 代表姿态
|
||||
2. `Fragment默认Up` 解释后的真实参考姿态
|
||||
3. `Rail` 路径框架(切向 / 法向)
|
||||
4. 宿主 `X/Y/Z` 角度修正
|
||||
- 不要再让 `Rail` 真实物体单独维护一套与 `Ground / Hoisting` 不同的对象姿态来源。
|
||||
|
||||
### 4.9 Rail 真实物体的通行空间与路径偏移
|
||||
|
||||
- `Rail` 真实物体的通行空间、起点中心偏移、逐帧法向偏移,必须共享“最终姿态”的同一套尺寸语义。
|
||||
- 当前稳定规则:
|
||||
- 不能只根据“宿主轴角度修正”单独投影尺寸
|
||||
- 必须同时吃到:
|
||||
- `Rail` 基姿态
|
||||
- 宿主 `X/Y/Z` 角度修正后的最终姿态
|
||||
- 否则会出现典型错误:
|
||||
- 物体本体姿态正确,但起点偏离路径
|
||||
- 单轴旋转时通行空间看起来对,双轴旋转后只跟上一个角度
|
||||
- `Rail` 真实物体通行空间与物体本体方向不一致
|
||||
- 当前消费规则:
|
||||
- `AnimationControlViewModel.UpdatePassageSpaceVisualization()` 中,真实物体的 `Rail` 和 `Hoisting` 必须分开消费
|
||||
- `Rail` 真实物体不能再复用 `Hoisting` 的法向/分段参数语义
|
||||
|
||||
## 5. 当前保留的日志策略
|
||||
|
||||
- 保留:
|
||||
|
||||
@ -707,19 +707,16 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
else if (_route.PathType == PathType.Rail)
|
||||
{
|
||||
double objectHeight = GetAnimatedObjectHeight();
|
||||
Point3D previousPoint = _pathPoints[0];
|
||||
Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0];
|
||||
double objectHeight = GetAnimatedObjectRailNormalExtent(previousPoint, _pathPoints[0], nextPoint);
|
||||
startPosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight);
|
||||
LogManager.Debug($"[移动到起点] Rail路径调整: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), 物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), 物体高度={objectHeight:F2}, 安装={_route.RailMountMode}, 对接={(PathRoute.IsTopPayloadAnchorForMountMode(_route.RailMountMode) ? "顶面对接" : "底面对接")}");
|
||||
|
||||
if (RailPathPoseHelper.TryCreateRailRotation(
|
||||
_route,
|
||||
if (TryCreateRailPathRotation(
|
||||
previousPoint,
|
||||
_pathPoints[0],
|
||||
nextPoint,
|
||||
GetCurrentRailModelAxisConvention(),
|
||||
_objectRotationCorrection,
|
||||
out var railRotation))
|
||||
{
|
||||
var railLinearTransform = new Transform3D(railRotation).Linear;
|
||||
@ -964,7 +961,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
|
||||
// 🔥 空中路径:根据路径类型和线段索引调整物体位置
|
||||
double objectHeight = _route.PathType == PathType.Rail
|
||||
? GetAnimatedObjectHeight()
|
||||
? GetAnimatedObjectRailNormalExtent(p1, framePosition, p2)
|
||||
: GetAnimatedObjectGroundContactHeight();
|
||||
|
||||
if (_route.PathType == PathType.Hoisting)
|
||||
@ -1079,13 +1076,10 @@ namespace NavisworksTransport.Core.Animation
|
||||
};
|
||||
|
||||
if (_route.PathType == PathType.Rail &&
|
||||
RailPathPoseHelper.TryCreateRailRotation(
|
||||
_route,
|
||||
TryCreateRailPathRotation(
|
||||
previousFramePoint,
|
||||
framePosition,
|
||||
nextFramePoint,
|
||||
GetCurrentRailModelAxisConvention(),
|
||||
_objectRotationCorrection,
|
||||
out var railRotation))
|
||||
{
|
||||
frame.Rotation = railRotation;
|
||||
@ -1941,7 +1935,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
Point3D expectedTrackedEndPoint;
|
||||
if (_route.PathType == PathType.Rail)
|
||||
{
|
||||
double objectHeight = GetAnimatedObjectHeight();
|
||||
double objectHeight = GetAnimatedObjectRailNormalExtent(previousAnchorPoint, terminalAnchorPoint, terminalAnchorPoint);
|
||||
expectedTrackedEndPoint = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(
|
||||
_route,
|
||||
terminalAnchorPoint,
|
||||
@ -3510,19 +3504,227 @@ namespace NavisworksTransport.Core.Animation
|
||||
return new Vector3((float)point.X, (float)point.Y, (float)point.Z);
|
||||
}
|
||||
|
||||
private ModelAxisConvention GetCurrentRailModelAxisConvention()
|
||||
private bool TryGetCurrentRailModelAxisConvention(
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
out ModelAxisConvention convention)
|
||||
{
|
||||
convention = null;
|
||||
|
||||
if (IsVirtualObjectMode)
|
||||
{
|
||||
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||
convention = ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||
return true;
|
||||
}
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
var convention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
||||
if (IsRealObjectMode && _hasRealObjectReferenceRotation)
|
||||
{
|
||||
Vector3 desiredForward = new Vector3(
|
||||
(float)(nextPoint.X - previousPoint.X),
|
||||
(float)(nextPoint.Y - previousPoint.Y),
|
||||
(float)(nextPoint.Z - previousPoint.Z));
|
||||
|
||||
if (desiredForward.LengthSquared() < 1e-6f)
|
||||
{
|
||||
desiredForward = new Vector3(
|
||||
(float)(nextPoint.X - currentPoint.X),
|
||||
(float)(nextPoint.Y - currentPoint.Y),
|
||||
(float)(nextPoint.Z - currentPoint.Z));
|
||||
}
|
||||
|
||||
if (RealObjectRailAxisConventionResolver.TryResolve(
|
||||
_realObjectReferenceAxisX,
|
||||
_realObjectReferenceAxisY,
|
||||
_realObjectReferenceAxisZ,
|
||||
desiredForward,
|
||||
adapter.HostType,
|
||||
out convention,
|
||||
out var selectedForwardAxis,
|
||||
out var selectedForwardWorldAxis))
|
||||
{
|
||||
LogManager.Info(
|
||||
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={IsVirtualObjectMode}, " +
|
||||
$"真实物体参考姿态生效, Forward={convention.ForwardAxis}, Up={convention.UpAxis}, " +
|
||||
$"选中Forward轴={selectedForwardAxis}, 选中Forward世界轴=({selectedForwardWorldAxis.X:F4},{selectedForwardWorldAxis.Y:F4},{selectedForwardWorldAxis.Z:F4})");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
convention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
||||
LogManager.Info(
|
||||
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={IsVirtualObjectMode}, " +
|
||||
$"Forward={convention.ForwardAxis}, Up={convention.UpAxis}");
|
||||
return convention;
|
||||
$"Forward={convention.ForwardAxis}, Up={convention.UpAxis}, 来源=默认轴约定");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryCalculateCurrentRealObjectRailProjectedExtents(
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
out ModelAxisConvention convention,
|
||||
out (double forwardExtent, double sideExtent, double upExtent) extents)
|
||||
{
|
||||
convention = null;
|
||||
extents = (0.0, 0.0, 0.0);
|
||||
|
||||
if (!IsRealObjectMode ||
|
||||
!_hasRealObjectReferenceRotation ||
|
||||
_realObjectLength <= 0.0 ||
|
||||
_realObjectWidth <= 0.0 ||
|
||||
_realObjectHeight <= 0.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
Vector3 desiredForward = new Vector3(
|
||||
(float)(nextPoint.X - previousPoint.X),
|
||||
(float)(nextPoint.Y - previousPoint.Y),
|
||||
(float)(nextPoint.Z - previousPoint.Z));
|
||||
|
||||
if (desiredForward.LengthSquared() < 1e-6f)
|
||||
{
|
||||
desiredForward = new Vector3(
|
||||
(float)(nextPoint.X - currentPoint.X),
|
||||
(float)(nextPoint.Y - currentPoint.Y),
|
||||
(float)(nextPoint.Z - currentPoint.Z));
|
||||
}
|
||||
|
||||
if (!TryGetCurrentRailModelAxisConvention(previousPoint, currentPoint, nextPoint, out var railConvention))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!RailPathPoseHelper.TryCreateRailRotation(
|
||||
_route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
railConvention,
|
||||
LocalEulerRotationCorrection.Zero,
|
||||
out var baselineRotation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Quaternion baselineHostQuaternion = new Quaternion(
|
||||
(float)baselineRotation.A,
|
||||
(float)baselineRotation.B,
|
||||
(float)baselineRotation.C,
|
||||
(float)baselineRotation.D);
|
||||
Quaternion finalHostQuaternion = adapter.ComposeHostQuaternion(
|
||||
baselineHostQuaternion,
|
||||
_objectRotationCorrection);
|
||||
|
||||
return RealObjectRailExtentResolver.TryResolveProjectedSemanticExtents(
|
||||
_realObjectReferenceAxisX,
|
||||
_realObjectReferenceAxisY,
|
||||
_realObjectReferenceAxisZ,
|
||||
desiredForward,
|
||||
adapter.HostType,
|
||||
_realObjectLength,
|
||||
_realObjectWidth,
|
||||
_realObjectHeight,
|
||||
baselineHostQuaternion,
|
||||
finalHostQuaternion,
|
||||
out convention,
|
||||
out extents);
|
||||
}
|
||||
|
||||
public bool TryGetCurrentRouteRealObjectRailProjectedExtents(
|
||||
out double forwardExtent,
|
||||
out double sideExtent,
|
||||
out double upExtent)
|
||||
{
|
||||
forwardExtent = 0.0;
|
||||
sideExtent = 0.0;
|
||||
upExtent = 0.0;
|
||||
|
||||
if (_route == null || _route.PathType != PathType.Rail || _pathPoints == null || _pathPoints.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Point3D previousPoint = _pathPoints[0];
|
||||
Point3D currentPoint = _pathPoints[0];
|
||||
Point3D nextPoint = _pathPoints.Count > 1 ? _pathPoints[1] : _pathPoints[0];
|
||||
|
||||
if (!TryCalculateCurrentRealObjectRailProjectedExtents(
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
out _,
|
||||
out var extents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
forwardExtent = extents.forwardExtent;
|
||||
sideExtent = extents.sideExtent;
|
||||
upExtent = extents.upExtent;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryCreateRailPathRotation(
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint,
|
||||
out Rotation3D rotation)
|
||||
{
|
||||
rotation = Rotation3D.Identity;
|
||||
|
||||
if (!TryGetCurrentRailModelAxisConvention(previousPoint, currentPoint, nextPoint, out var railConvention))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsRealObjectMode)
|
||||
{
|
||||
if (!RailPathPoseHelper.TryCreateRailRotation(
|
||||
_route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
railConvention,
|
||||
LocalEulerRotationCorrection.Zero,
|
||||
out var baselineRotation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
Quaternion baselineHostQuaternion = new Quaternion(
|
||||
(float)baselineRotation.A,
|
||||
(float)baselineRotation.B,
|
||||
(float)baselineRotation.C,
|
||||
(float)baselineRotation.D);
|
||||
Quaternion composedHostQuaternion = adapter.ComposeHostQuaternion(
|
||||
baselineHostQuaternion,
|
||||
_objectRotationCorrection);
|
||||
rotation = adapter.FromHostQuaternion(composedHostQuaternion);
|
||||
|
||||
Matrix4x4 baselineLinear = Matrix4x4.CreateFromQuaternion(baselineHostQuaternion);
|
||||
Matrix4x4 composedLinear = Matrix4x4.CreateFromQuaternion(composedHostQuaternion);
|
||||
LogManager.Info(
|
||||
$"[Rail真实物体角度修正] BaselineX=({baselineLinear.M11:F4},{baselineLinear.M21:F4},{baselineLinear.M31:F4}), " +
|
||||
$"BaselineY=({baselineLinear.M12:F4},{baselineLinear.M22:F4},{baselineLinear.M32:F4}), " +
|
||||
$"BaselineZ=({baselineLinear.M13:F4},{baselineLinear.M23:F4},{baselineLinear.M33:F4}), " +
|
||||
$"HostComposeX=({composedLinear.M11:F4},{composedLinear.M21:F4},{composedLinear.M31:F4}), " +
|
||||
$"HostComposeY=({composedLinear.M12:F4},{composedLinear.M22:F4},{composedLinear.M32:F4}), " +
|
||||
$"HostComposeZ=({composedLinear.M13:F4},{composedLinear.M23:F4},{composedLinear.M33:F4})");
|
||||
return true;
|
||||
}
|
||||
|
||||
return RailPathPoseHelper.TryCreateRailRotation(
|
||||
_route,
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
railConvention,
|
||||
_objectRotationCorrection,
|
||||
out rotation);
|
||||
}
|
||||
|
||||
private ModelAxisConvention GetCurrentModelAxisConvention()
|
||||
@ -3627,6 +3829,27 @@ namespace NavisworksTransport.Core.Animation
|
||||
return true;
|
||||
}
|
||||
|
||||
private double GetAnimatedObjectRailNormalExtent(
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
Point3D nextPoint)
|
||||
{
|
||||
if (TryCalculateCurrentRealObjectRailProjectedExtents(
|
||||
previousPoint,
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
out var convention,
|
||||
out var extents))
|
||||
{
|
||||
LogManager.Debug(
|
||||
$"[Rail法向尺寸] 真实物体有效尺寸: Forward={extents.forwardExtent:F3}, Side={extents.sideExtent:F3}, Up={extents.upExtent:F3}, " +
|
||||
$"ForwardAxis={convention.ForwardAxis}, UpAxis={convention.UpAxis}, 角度={_objectRotationCorrection}");
|
||||
return extents.upExtent;
|
||||
}
|
||||
|
||||
return GetAnimatedObjectHeight();
|
||||
}
|
||||
|
||||
private bool TryCreatePlanarPathRotationFromHostForward(Vector3D hostForward, out Rotation3D rotation)
|
||||
{
|
||||
rotation = Rotation3D.Identity;
|
||||
|
||||
@ -1713,9 +1713,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
_pathAnimationManager?.SetRealObjectDimensions(objectLength, objectWidth, objectHeight);
|
||||
LogManager.Debug($"[选择物体] 保存原始尺寸: 长度={_objectOriginalLength:F2}m, 宽度={_objectOriginalWidth:F2}m, 高度={_objectOriginalHeight:F2}m");
|
||||
|
||||
// 3. 设置新物体(会触发UpdatePassageSpaceVisualization)
|
||||
SelectedAnimatedObject = newObject;
|
||||
// 3. 先注册到动画管理器,再设置 SelectedAnimatedObject。
|
||||
// SelectedAnimatedObject 的 setter 会立即触发 MoveAnimatedObjectToPathStart();
|
||||
// 如果先移动、后 SetAnimatedObject,会把“已摆到起点后的当前姿态”再次当成参考姿态缓存。
|
||||
_pathAnimationManager?.SetAnimatedObject(newObject);
|
||||
SelectedAnimatedObject = newObject;
|
||||
LogManager.Info($"已选择移动物体: {SelectedAnimatedObject.DisplayName}");
|
||||
|
||||
// 选择实体物体意味着切换到实体模式,避免后续起点同步仍走虚拟物体分支。
|
||||
@ -2160,16 +2162,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
LogManager.Info("已清除PathAnimationManager中的动画数据并归位物体");
|
||||
}
|
||||
|
||||
// 2. 重置角度修正值为0
|
||||
// 2. 先清空选择,避免角度修正归零时又触发“移动到起点”链路。
|
||||
SelectedAnimatedObject = null;
|
||||
|
||||
// 3. 重置角度修正值为0
|
||||
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||
LogManager.Debug("[清除物体] 已重置角度修正值为0");
|
||||
|
||||
// 3. 重置属性
|
||||
SelectedAnimatedObject = null;
|
||||
// 4. 重置属性
|
||||
UpdateAnimatedObjectInfo();
|
||||
UpdateCanGenerateAnimation();
|
||||
|
||||
// 4. 清理高亮
|
||||
// 5. 清理高亮
|
||||
ModelHighlightHelper.ClearCollisionHighlights();
|
||||
|
||||
LogManager.Info("移动物体已完全清除并归位");
|
||||
@ -4339,6 +4343,20 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
upSize = _objectOriginalHeight * metersToUnits;
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
if (CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail &&
|
||||
_pathAnimationManager != null &&
|
||||
_pathAnimationManager.TryGetCurrentRouteRealObjectRailProjectedExtents(
|
||||
out double resolvedForward,
|
||||
out double resolvedSide,
|
||||
out double resolvedUp))
|
||||
{
|
||||
double unitsToMetersForRail = UnitsConverter.GetUnitsToMetersConversionFactor();
|
||||
LogManager.Debug(
|
||||
$"[角度修正] Rail真实物体复用路径姿态尺寸: Forward={resolvedForward * unitsToMetersForRail:F2}m, " +
|
||||
$"Side={resolvedSide * unitsToMetersForRail:F2}m, Up={resolvedUp * unitsToMetersForRail:F2}m, 角度={_objectRotationCorrection}");
|
||||
return (resolvedForward, resolvedSide, resolvedUp);
|
||||
}
|
||||
|
||||
axisConvention = CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail
|
||||
? ModelAxisConvention.CreateRailAssetConvention()
|
||||
: ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
||||
@ -4442,9 +4460,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
else if (SelectedAnimatedObject != null)
|
||||
{
|
||||
// 根据路径类型确定通行空间的尺寸
|
||||
if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Rail || CurrentPathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||||
if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||||
{
|
||||
// 空中路径(空轨或吊装):
|
||||
// 吊装路径:
|
||||
// 高度上下都加间隙
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||
passageNormalToPath = effectiveHeight + 2 * safetyMargin; // 局部up方向高度 + 2*间隙(法线方向)
|
||||
@ -4467,7 +4485,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
passageNormalToPathHorizontal = passageNormalToPath;
|
||||
LogManager.Debug($"[通行空间可视化] 地面路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 有效高度={effectiveHeight / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
|
||||
}
|
||||
else // Rail
|
||||
else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Rail)
|
||||
{
|
||||
// 空轨路径(可能有坡度):
|
||||
// 物体的长度方向(X轴,前进方向)朝向路径方向
|
||||
@ -4482,6 +4500,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
passageNormalToPathHorizontal = passageNormalToPath;
|
||||
LogManager.Debug($"[通行空间可视化] 空轨路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 有效高度={effectiveHeight / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
|
||||
}
|
||||
else
|
||||
{
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin;
|
||||
passageNormalToPath = effectiveHeight + safetyMargin;
|
||||
passageAlongPath = effectiveLength;
|
||||
passageNormalToPathVertical = passageNormalToPath;
|
||||
passageNormalToPathHorizontal = passageNormalToPath;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -28,6 +28,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
private readonly UIStateManager _uiStateManager;
|
||||
private readonly HashSet<string> _documentRotationHintShown = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly HashSet<string> _documentRotationHintPending = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 日志管理字段
|
||||
private ObservableCollection<string> _logLevels;
|
||||
@ -506,7 +507,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
private void ShowRootModelRotationHintIfNeeded()
|
||||
{
|
||||
string documentKey = GetCurrentDocumentKey();
|
||||
if (string.IsNullOrWhiteSpace(documentKey) || _documentRotationHintShown.Contains(documentKey))
|
||||
if (string.IsNullOrWhiteSpace(documentKey) ||
|
||||
_documentRotationHintShown.Contains(documentKey) ||
|
||||
_documentRotationHintPending.Contains(documentKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -522,13 +525,51 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
_documentRotationHintShown.Add(documentKey);
|
||||
LogManager.Info($"[模型整体旋转提示] {hintMessage}");
|
||||
System.Windows.MessageBox.Show(
|
||||
hintMessage,
|
||||
"坐标系提示",
|
||||
System.Windows.MessageBoxButton.OK,
|
||||
System.Windows.MessageBoxImage.Information);
|
||||
_documentRotationHintPending.Add(documentKey);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(1500).ConfigureAwait(false);
|
||||
_uiStateManager?.QueueUIUpdate(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
string currentDocumentKey = GetCurrentDocumentKey();
|
||||
if (!string.Equals(documentKey, currentDocumentKey, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_documentRotationHintPending.Remove(documentKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_documentRotationHintShown.Contains(documentKey))
|
||||
{
|
||||
_documentRotationHintPending.Remove(documentKey);
|
||||
return;
|
||||
}
|
||||
|
||||
_documentRotationHintShown.Add(documentKey);
|
||||
_documentRotationHintPending.Remove(documentKey);
|
||||
LogManager.Info($"[模型整体旋转提示] {hintMessage}");
|
||||
System.Windows.MessageBox.Show(
|
||||
hintMessage,
|
||||
"坐标系提示",
|
||||
System.Windows.MessageBoxButton.OK,
|
||||
System.Windows.MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_documentRotationHintPending.Remove(documentKey);
|
||||
LogManager.Warning($"[模型整体旋转提示] 延后弹出失败: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_documentRotationHintPending.Remove(documentKey);
|
||||
LogManager.Warning($"[模型整体旋转提示] 延后任务失败: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TryGetRootModelRotationHint(Document doc, out string hintMessage)
|
||||
|
||||
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据“已解释为当前宿主语义”的真实物体参考姿态,解析 Rail 路径应使用的局部轴约定。
|
||||
///
|
||||
/// 语义:
|
||||
/// - 真实物体没有资产坐标系,但 fragment 参考姿态经过 Fragment默认Up 解释后,
|
||||
/// 可以得到“当前宿主语义下”的真实参考三轴。
|
||||
/// - Rail 与 Ground/Hoisting 的统一点在于:都先解释对象姿态,再进入路径框架。
|
||||
/// - Rail 的特殊性只在路径框架:forward 来自 rail 切向,up 来自 rail 法向。
|
||||
/// - 本解析器只负责回答:对这个真实物体来说,哪个局部 forward 轴最接近当前路径 forward;
|
||||
/// up 轴始终采用当前宿主语义对应的轴族(YUp => +Y,ZUp => +Z)。
|
||||
/// </summary>
|
||||
public static class RealObjectRailAxisConventionResolver
|
||||
{
|
||||
private const float AxisEpsilon = 1e-6f;
|
||||
|
||||
public static bool TryResolve(
|
||||
Vector3 referenceAxisX,
|
||||
Vector3 referenceAxisY,
|
||||
Vector3 referenceAxisZ,
|
||||
Vector3 desiredForward,
|
||||
CoordinateSystemType hostType,
|
||||
out ModelAxisConvention convention,
|
||||
out LocalAxisDirection selectedForwardAxis,
|
||||
out Vector3 selectedForwardWorldAxis)
|
||||
{
|
||||
convention = null;
|
||||
selectedForwardAxis = LocalAxisDirection.PositiveX;
|
||||
selectedForwardWorldAxis = Vector3.Zero;
|
||||
|
||||
if (!TryNormalize(desiredForward, out Vector3 normalizedForward))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryNormalize(referenceAxisX, out Vector3 axisX) ||
|
||||
!TryNormalize(referenceAxisY, out Vector3 axisY) ||
|
||||
!TryNormalize(referenceAxisZ, out Vector3 axisZ))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalAxisDirection semanticUpAxis =
|
||||
hostType == CoordinateSystemType.YUp
|
||||
? LocalAxisDirection.PositiveY
|
||||
: LocalAxisDirection.PositiveZ;
|
||||
|
||||
Candidate best = Candidate.Invalid;
|
||||
foreach (Candidate candidate in EnumerateForwardCandidates(axisX, axisY, axisZ, semanticUpAxis))
|
||||
{
|
||||
float score = Vector3.Dot(candidate.WorldAxis, normalizedForward);
|
||||
if (best.IsInvalid || score > best.Score)
|
||||
{
|
||||
best = new Candidate(candidate.Axis, candidate.WorldAxis, score);
|
||||
}
|
||||
}
|
||||
|
||||
if (best.IsInvalid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convention = new ModelAxisConvention(best.Axis, semanticUpAxis);
|
||||
selectedForwardAxis = best.Axis;
|
||||
selectedForwardWorldAxis = best.WorldAxis;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Candidate[] EnumerateForwardCandidates(
|
||||
Vector3 axisX,
|
||||
Vector3 axisY,
|
||||
Vector3 axisZ,
|
||||
LocalAxisDirection semanticUpAxis)
|
||||
{
|
||||
switch (semanticUpAxis)
|
||||
{
|
||||
case LocalAxisDirection.PositiveY:
|
||||
return new[]
|
||||
{
|
||||
new Candidate(LocalAxisDirection.PositiveX, axisX, 0f),
|
||||
new Candidate(LocalAxisDirection.NegativeX, -axisX, 0f),
|
||||
new Candidate(LocalAxisDirection.PositiveZ, axisZ, 0f),
|
||||
new Candidate(LocalAxisDirection.NegativeZ, -axisZ, 0f)
|
||||
};
|
||||
|
||||
case LocalAxisDirection.PositiveZ:
|
||||
default:
|
||||
return new[]
|
||||
{
|
||||
new Candidate(LocalAxisDirection.PositiveX, axisX, 0f),
|
||||
new Candidate(LocalAxisDirection.NegativeX, -axisX, 0f),
|
||||
new Candidate(LocalAxisDirection.PositiveY, axisY, 0f),
|
||||
new Candidate(LocalAxisDirection.NegativeY, -axisY, 0f)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryNormalize(Vector3 value, out Vector3 normalized)
|
||||
{
|
||||
if (value.LengthSquared() < AxisEpsilon)
|
||||
{
|
||||
normalized = Vector3.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
normalized = Vector3.Normalize(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
private readonly struct Candidate
|
||||
{
|
||||
public static Candidate Invalid => new Candidate(LocalAxisDirection.PositiveX, Vector3.Zero, float.MinValue);
|
||||
|
||||
public LocalAxisDirection Axis { get; }
|
||||
public Vector3 WorldAxis { get; }
|
||||
public float Score { get; }
|
||||
|
||||
public bool IsInvalid => Score == float.MinValue;
|
||||
|
||||
public Candidate(LocalAxisDirection axis, Vector3 worldAxis, float score)
|
||||
{
|
||||
Axis = axis;
|
||||
WorldAxis = worldAxis;
|
||||
Score = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/Utils/CoordinateSystem/RealObjectRailExtentResolver.cs
Normal file
77
src/Utils/CoordinateSystem/RealObjectRailExtentResolver.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 真实物体在 Rail 路径下的尺寸语义解析。
|
||||
/// 先根据解释后的真实参考姿态解析 rail 轴约定,再使用:
|
||||
/// 1. rail 基姿态(零角度时对象与路径框架对齐后的宿主姿态)
|
||||
/// 2. 最终姿态(rail 基姿态再叠加宿主轴角度修正)
|
||||
/// 共同计算沿 rail forward/side/normal 的有效尺寸。
|
||||
///
|
||||
/// 关键点:
|
||||
/// - 这里不能只吃“角度修正”本身;否则双轴旋转时会漏掉 rail 基姿态语义。
|
||||
/// - 目标语义轴来自 rail 基姿态,而不是宿主世界轴。
|
||||
/// </summary>
|
||||
public static class RealObjectRailExtentResolver
|
||||
{
|
||||
public static bool TryResolveProjectedSemanticExtents(
|
||||
Vector3 referenceAxisX,
|
||||
Vector3 referenceAxisY,
|
||||
Vector3 referenceAxisZ,
|
||||
Vector3 desiredForward,
|
||||
CoordinateSystemType hostType,
|
||||
double forwardSize,
|
||||
double sideSize,
|
||||
double upSize,
|
||||
Quaternion baselineHostQuaternion,
|
||||
Quaternion finalHostQuaternion,
|
||||
out ModelAxisConvention convention,
|
||||
out (double forwardExtent, double sideExtent, double upExtent) extents)
|
||||
{
|
||||
convention = null;
|
||||
extents = (0.0, 0.0, 0.0);
|
||||
|
||||
if (!RealObjectRailAxisConventionResolver.TryResolve(
|
||||
referenceAxisX,
|
||||
referenceAxisY,
|
||||
referenceAxisZ,
|
||||
desiredForward,
|
||||
hostType,
|
||||
out convention,
|
||||
out _,
|
||||
out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 localSize = convention.CreateScaleVector3(forwardSize, sideSize, upSize);
|
||||
Vector3 rotatedLocalX = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, finalHostQuaternion));
|
||||
Vector3 rotatedLocalY = Vector3.Normalize(Vector3.Transform(Vector3.UnitY, finalHostQuaternion));
|
||||
Vector3 rotatedLocalZ = Vector3.Normalize(Vector3.Transform(Vector3.UnitZ, finalHostQuaternion));
|
||||
|
||||
Vector3 targetForward = Vector3.Normalize(Vector3.Transform(convention.ForwardUnitVector, baselineHostQuaternion));
|
||||
Vector3 targetUp = Vector3.Normalize(Vector3.Transform(convention.UpUnitVector, baselineHostQuaternion));
|
||||
Vector3 targetSide = Vector3.Normalize(Vector3.Cross(targetForward, targetUp));
|
||||
|
||||
extents = (
|
||||
ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetForward),
|
||||
ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetSide),
|
||||
ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, targetUp));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static double ProjectExtent(
|
||||
Vector3 localSize,
|
||||
Vector3 rotatedLocalX,
|
||||
Vector3 rotatedLocalY,
|
||||
Vector3 rotatedLocalZ,
|
||||
Vector3 targetAxis)
|
||||
{
|
||||
return Math.Abs(Vector3.Dot(rotatedLocalX, targetAxis)) * localSize.X +
|
||||
Math.Abs(Vector3.Dot(rotatedLocalY, targetAxis)) * localSize.Y +
|
||||
Math.Abs(Vector3.Dot(rotatedLocalZ, targetAxis)) * localSize.Z;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user