Checkpoint animation rotation refactor state
This commit is contained in:
parent
1cf4fe5967
commit
d52b1aef08
13
AGENTS.md
13
AGENTS.md
@ -308,6 +308,19 @@ double distance = GeometryHelper.Distance(pointA, pointB);
|
|||||||
- 需要坐标转换?→ 使用 `CoordinateConverter`
|
- 需要坐标转换?→ 使用 `CoordinateConverter`
|
||||||
- 需要路径相关工具?→ 使用 `PathHelper`
|
- 需要路径相关工具?→ 使用 `PathHelper`
|
||||||
|
|
||||||
|
#### 5. 临时定位代码必须回收
|
||||||
|
|
||||||
|
为定位问题临时加入的兜底调用、强制刷新、额外同步、绕过分支等代码,如果后续确认**不是真正根因**,在根因修复后必须恢复或删除,不能把这类临时补丁长期保留在正式代码中。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ 错误:为掩盖问题长期保留的强制调用
|
||||||
|
DoNormalUpdate();
|
||||||
|
ForceRefreshAgain(); // 只是为了“看起来好了”
|
||||||
|
|
||||||
|
// ✅ 正确:先定位根因,再移除临时补丁
|
||||||
|
DoNormalUpdate();
|
||||||
|
```
|
||||||
|
|
||||||
### 单位系统 - 极其重要
|
### 单位系统 - 极其重要
|
||||||
|
|
||||||
**所有网格地图和路径规划计算必须使用模型单位,严禁混用米制单位。**
|
**所有网格地图和路径规划计算必须使用模型单位,严禁混用米制单位。**
|
||||||
|
|||||||
@ -334,11 +334,14 @@
|
|||||||
<Compile Include="src\Utils\CoordinateSystem\CoordinateSystemType.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\CoordinateSystemType.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\ICoordinateSystem.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\ICoordinateSystem.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalBounds3.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\CanonicalBounds3.cs" />
|
||||||
|
<Compile Include="src\Utils\CoordinateSystem\LocalAxisPoseBuilder.cs" />
|
||||||
|
<Compile Include="src\Utils\CoordinateSystem\LocalEulerRotationCorrection.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalPlanarPoseBuilder.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\CanonicalPlanarPoseBuilder.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailOffsetResolver.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailOffsetResolver.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailPoseBuilder.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailPoseBuilder.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalTrackedPositionResolver.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\CanonicalTrackedPositionResolver.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\ObjectSpaceOrientationHelper.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\ObjectSpaceOrientationHelper.cs" />
|
||||||
|
<Compile Include="src\Utils\CoordinateSystem\RotatedObjectExtentHelper.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\HostCoordinateAdapter.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\HostCoordinateAdapter.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\HoistingCoordinateHelper.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\HoistingCoordinateHelper.cs" />
|
||||||
<Compile Include="src\Utils\CoordinateSystem\LocalAxisDirection.cs" />
|
<Compile Include="src\Utils\CoordinateSystem\LocalAxisDirection.cs" />
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using NavisworksTransport.Utils.CoordinateSystem;
|
using NavisworksTransport.Utils.CoordinateSystem;
|
||||||
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||||
@ -55,6 +56,52 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
|||||||
Assert.AreEqual(1.0, linear.M33, 1e-6);
|
Assert.AreEqual(1.0, linear.M33, 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ZeroLocalEulerCorrection_ShouldMatchCurrentPlanarBaseline()
|
||||||
|
{
|
||||||
|
var convention = ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp);
|
||||||
|
|
||||||
|
Assert.IsTrue(CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
||||||
|
new Vector3(5, 0, 0),
|
||||||
|
Vector3.UnitZ,
|
||||||
|
convention,
|
||||||
|
out Quaternion baselineRotation));
|
||||||
|
|
||||||
|
Assert.IsTrue(CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
||||||
|
new Vector3(5, 0, 0),
|
||||||
|
Vector3.UnitZ,
|
||||||
|
convention,
|
||||||
|
LocalEulerRotationCorrection.Zero,
|
||||||
|
out Quaternion correctedRotation));
|
||||||
|
|
||||||
|
AssertQuaternionEquivalent(baselineRotation, correctedRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LocalEulerCorrection_ShouldAlignCorrectedLocalForwardWithPlanarForward()
|
||||||
|
{
|
||||||
|
var convention = ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp);
|
||||||
|
var correction = new LocalEulerRotationCorrection(0.0, 0.0, 90.0);
|
||||||
|
var adapter = new HostCoordinateAdapter(CoordinateSystemType.ZUp);
|
||||||
|
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(correction);
|
||||||
|
|
||||||
|
Assert.IsTrue(CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
||||||
|
new Vector3(5, 0, 0),
|
||||||
|
Vector3.UnitZ,
|
||||||
|
convention,
|
||||||
|
correctionQuaternion,
|
||||||
|
out Quaternion rotation));
|
||||||
|
|
||||||
|
LocalAxisPoseBuilder.ApplyLocalPreRotation(convention, correctionQuaternion, out var correctedLocalForward, out var correctedLocalUp);
|
||||||
|
Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(rotation);
|
||||||
|
|
||||||
|
Vector3 mappedForward = TransformLocalVector(linear, correctedLocalForward);
|
||||||
|
Vector3 mappedUp = TransformLocalVector(linear, correctedLocalUp);
|
||||||
|
|
||||||
|
AssertVector(mappedForward, 1, 0, 0);
|
||||||
|
AssertVector(mappedUp, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
private static void AssertColumn(Matrix4x4 matrix, int column, double x, double y, double z)
|
private static void AssertColumn(Matrix4x4 matrix, int column, double x, double y, double z)
|
||||||
{
|
{
|
||||||
switch (column)
|
switch (column)
|
||||||
@ -79,5 +126,32 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AssertVector(Vector3 actual, double x, double y, double z)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(x, actual.X, 1e-6);
|
||||||
|
Assert.AreEqual(y, actual.Y, 1e-6);
|
||||||
|
Assert.AreEqual(z, actual.Z, 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertQuaternionEquivalent(Quaternion expected, Quaternion actual)
|
||||||
|
{
|
||||||
|
double dot = Math.Abs(
|
||||||
|
expected.X * actual.X +
|
||||||
|
expected.Y * actual.Y +
|
||||||
|
expected.Z * actual.Z +
|
||||||
|
expected.W * actual.W);
|
||||||
|
|
||||||
|
Assert.AreEqual(1.0, dot, 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 TransformLocalVector(Matrix4x4 linear, Vector3 localVector)
|
||||||
|
{
|
||||||
|
Vector3 world = new Vector3(
|
||||||
|
linear.M11 * localVector.X + linear.M12 * localVector.Y + linear.M13 * localVector.Z,
|
||||||
|
linear.M21 * localVector.X + linear.M22 * localVector.Y + linear.M23 * localVector.Z,
|
||||||
|
linear.M31 * localVector.X + linear.M32 * localVector.Y + linear.M33 * localVector.Z);
|
||||||
|
return Vector3.Normalize(world);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,6 +137,34 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
|||||||
AssertVector(mappedUp, 0, 0, 1);
|
AssertVector(mappedUp, 0, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LocalEulerCorrection_ShouldAlignCorrectedLocalForwardWithRailTangent()
|
||||||
|
{
|
||||||
|
var convention = ModelAxisConvention.CreateRailAssetConvention();
|
||||||
|
var correction = new LocalEulerRotationCorrection(0.0, 90.0, 0.0);
|
||||||
|
var adapter = new HostCoordinateAdapter(CoordinateSystemType.ZUp);
|
||||||
|
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(correction);
|
||||||
|
|
||||||
|
Assert.IsTrue(CanonicalRailPoseBuilder.TryCreateQuaternion(
|
||||||
|
new Vector3(0, 0, 0),
|
||||||
|
new Vector3(1, 0, 0),
|
||||||
|
new Vector3(2, 0, 0),
|
||||||
|
Vector3.UnitZ,
|
||||||
|
convention,
|
||||||
|
null,
|
||||||
|
correctionQuaternion,
|
||||||
|
out Quaternion rotation));
|
||||||
|
|
||||||
|
LocalAxisPoseBuilder.ApplyLocalPreRotation(convention, correctionQuaternion, out var correctedLocalForward, out var correctedLocalUp);
|
||||||
|
Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(rotation);
|
||||||
|
|
||||||
|
Vector3 mappedForward = TransformLocalVector(linear, correctedLocalForward);
|
||||||
|
Vector3 mappedUp = TransformLocalVector(linear, correctedLocalUp);
|
||||||
|
|
||||||
|
AssertVector(mappedForward, 1, 0, 0);
|
||||||
|
AssertVector(mappedUp, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void PreferredNormal_ShouldOverrideCanonicalUpForRailFrame()
|
public void PreferredNormal_ShouldOverrideCanonicalUpForRailFrame()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using NavisworksTransport.Utils.CoordinateSystem;
|
using NavisworksTransport.Utils.CoordinateSystem;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System;
|
||||||
using Autodesk.Navisworks.Api;
|
using Autodesk.Navisworks.Api;
|
||||||
|
|
||||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||||
@ -143,6 +144,20 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
|||||||
Assert.AreEqual(0.0000, hostLinear.M33, 1e-3);
|
Assert.AreEqual(0.0000, hostLinear.M33, 1e-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void YUp_HostRotationCorrection_ShouldMapHostYAxisToCanonicalUp()
|
||||||
|
{
|
||||||
|
var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp);
|
||||||
|
var correction = new LocalEulerRotationCorrection(0.0, 90.0, 0.0);
|
||||||
|
|
||||||
|
Quaternion canonicalCorrection = adapter.CreateCanonicalRotationCorrection(correction);
|
||||||
|
Vector3 rotatedForward = Vector3.Transform(Vector3.UnitX, canonicalCorrection);
|
||||||
|
|
||||||
|
Assert.AreEqual(0.0, rotatedForward.X, 1e-6);
|
||||||
|
Assert.AreEqual(1.0, rotatedForward.Y, 1e-6);
|
||||||
|
Assert.AreEqual(0.0, rotatedForward.Z, 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
private static void AssertPoint(Vector3 point, double x, double y, double z)
|
private static void AssertPoint(Vector3 point, double x, double y, double z)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(x, point.X, 1e-9);
|
Assert.AreEqual(x, point.X, 1e-9);
|
||||||
|
|||||||
@ -36,5 +36,21 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
|||||||
Assert.AreEqual(0.0, up.Y, 1e-6);
|
Assert.AreEqual(0.0, up.Y, 1e-6);
|
||||||
Assert.AreEqual(0.0, up.Z, 1e-6);
|
Assert.AreEqual(0.0, up.Z, 1e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TryCalculateAxes_ShouldReturnFalse_ForZeroLengthSegment()
|
||||||
|
{
|
||||||
|
var segmentDirection = Vector3.Zero;
|
||||||
|
var upReference = new Vector3(0f, 1f, 0f);
|
||||||
|
|
||||||
|
bool ok = ObjectSpaceOrientationHelper.TryCalculateAxes(
|
||||||
|
segmentDirection,
|
||||||
|
upReference,
|
||||||
|
out var axes);
|
||||||
|
|
||||||
|
Assert.IsFalse(ok);
|
||||||
|
Assert.AreEqual(Vector3.Zero, axes.right);
|
||||||
|
Assert.AreEqual(Vector3.Zero, axes.up);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,21 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
|||||||
"$ErrorActionPreference = 'Stop';" ^
|
"$ErrorActionPreference = 'Stop';" ^
|
||||||
"$targetDir = [System.IO.Path]::GetFullPath('%TARGET_DIR%');" ^
|
"$targetDir = [System.IO.Path]::GetFullPath('%TARGET_DIR%');" ^
|
||||||
"$buildDir = [System.IO.Path]::GetFullPath('%BUILD_DIR%');" ^
|
"$buildDir = [System.IO.Path]::GetFullPath('%BUILD_DIR%');" ^
|
||||||
|
"$deployDllNames = @(" ^
|
||||||
|
" 'TransportPlugin.dll'," ^
|
||||||
|
" 'geometry4Sharp.dll'," ^
|
||||||
|
" 'Newtonsoft.Json.dll'," ^
|
||||||
|
" 'Roy-T.AStar.dll'," ^
|
||||||
|
" 'SQLite.Interop.dll'," ^
|
||||||
|
" 'System.Data.SQLite.dll'," ^
|
||||||
|
" 'Tomlyn.dll'" ^
|
||||||
|
");" ^
|
||||||
|
"$stalePluginFiles = @(" ^
|
||||||
|
" 'Autodesk.Navisworks.Api.dll'," ^
|
||||||
|
" 'NavisworksTransport.UnitTests.dll'," ^
|
||||||
|
" 'Microsoft.VisualStudio.TestPlatform.TestFramework.dll'," ^
|
||||||
|
" 'Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll'" ^
|
||||||
|
");" ^
|
||||||
"function Test-FileUnlocked([string]$path) {" ^
|
"function Test-FileUnlocked([string]$path) {" ^
|
||||||
" if (-not (Test-Path $path)) { return $true }" ^
|
" if (-not (Test-Path $path)) { return $true }" ^
|
||||||
" try {" ^
|
" try {" ^
|
||||||
@ -39,14 +54,22 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
|||||||
"foreach ($keyFile in $keyTargetFiles) { Wait-FileUnlocked $keyFile $deadline }" ^
|
"foreach ($keyFile in $keyTargetFiles) { Wait-FileUnlocked $keyFile $deadline }" ^
|
||||||
"" ^
|
"" ^
|
||||||
"New-Item -ItemType Directory -Force -Path $targetDir | Out-Null;" ^
|
"New-Item -ItemType Directory -Force -Path $targetDir | Out-Null;" ^
|
||||||
"Copy-Item (Join-Path $buildDir '*.dll') $targetDir -Force;" ^
|
"foreach ($staleFileName in $stalePluginFiles) {" ^
|
||||||
|
" $stalePath = Join-Path $targetDir $staleFileName;" ^
|
||||||
|
" if (Test-Path $stalePath) { Remove-Item $stalePath -Force }" ^
|
||||||
|
"}" ^
|
||||||
|
"foreach ($dllName in $deployDllNames) {" ^
|
||||||
|
" $sourceDll = Join-Path $buildDir $dllName;" ^
|
||||||
|
" if (-not (Test-Path $sourceDll)) { throw ('Deployment source file missing: ' + $sourceDll) }" ^
|
||||||
|
" Copy-Item $sourceDll $targetDir -Force;" ^
|
||||||
|
"}" ^
|
||||||
"if (Test-Path (Join-Path $buildDir 'resources')) {" ^
|
"if (Test-Path (Join-Path $buildDir 'resources')) {" ^
|
||||||
" New-Item -ItemType Directory -Force -Path (Join-Path $targetDir 'resources') | Out-Null;" ^
|
" New-Item -ItemType Directory -Force -Path (Join-Path $targetDir 'resources') | Out-Null;" ^
|
||||||
" Copy-Item (Join-Path $buildDir 'resources\\*') (Join-Path $targetDir 'resources') -Recurse -Force;" ^
|
" Copy-Item (Join-Path $buildDir 'resources\\*') (Join-Path $targetDir 'resources') -Recurse -Force;" ^
|
||||||
"}" ^
|
"}" ^
|
||||||
"" ^
|
"" ^
|
||||||
"$sourceFiles = @();" ^
|
"$sourceFiles = @();" ^
|
||||||
"$sourceFiles += Get-ChildItem $buildDir -File -Filter '*.dll';" ^
|
"foreach ($dllName in $deployDllNames) { $sourceFiles += Get-Item (Join-Path $buildDir $dllName) }" ^
|
||||||
"if (Test-Path (Join-Path $buildDir 'resources')) {" ^
|
"if (Test-Path (Join-Path $buildDir 'resources')) {" ^
|
||||||
" $sourceFiles += Get-ChildItem (Join-Path $buildDir 'resources') -File;" ^
|
" $sourceFiles += Get-ChildItem (Join-Path $buildDir 'resources') -File;" ^
|
||||||
"}" ^
|
"}" ^
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
- Navisworks 世界坐标统一视为**外部坐标**
|
- Navisworks 世界坐标统一视为**外部坐标**
|
||||||
- 程序内部统一使用一套**规范内部坐标**
|
- 程序内部统一使用一套**规范内部坐标**
|
||||||
- 工程语义使用独立的**业务基准坐标**
|
- 工程语义使用独立的**业务基准坐标**
|
||||||
|
- 只有插件自带资源才允许引入**资产坐标系**
|
||||||
- 坐标转换只发生在少数边界入口
|
- 坐标转换只发生在少数边界入口
|
||||||
- 业务逻辑禁止直接依赖宿主坐标语义
|
- 业务逻辑禁止直接依赖宿主坐标语义
|
||||||
|
|
||||||
@ -95,6 +96,7 @@ flowchart LR
|
|||||||
特点:
|
特点:
|
||||||
|
|
||||||
- 是程序的输入/输出坐标
|
- 是程序的输入/输出坐标
|
||||||
|
- UI 文本框、对话框、日志、鼠标拾取结果,统一按宿主坐标系解释和显示
|
||||||
- 可能来自 `Y-up` 项目,也可能来自 `Z-up` 项目
|
- 可能来自 `Y-up` 项目,也可能来自 `Z-up` 项目
|
||||||
- 不应直接当成内部计算坐标
|
- 不应直接当成内部计算坐标
|
||||||
|
|
||||||
@ -118,6 +120,29 @@ flowchart LR
|
|||||||
- 项目 up 方向的业务解释
|
- 项目 up 方向的业务解释
|
||||||
- 终端安装参考面
|
- 终端安装参考面
|
||||||
- 轨道参考面
|
- 轨道参考面
|
||||||
|
|
||||||
|
### 3.2.1 术语约束
|
||||||
|
|
||||||
|
后续文档、代码注释和日志中,统一使用以下说法:
|
||||||
|
|
||||||
|
- **宿主坐标系(Host Space)**
|
||||||
|
- 指 Navisworks 文档坐标系
|
||||||
|
- `Y-up` / `Z-up` 的判断只属于这一层
|
||||||
|
- **内部坐标系(Canonical Space)**
|
||||||
|
- 指程序内部统一使用的 `Z-up` 坐标系
|
||||||
|
- 仅供内部计算使用,禁止直接暴露给 UI
|
||||||
|
- **资产坐标系(Asset Space)**
|
||||||
|
- 只用于插件自带资源
|
||||||
|
- 当前明确只有两类:`虚拟物体` 与 `单位圆柱体(参考杆资源)`
|
||||||
|
- 用于描述这些资源文件自身的 `Forward/Up/Side` 轴约定
|
||||||
|
|
||||||
|
禁止继续使用含糊的“本地坐标系”说法,因为它容易混淆:
|
||||||
|
|
||||||
|
- 宿主坐标系
|
||||||
|
- 内部坐标系
|
||||||
|
- 资产坐标系
|
||||||
|
|
||||||
|
后续凡是 UI 输入/输出,必须明确是**宿主坐标系**语义;凡是姿态或几何内部计算,必须明确是**内部坐标系**语义;凡是虚拟物体/单位圆柱体资源朝向,必须明确是**资产坐标系**语义。
|
||||||
- 业务锚点
|
- 业务锚点
|
||||||
|
|
||||||
这层不能偷用世界原点,也不能偷用宿主世界轴。
|
这层不能偷用世界原点,也不能偷用宿主世界轴。
|
||||||
@ -154,24 +179,35 @@ flowchart LR
|
|||||||
|
|
||||||
都必须基于这组定义来解释,不能继续偷用“世界 Z 就是 up”的旧假设。
|
都必须基于这组定义来解释,不能继续偷用“世界 Z 就是 up”的旧假设。
|
||||||
|
|
||||||
### 3.4 模型局部轴约定是独立层
|
### 3.4 资产坐标系是独立层
|
||||||
|
|
||||||
除了宿主坐标系和内部统一坐标系,还必须区分**模型局部轴约定**。
|
除了宿主坐标系和内部统一坐标系,还必须区分**资产坐标系**。
|
||||||
|
|
||||||
|
注意,这一层**不是所有模型都有**。当前项目里只有插件自带资源需要它:
|
||||||
|
|
||||||
|
- 虚拟物体资源
|
||||||
|
- 单位圆柱体参考杆资源
|
||||||
|
|
||||||
这是另一层独立定义,至少包含:
|
这是另一层独立定义,至少包含:
|
||||||
|
|
||||||
- `LocalForwardAxis`
|
- `AssetForwardAxis`
|
||||||
- `LocalUpAxis`
|
- `AssetUpAxis`
|
||||||
|
|
||||||
例如:
|
例如:
|
||||||
|
|
||||||
- 虚拟物体资源通常按程序约定构建,可能是 `Local X = Forward, Local Z = Up`
|
- 虚拟物体资源通常按程序约定构建,可能是 `Asset X = Forward, Asset Z = Up`
|
||||||
- 真实模型如果来自 `Y-up` 项目,则很可能是 `Local X = Forward, Local Y = Up`
|
- 单位圆柱体参考杆资源当前约定为 `Asset X = 杆轴方向, Asset Z = 截面 Up`
|
||||||
|
|
||||||
|
而对于客户真实模型:
|
||||||
|
|
||||||
|
- 不单独引入“资产坐标系”概念
|
||||||
|
- UI 和数据输入输出仍只认宿主坐标系
|
||||||
|
- 内部算法如需统一姿态,先进入 Canonical Space,再叠加业务参考信息
|
||||||
|
|
||||||
这意味着:
|
这意味着:
|
||||||
|
|
||||||
- 即使宿主坐标系转换已经正确
|
- 即使宿主坐标系转换已经正确
|
||||||
- 如果模型局部轴约定没有显式处理
|
- 如果资产坐标系语义没有显式处理
|
||||||
- 动画和姿态仍然会出现“路径对了、通行空间对了、模型自己站歪了”的现象
|
- 动画和姿态仍然会出现“路径对了、通行空间对了、模型自己站歪了”的现象
|
||||||
|
|
||||||
因此,后续姿态系统必须显式区分:
|
因此,后续姿态系统必须显式区分:
|
||||||
@ -179,7 +215,7 @@ flowchart LR
|
|||||||
1. 宿主坐标系定义
|
1. 宿主坐标系定义
|
||||||
2. 内部统一坐标系定义
|
2. 内部统一坐标系定义
|
||||||
3. 业务基准定义
|
3. 业务基准定义
|
||||||
4. 模型局部轴约定
|
4. 资产坐标系约定(仅限虚拟物体和单位圆柱体等插件自带资源)
|
||||||
|
|
||||||
### 3.5 Rail 局部坐标系与动画跟踪点
|
### 3.5 Rail 局部坐标系与动画跟踪点
|
||||||
|
|
||||||
@ -599,6 +635,8 @@ WPF 插件的最终部署,必须依赖完整主项目构建产物,而不应
|
|||||||
- **Navisworks 世界坐标统一视为外部坐标**
|
- **Navisworks 世界坐标统一视为外部坐标**
|
||||||
- **程序内部统一使用 Canonical Space(Z-up)**
|
- **程序内部统一使用 Canonical Space(Z-up)**
|
||||||
- **业务基准点单独建模**
|
- **业务基准点单独建模**
|
||||||
|
- **UI 输入输出统一使用宿主坐标系**
|
||||||
|
- **资产坐标系只属于插件自带资源,不得泛化成“所有模型都有本地轴”**
|
||||||
- **坐标转换只发生在边界层**
|
- **坐标转换只发生在边界层**
|
||||||
|
|
||||||
这是一条更接近业界三维软件成熟做法的路线。
|
这是一条更接近业界三维软件成熟做法的路线。
|
||||||
|
|||||||
@ -2,9 +2,14 @@
|
|||||||
|
|
||||||
## 功能点
|
## 功能点
|
||||||
|
|
||||||
|
### [2026/3/20]
|
||||||
|
|
||||||
|
1. [x] (优化)实现Yup模型兼容
|
||||||
|
2. [x] (功能)运动物体在起点支持三个方向的旋转
|
||||||
|
|
||||||
### [2026/3/18]
|
### [2026/3/18]
|
||||||
|
|
||||||
1. [x](功能)增加轨上路径(角度可倾斜)
|
1. [x] (功能)增加终端仿真需要的轨上和轨下路径(角度可倾斜)
|
||||||
|
|
||||||
### [2026/3/11]
|
### [2026/3/11]
|
||||||
|
|
||||||
|
|||||||
@ -88,12 +88,19 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal enum AnimatedObjectMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
RealObject,
|
||||||
|
VirtualObject
|
||||||
|
}
|
||||||
|
|
||||||
public class PathAnimationManager
|
public class PathAnimationManager
|
||||||
{
|
{
|
||||||
private static PathAnimationManager _instance;
|
private static PathAnimationManager _instance;
|
||||||
private static readonly Dictionary<string, int> _animationHashToRecordId = new Dictionary<string, int>(); // 动画配置哈希到检测记录ID的映射
|
private static readonly Dictionary<string, int> _animationHashToRecordId = new Dictionary<string, int>(); // 动画配置哈希到检测记录ID的映射
|
||||||
private ModelItem _animatedObject;
|
private ModelItem _animatedObject;
|
||||||
private bool _isVirtualObject = false; // 是否使用虚拟物体
|
private AnimatedObjectMode _animatedObjectMode = AnimatedObjectMode.None;
|
||||||
private double _virtualObjectLength = 0; // 虚拟物体长度(模型单位)
|
private double _virtualObjectLength = 0; // 虚拟物体长度(模型单位)
|
||||||
private double _virtualObjectWidth = 0; // 虚拟物体宽度(模型单位)
|
private double _virtualObjectWidth = 0; // 虚拟物体宽度(模型单位)
|
||||||
private double _virtualObjectHeight = 0; // 虚拟物体高度(模型单位)
|
private double _virtualObjectHeight = 0; // 虚拟物体高度(模型单位)
|
||||||
@ -105,7 +112,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
private bool _manualCollisionOverrideEnabled = false;
|
private bool _manualCollisionOverrideEnabled = false;
|
||||||
|
|
||||||
// === 角度修正 ===
|
// === 角度修正 ===
|
||||||
private double _objectRotationCorrection = 0.0; // 物体角度修正值(度,顺时针)
|
private LocalEulerRotationCorrection _objectRotationCorrection = LocalEulerRotationCorrection.Zero; // 物体绕宿主 X/Y/Z 轴的角度修正
|
||||||
|
|
||||||
// === 碰撞排除列表缓存管理(从LogisticsAnimationManager迁移)===
|
// === 碰撞排除列表缓存管理(从LogisticsAnimationManager迁移)===
|
||||||
private ModelItem _currentCachedAnimationObject;
|
private ModelItem _currentCachedAnimationObject;
|
||||||
@ -182,6 +189,12 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
private bool _savedObjectHasCustomRotation = false;
|
private bool _savedObjectHasCustomRotation = false;
|
||||||
private bool _hasSavedObjectState = false;
|
private bool _hasSavedObjectState = false;
|
||||||
|
|
||||||
|
private bool IsVirtualObjectMode => _animatedObjectMode == AnimatedObjectMode.VirtualObject;
|
||||||
|
private bool IsRealObjectMode => _animatedObjectMode == AnimatedObjectMode.RealObject;
|
||||||
|
|
||||||
|
private ModelItem CurrentControlledObject =>
|
||||||
|
IsVirtualObjectMode ? VirtualObjectManager.Instance.CurrentVirtualObject : _animatedObject;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前正在进行动画的对象
|
/// 当前正在进行动画的对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -397,7 +410,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
|
|
||||||
// 🔥 支持虚拟物体的恢复
|
// 🔥 支持虚拟物体的恢复
|
||||||
ModelItem objectToRestore = null;
|
ModelItem objectToRestore = null;
|
||||||
bool isVirtual = _isVirtualObject;
|
bool isVirtual = IsVirtualObjectMode;
|
||||||
|
|
||||||
if (isVirtual)
|
if (isVirtual)
|
||||||
{
|
{
|
||||||
@ -458,7 +471,19 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
public void SetAnimatedObject(ModelItem animatedObject)
|
public void SetAnimatedObject(ModelItem animatedObject)
|
||||||
{
|
{
|
||||||
_animatedObject = animatedObject;
|
_animatedObject = animatedObject;
|
||||||
_isVirtualObject = (animatedObject == null);
|
_animatedObjectMode = animatedObject == null
|
||||||
|
? AnimatedObjectMode.None
|
||||||
|
: AnimatedObjectMode.RealObject;
|
||||||
|
|
||||||
|
if (animatedObject != null)
|
||||||
|
{
|
||||||
|
_originalTransform = animatedObject.Transform;
|
||||||
|
_originalCenter = animatedObject.BoundingBox().Center;
|
||||||
|
_trackedPosition = GetTrackedObjectPosition(animatedObject);
|
||||||
|
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
|
||||||
|
_trackedRotation = _originalTransform.Factor().Rotation;
|
||||||
|
_hasTrackedRotation = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -467,7 +492,9 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetVirtualObjectParameters(bool isVirtualObject, double length, double width, double height)
|
public void SetVirtualObjectParameters(bool isVirtualObject, double length, double width, double height)
|
||||||
{
|
{
|
||||||
_isVirtualObject = isVirtualObject;
|
_animatedObjectMode = isVirtualObject
|
||||||
|
? AnimatedObjectMode.VirtualObject
|
||||||
|
: (_animatedObject != null ? AnimatedObjectMode.RealObject : AnimatedObjectMode.None);
|
||||||
_virtualObjectLength = length; // 模型单位
|
_virtualObjectLength = length; // 模型单位
|
||||||
_virtualObjectWidth = width; // 模型单位
|
_virtualObjectWidth = width; // 模型单位
|
||||||
_virtualObjectHeight = height; // 模型单位
|
_virtualObjectHeight = height; // 模型单位
|
||||||
@ -592,8 +619,8 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
{
|
{
|
||||||
// 🔥 重要:先恢复物体到原始状态(CAD位置和原始朝向),确保每次计算都从相同的状态开始
|
// 🔥 重要:先恢复物体到原始状态(CAD位置和原始朝向),确保每次计算都从相同的状态开始
|
||||||
// 这样可以避免之前旋转导致的包围盒尺寸计算不准确的问题
|
// 这样可以避免之前旋转导致的包围盒尺寸计算不准确的问题
|
||||||
// 注意:也要处理虚拟物体(_isVirtualObject为true时_animatedObject可能为null)
|
// 注意:也要处理虚拟物体(当前控制对象可能不在 _animatedObject 中)
|
||||||
if (_animatedObject != null || _isVirtualObject)
|
if (CurrentControlledObject != null || IsVirtualObjectMode)
|
||||||
{
|
{
|
||||||
RestoreObjectToCADPosition();
|
RestoreObjectToCADPosition();
|
||||||
LogManager.Info($"[移动到起点] 已恢复物体到原始状态, _currentYaw={_currentYaw * 180 / Math.PI:F2}°");
|
LogManager.Info($"[移动到起点] 已恢复物体到原始状态, _currentYaw={_currentYaw * 180 / Math.PI:F2}°");
|
||||||
@ -603,6 +630,12 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
if (animatedObject != null)
|
if (animatedObject != null)
|
||||||
{
|
{
|
||||||
_animatedObject = animatedObject;
|
_animatedObject = animatedObject;
|
||||||
|
bool isVirtualObject =
|
||||||
|
VirtualObjectManager.Instance.IsVirtualObjectActive &&
|
||||||
|
ReferenceEquals(animatedObject, VirtualObjectManager.Instance.CurrentVirtualObject);
|
||||||
|
_animatedObjectMode = isVirtualObject
|
||||||
|
? AnimatedObjectMode.VirtualObject
|
||||||
|
: AnimatedObjectMode.RealObject;
|
||||||
_originalTransform = animatedObject.Transform;
|
_originalTransform = animatedObject.Transform;
|
||||||
_originalCenter = animatedObject.BoundingBox().Center;
|
_originalCenter = animatedObject.BoundingBox().Center;
|
||||||
_trackedPosition = GetTrackedObjectPosition(animatedObject);
|
_trackedPosition = GetTrackedObjectPosition(animatedObject);
|
||||||
@ -611,6 +644,9 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
|
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
|
||||||
_trackedRotation = _originalTransform.Factor().Rotation;
|
_trackedRotation = _originalTransform.Factor().Rotation;
|
||||||
_hasTrackedRotation = true;
|
_hasTrackedRotation = true;
|
||||||
|
|
||||||
|
LogManager.Info(
|
||||||
|
$"[移动到起点] 更新动画对象内部状态: 对象={animatedObject.DisplayName}, 虚拟物体={IsVirtualObjectMode}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathPoints != null)
|
if (pathPoints != null)
|
||||||
@ -629,7 +665,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
double pathDirectionYaw = Math.Atan2(_pathPoints[1].Y - _pathPoints[0].Y, _pathPoints[1].X - _pathPoints[0].X);
|
double pathDirectionYaw = Math.Atan2(_pathPoints[1].Y - _pathPoints[0].Y, _pathPoints[1].X - _pathPoints[0].X);
|
||||||
double yaw;
|
double yaw;
|
||||||
|
|
||||||
LogManager.Info($"[移动到起点] 路径方向yaw: {pathDirectionYaw * 180 / Math.PI:F2}度, 角度修正: {_objectRotationCorrection:F1}度");
|
LogManager.Info($"[移动到起点] 路径方向yaw: {pathDirectionYaw * 180 / Math.PI:F2}度, 角度修正: {_objectRotationCorrection}");
|
||||||
|
|
||||||
// 根据路径类型调整朝向
|
// 根据路径类型调整朝向
|
||||||
if (_route.PathType == PathType.Hoisting)
|
if (_route.PathType == PathType.Hoisting)
|
||||||
@ -646,12 +682,12 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
LogManager.Debug($"[移动到起点] 地面/空轨路径使用路径方向: {yaw * 180 / Math.PI:F2}度");
|
LogManager.Debug($"[移动到起点] 地面/空轨路径使用路径方向: {yaw * 180 / Math.PI:F2}度");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用角度修正值(顺时针,转换为弧度)
|
// 旧 yaw 链仅保留给未进入完整三维姿态的遗留路径。
|
||||||
if (_objectRotationCorrection != 0.0)
|
if (!_objectRotationCorrection.IsZero)
|
||||||
{
|
{
|
||||||
double correctionRad = _objectRotationCorrection * Math.PI / 180.0;
|
double correctionRad = _objectRotationCorrection.ZDegrees * Math.PI / 180.0;
|
||||||
yaw += correctionRad;
|
yaw += correctionRad;
|
||||||
LogManager.Debug($"[移动到起点] 应用角度修正: {_objectRotationCorrection:F1}°, 修正后yaw: {yaw * 180 / Math.PI:F2}度");
|
LogManager.Debug($"[移动到起点] 应用遗留Z轴修正: {_objectRotationCorrection.ZDegrees:F1}°, 修正后yaw: {yaw * 180 / Math.PI:F2}度");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据路径类型调整起点位置
|
// 根据路径类型调整起点位置
|
||||||
@ -1147,9 +1183,9 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
LogManager.Debug($"被撞物体位置:{GetObjectPosition(collider).X:F2},{GetObjectPosition(collider).Y:F2},{GetObjectPosition(collider).Z:F2}");
|
LogManager.Debug($"被撞物体位置:{GetObjectPosition(collider).X:F2},{GetObjectPosition(collider).Y:F2},{GetObjectPosition(collider).Z:F2}");
|
||||||
|
|
||||||
double actualYawRadians = yawRadians;
|
double actualYawRadians = yawRadians;
|
||||||
if (!frame.HasCustomRotation && _objectRotationCorrection != 0.0)
|
if (!frame.HasCustomRotation && !_objectRotationCorrection.IsZero)
|
||||||
{
|
{
|
||||||
actualYawRadians += _objectRotationCorrection * Math.PI / 180.0;
|
actualYawRadians += _objectRotationCorrection.ZDegrees * Math.PI / 180.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var collisionResult = new CollisionResult
|
var collisionResult = new CollisionResult
|
||||||
@ -1548,7 +1584,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
{
|
{
|
||||||
var firstFrame = _animationFrames[0];
|
var firstFrame = _animationFrames[0];
|
||||||
|
|
||||||
LogManager.Debug($"[动画开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, _isVirtualObject={_isVirtualObject}");
|
LogManager.Debug($"[动画开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, 模式={_animatedObjectMode}");
|
||||||
|
|
||||||
// 🔥 确保物体在起点位置和朝向
|
// 🔥 确保物体在起点位置和朝向
|
||||||
// 注意:MoveObjectToPathStart 已在 StartAnimation 中调用,这里只是日志记录
|
// 注意:MoveObjectToPathStart 已在 StartAnimation 中调用,这里只是日志记录
|
||||||
@ -1829,7 +1865,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
_detectionTolerance,
|
_detectionTolerance,
|
||||||
_currentRouteId,
|
_currentRouteId,
|
||||||
_animatedObject,
|
_animatedObject,
|
||||||
_isVirtualObject,
|
IsVirtualObjectMode,
|
||||||
_animationFrameRate,
|
_animationFrameRate,
|
||||||
_animationDuration,
|
_animationDuration,
|
||||||
_virtualObjectLength,
|
_virtualObjectLength,
|
||||||
@ -2411,8 +2447,8 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool isRailRealObject = _route?.PathType == PathType.Rail && !_isVirtualObject && _animatedObject != null;
|
bool isRailRealObject = _route?.PathType == PathType.Rail && IsRealObjectMode && _animatedObject != null;
|
||||||
if (_isVirtualObject)
|
if (IsVirtualObjectMode)
|
||||||
{
|
{
|
||||||
VirtualObjectManager.Instance.MoveVirtualObject(newPosition, newRotation);
|
VirtualObjectManager.Instance.MoveVirtualObject(newPosition, newRotation);
|
||||||
}
|
}
|
||||||
@ -2475,7 +2511,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
}
|
}
|
||||||
else if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
|
else if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
|
||||||
{
|
{
|
||||||
LogHostActualPoseAxes(_isVirtualObject ? VirtualObjectManager.Instance.CurrentVirtualObject : _animatedObject, "[平面姿态应用后宿主姿态]", false);
|
LogHostActualPoseAxes(CurrentControlledObject, "[平面姿态应用后宿主姿态]", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -2688,7 +2724,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isVirtualObject)
|
if (IsVirtualObjectMode)
|
||||||
{
|
{
|
||||||
return VirtualObjectManager.Instance.IsVirtualObjectActive &&
|
return VirtualObjectManager.Instance.IsVirtualObjectActive &&
|
||||||
ReferenceEquals(VirtualObjectManager.Instance.CurrentVirtualObject, obj);
|
ReferenceEquals(VirtualObjectManager.Instance.CurrentVirtualObject, obj);
|
||||||
@ -2723,7 +2759,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var originalAnimatedObject = _animatedObject;
|
var originalAnimatedObject = _animatedObject;
|
||||||
if (!_isVirtualObject)
|
if (!IsVirtualObjectMode)
|
||||||
{
|
{
|
||||||
_animatedObject = obj;
|
_animatedObject = obj;
|
||||||
}
|
}
|
||||||
@ -3243,7 +3279,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double GetAnimatedObjectHeight()
|
private double GetAnimatedObjectHeight()
|
||||||
{
|
{
|
||||||
if (_isVirtualObject)
|
if (IsVirtualObjectMode)
|
||||||
{
|
{
|
||||||
return _virtualObjectHeight;
|
return _virtualObjectHeight;
|
||||||
}
|
}
|
||||||
@ -3334,7 +3370,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
|
|
||||||
_pathName = pathName;
|
_pathName = pathName;
|
||||||
_currentRouteId = routeId;
|
_currentRouteId = routeId;
|
||||||
_isVirtualObject = isVirtualObject; // 设置是否使用虚拟物体
|
_animatedObjectMode = isVirtualObject ? AnimatedObjectMode.VirtualObject : AnimatedObjectMode.RealObject;
|
||||||
_virtualObjectLength = virtualObjectLength; // 模型单位
|
_virtualObjectLength = virtualObjectLength; // 模型单位
|
||||||
_virtualObjectWidth = virtualObjectWidth; // 模型单位
|
_virtualObjectWidth = virtualObjectWidth; // 模型单位
|
||||||
_virtualObjectHeight = virtualObjectHeight; // 模型单位
|
_virtualObjectHeight = virtualObjectHeight; // 模型单位
|
||||||
@ -3351,12 +3387,12 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
|
|
||||||
// 保持当前的 _currentYaw(因为物体可能已经被 MoveObjectToPathStart 旋转)
|
// 保持当前的 _currentYaw(因为物体可能已经被 MoveObjectToPathStart 旋转)
|
||||||
// 不要从 Transform 中提取,因为 Transform 返回的是原始值,不是当前值
|
// 不要从 Transform 中提取,因为 Transform 返回的是原始值,不是当前值
|
||||||
LogManager.Debug($"[CreateAnimation] 保持_currentYaw={_currentYaw * 180 / Math.PI:F2}度不变, _isVirtualObject={_isVirtualObject}");
|
LogManager.Debug($"[CreateAnimation] 保持_currentYaw={_currentYaw * 180 / Math.PI:F2}度不变, 模式={_animatedObjectMode}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置动画参数并预计算动画帧
|
// 设置动画参数并预计算动画帧
|
||||||
// 注意:物体应该在调用CreateAnimation之前已经通过MoveObjectToPathStart旋转到起点
|
// 注意:物体应该在调用CreateAnimation之前已经通过MoveObjectToPathStart旋转到起点
|
||||||
LogManager.Debug($"[CreateAnimation开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, _isVirtualObject={_isVirtualObject}");
|
LogManager.Debug($"[CreateAnimation开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, 模式={_animatedObjectMode}");
|
||||||
SetupAnimation(animatedObject, durationSeconds, _route);
|
SetupAnimation(animatedObject, durationSeconds, _route);
|
||||||
LogManager.Debug($"[CreateAnimation结束] _currentYaw之后={_currentYaw * 180 / Math.PI:F2}度");
|
LogManager.Debug($"[CreateAnimation结束] _currentYaw之后={_currentYaw * 180 / Math.PI:F2}度");
|
||||||
// 设置动画状态为Ready(动画已生成,可以播放)
|
// 设置动画状态为Ready(动画已生成,可以播放)
|
||||||
@ -3405,7 +3441,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
|
|
||||||
private ModelAxisConvention GetCurrentRailModelAxisConvention()
|
private ModelAxisConvention GetCurrentRailModelAxisConvention()
|
||||||
{
|
{
|
||||||
if (_isVirtualObject)
|
if (IsVirtualObjectMode)
|
||||||
{
|
{
|
||||||
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||||
}
|
}
|
||||||
@ -3413,14 +3449,14 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||||
var convention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
var convention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
||||||
LogManager.Info(
|
LogManager.Info(
|
||||||
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={_isVirtualObject}, " +
|
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={IsVirtualObjectMode}, " +
|
||||||
$"Forward={convention.ForwardAxis}, Up={convention.UpAxis}");
|
$"Forward={convention.ForwardAxis}, Up={convention.UpAxis}");
|
||||||
return convention;
|
return convention;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelAxisConvention GetCurrentModelAxisConvention()
|
private ModelAxisConvention GetCurrentModelAxisConvention()
|
||||||
{
|
{
|
||||||
if (_isVirtualObject)
|
if (IsVirtualObjectMode)
|
||||||
{
|
{
|
||||||
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||||
}
|
}
|
||||||
@ -3487,6 +3523,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
Vector3 canonicalPrevious = ToNumerics(adapter.ToCanonicalPoint(previousPoint));
|
Vector3 canonicalPrevious = ToNumerics(adapter.ToCanonicalPoint(previousPoint));
|
||||||
Vector3 canonicalCurrent = ToNumerics(adapter.ToCanonicalPoint(currentPoint));
|
Vector3 canonicalCurrent = ToNumerics(adapter.ToCanonicalPoint(currentPoint));
|
||||||
Vector3 canonicalNext = ToNumerics(adapter.ToCanonicalPoint(nextPoint));
|
Vector3 canonicalNext = ToNumerics(adapter.ToCanonicalPoint(nextPoint));
|
||||||
|
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection);
|
||||||
|
|
||||||
Vector3 forward = canonicalNext - canonicalPrevious;
|
Vector3 forward = canonicalNext - canonicalPrevious;
|
||||||
if (forward.LengthSquared() < 1e-6f)
|
if (forward.LengthSquared() < 1e-6f)
|
||||||
@ -3494,12 +3531,11 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
forward = canonicalNext - canonicalCurrent;
|
forward = canonicalNext - canonicalCurrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
forward = ApplyPlanarRotationCorrection(forward);
|
|
||||||
|
|
||||||
if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
||||||
forward,
|
forward,
|
||||||
HostCoordinateAdapter.CanonicalUpVector3,
|
HostCoordinateAdapter.CanonicalUpVector3,
|
||||||
convention,
|
convention,
|
||||||
|
correctionQuaternion,
|
||||||
out var quaternion))
|
out var quaternion))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -3516,12 +3552,12 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||||
var convention = GetCurrentModelAxisConvention();
|
var convention = GetCurrentModelAxisConvention();
|
||||||
Vector3 canonicalForward = adapter.ToCanonicalVector3(new Vector3((float)hostForward.X, (float)hostForward.Y, (float)hostForward.Z));
|
Vector3 canonicalForward = adapter.ToCanonicalVector3(new Vector3((float)hostForward.X, (float)hostForward.Y, (float)hostForward.Z));
|
||||||
canonicalForward = ApplyPlanarRotationCorrection(canonicalForward);
|
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection);
|
||||||
|
|
||||||
if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
||||||
canonicalForward,
|
canonicalForward,
|
||||||
HostCoordinateAdapter.CanonicalUpVector3,
|
HostCoordinateAdapter.CanonicalUpVector3,
|
||||||
convention,
|
convention,
|
||||||
|
correctionQuaternion,
|
||||||
out var quaternion))
|
out var quaternion))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -3531,25 +3567,10 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector3 ApplyPlanarRotationCorrection(Vector3 canonicalForward)
|
|
||||||
{
|
|
||||||
if (Math.Abs(_objectRotationCorrection) < 1e-9)
|
|
||||||
{
|
|
||||||
return canonicalForward;
|
|
||||||
}
|
|
||||||
|
|
||||||
float correctionRadians = (float)(_objectRotationCorrection * Math.PI / 180.0);
|
|
||||||
Quaternion correction = Quaternion.CreateFromAxisAngle(
|
|
||||||
Vector3.Normalize(HostCoordinateAdapter.CanonicalUpVector3),
|
|
||||||
correctionRadians);
|
|
||||||
return Vector3.Transform(canonicalForward, correction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置物体角度修正值(度,顺时针)
|
/// 设置物体绕宿主 X/Y/Z 轴的角度修正
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rotationCorrection">角度修正值(度)</param>
|
public void SetObjectRotationCorrection(LocalEulerRotationCorrection rotationCorrection)
|
||||||
public void SetObjectRotationCorrection(double rotationCorrection)
|
|
||||||
{
|
{
|
||||||
_objectRotationCorrection = rotationCorrection;
|
_objectRotationCorrection = rotationCorrection;
|
||||||
|
|
||||||
@ -3560,7 +3581,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
{
|
{
|
||||||
// 重新计算并应用朝向
|
// 重新计算并应用朝向
|
||||||
MoveObjectToPathStart();
|
MoveObjectToPathStart();
|
||||||
LogManager.Info($"[角度修正] 物体角度已更新: {_objectRotationCorrection:F1}°");
|
LogManager.Info($"[角度修正] 物体角度已更新: {_objectRotationCorrection}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -3570,13 +3591,36 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 直接设置物体角度修正值(不触发物体旋转)
|
/// 保留给旧单轴调用方:按当前宿主 up 轴映射成三轴角度修正。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rotationCorrection">角度修正值(度)</param>
|
public void SetObjectRotationCorrection(double rotationCorrection)
|
||||||
public void SetObjectRotationCorrectionDirect(double rotationCorrection)
|
{
|
||||||
|
SetObjectRotationCorrection(CreateLegacyUpAxisCorrection(rotationCorrection));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 直接设置物体绕宿主 X/Y/Z 轴的角度修正(不触发物体旋转)
|
||||||
|
/// </summary>
|
||||||
|
public void SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection rotationCorrection)
|
||||||
{
|
{
|
||||||
_objectRotationCorrection = rotationCorrection;
|
_objectRotationCorrection = rotationCorrection;
|
||||||
LogManager.Debug($"[角度修正] 直接设置角度修正值: {_objectRotationCorrection:F1}°(不触发旋转)");
|
LogManager.Debug($"[角度修正] 直接设置角度修正值: {_objectRotationCorrection}(不触发旋转)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保留给旧单轴调用方:按当前宿主 up 轴映射成三轴角度修正。
|
||||||
|
/// </summary>
|
||||||
|
public void SetObjectRotationCorrectionDirect(double rotationCorrection)
|
||||||
|
{
|
||||||
|
SetObjectRotationCorrectionDirect(CreateLegacyUpAxisCorrection(rotationCorrection));
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalEulerRotationCorrection CreateLegacyUpAxisCorrection(double rotationCorrection)
|
||||||
|
{
|
||||||
|
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||||
|
return adapter.HostType == CoordinateSystemType.YUp
|
||||||
|
? new LocalEulerRotationCorrection(0.0, rotationCorrection, 0.0)
|
||||||
|
: new LocalEulerRotationCorrection(0.0, 0.0, rotationCorrection);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 动画实现方法
|
#region 动画实现方法
|
||||||
@ -3732,11 +3776,6 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
}
|
}
|
||||||
|
|
||||||
double actualYaw = frameData.YawRadians;
|
double actualYaw = frameData.YawRadians;
|
||||||
if (_objectRotationCorrection != 0.0)
|
|
||||||
{
|
|
||||||
double correctionRad = _objectRotationCorrection * Math.PI / 180.0;
|
|
||||||
actualYaw += correctionRad;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frameData.HasCustomRotation)
|
if (frameData.HasCustomRotation)
|
||||||
{
|
{
|
||||||
@ -3858,7 +3897,7 @@ namespace NavisworksTransport.Core.Animation
|
|||||||
sb.Append("|");
|
sb.Append("|");
|
||||||
|
|
||||||
// 包含角度修正(确保角度修正改变时重新检测)
|
// 包含角度修正(确保角度修正改变时重新检测)
|
||||||
sb.Append($"RotationCorrection:{_objectRotationCorrection:F2}deg");
|
sb.Append($"RotationCorrection:{_objectRotationCorrection}");
|
||||||
sb.Append("|");
|
sb.Append("|");
|
||||||
|
|
||||||
// 包含手工检测对象列表(确保手工指定模式的目标变化时重新检测)
|
// 包含手工检测对象列表(确保手工指定模式的目标变化时重新检测)
|
||||||
|
|||||||
@ -1886,6 +1886,19 @@ namespace NavisworksTransport
|
|||||||
{
|
{
|
||||||
var startPoint = sortedPoints[i];
|
var startPoint = sortedPoints[i];
|
||||||
var endPoint = sortedPoints[i + 1];
|
var endPoint = sortedPoints[i + 1];
|
||||||
|
double segmentDx = endPoint.Position.X - startPoint.Position.X;
|
||||||
|
double segmentDy = endPoint.Position.Y - startPoint.Position.Y;
|
||||||
|
double segmentDz = endPoint.Position.Z - startPoint.Position.Z;
|
||||||
|
double segmentLength = Math.Sqrt(
|
||||||
|
segmentDx * segmentDx +
|
||||||
|
segmentDy * segmentDy +
|
||||||
|
segmentDz * segmentDz);
|
||||||
|
|
||||||
|
if (segmentLength < 0.001)
|
||||||
|
{
|
||||||
|
LogManager.Debug($"[路径渲染] 跳过零长度路径段: 索引={i}, 路径={visualization.PathRoute?.Name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 对于吊装路径,判断是否是垂直段和起吊/下降段
|
// 对于吊装路径,判断是否是垂直段和起吊/下降段
|
||||||
bool isVerticalSegment = false;
|
bool isVerticalSegment = false;
|
||||||
@ -1911,11 +1924,7 @@ namespace NavisworksTransport
|
|||||||
|
|
||||||
// 沿宿主 up 判断垂直段,避免把世界 Z 误当成向上轴
|
// 沿宿主 up 判断垂直段,避免把世界 Z 误当成向上轴
|
||||||
var hostUp = GetHostUpVector();
|
var hostUp = GetHostUpVector();
|
||||||
double dx = endPoint.Position.X - startPoint.Position.X;
|
double upDelta = segmentDx * hostUp.X + segmentDy * hostUp.Y + segmentDz * hostUp.Z;
|
||||||
double dy = endPoint.Position.Y - startPoint.Position.Y;
|
|
||||||
double dz = endPoint.Position.Z - startPoint.Position.Z;
|
|
||||||
double upDelta = dx * hostUp.X + dy * hostUp.Y + dz * hostUp.Z;
|
|
||||||
double segmentLength = Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
|
||||||
double horizontalDist = Math.Sqrt(Math.Max(0.0, segmentLength * segmentLength - upDelta * upDelta));
|
double horizontalDist = Math.Sqrt(Math.Max(0.0, segmentLength * segmentLength - upDelta * upDelta));
|
||||||
// 如果沿宿主 up 的变化占主导(垂直段),或者是第一个/最后一个线段
|
// 如果沿宿主 up 的变化占主导(垂直段),或者是第一个/最后一个线段
|
||||||
bool isZDominant = Math.Abs(upDelta) > horizontalDist * 2.0;
|
bool isZDominant = Math.Abs(upDelta) > horizontalDist * 2.0;
|
||||||
|
|||||||
@ -213,7 +213,9 @@ namespace NavisworksTransport.Core
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
LogVirtualObjectGeometry("[虚拟物体姿态] 应用前");
|
LogVirtualObjectGeometry("[虚拟物体姿态] 应用前");
|
||||||
ModelItemTransformHelper.MoveItemToPositionAndRotation(_virtualObjectModelItem, position, rotation);
|
// 虚拟物体当前尺寸是通过永久变换中的缩放实现的。
|
||||||
|
// 应用完整姿态时必须保留当前缩放,否则会破坏虚拟物体的资产姿态和几何语义。
|
||||||
|
ModelItemTransformHelper.MoveItemToPositionAndRotationWithCurrentScale(_virtualObjectModelItem, position, rotation);
|
||||||
var actualBounds = _virtualObjectModelItem.BoundingBox();
|
var actualBounds = _virtualObjectModelItem.BoundingBox();
|
||||||
Point3D actualCenter = actualBounds?.Center ?? new Point3D(0, 0, 0);
|
Point3D actualCenter = actualBounds?.Center ?? new Point3D(0, 0, 0);
|
||||||
LogManager.Info(
|
LogManager.Info(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@ -355,7 +356,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
private double _safetyMarginInMeters; // 检测间隙(米),从路径编辑同步
|
private double _safetyMarginInMeters; // 检测间隙(米),从路径编辑同步
|
||||||
|
|
||||||
// 角度修正相关字段
|
// 角度修正相关字段
|
||||||
private double _objectRotationCorrection; // 物体角度修正值(度,顺时针)
|
private LocalEulerRotationCorrection _objectRotationCorrection = LocalEulerRotationCorrection.Zero; // 物体绕宿主 X/Y/Z 轴的角度修正
|
||||||
|
|
||||||
// 移动物体原始尺寸(米,在选择物体时保存)
|
// 移动物体原始尺寸(米,在选择物体时保存)
|
||||||
private double _objectOriginalLength; // 物体原始长度(X方向)
|
private double _objectOriginalLength; // 物体原始长度(X方向)
|
||||||
@ -708,8 +709,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
VirtualObjectLengthInMeters, VirtualObjectWidthInMeters, VirtualObjectHeightInMeters);
|
VirtualObjectLengthInMeters, VirtualObjectWidthInMeters, VirtualObjectHeightInMeters);
|
||||||
|
|
||||||
// 重置角度修正值(虚拟物体重新选择时重置)
|
// 重置角度修正值(虚拟物体重新选择时重置)
|
||||||
ObjectRotationCorrection = 0.0;
|
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||||
LogManager.Debug($"[切换虚拟物体] 已重置角度修正值为0度");
|
LogManager.Debug("[切换虚拟物体] 已重置角度修正值为0");
|
||||||
|
|
||||||
// 当切换到虚拟物体模式时,根据路径类型打开通行空间可视化
|
// 当切换到虚拟物体模式时,根据路径类型打开通行空间可视化
|
||||||
UpdatePassageSpaceVisualization();
|
UpdatePassageSpaceVisualization();
|
||||||
@ -792,9 +793,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 物体角度修正值(度,顺时针)
|
/// 物体绕宿主 X/Y/Z 轴的角度修正。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double ObjectRotationCorrection
|
public LocalEulerRotationCorrection ObjectRotationCorrection
|
||||||
{
|
{
|
||||||
get => _objectRotationCorrection;
|
get => _objectRotationCorrection;
|
||||||
set
|
set
|
||||||
@ -1261,8 +1262,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
SelectAnimatedObjectCommand = new RelayCommand(() =>
|
SelectAnimatedObjectCommand = new RelayCommand(() =>
|
||||||
{
|
{
|
||||||
// 重置角度修正值(每个物体的旋转独立)
|
// 重置角度修正值(每个物体的旋转独立)
|
||||||
ObjectRotationCorrection = 0.0;
|
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||||
LogManager.Debug($"[选择物体] 已重置角度修正值为0度");
|
LogManager.Debug("[选择物体] 已重置角度修正值为0");
|
||||||
ExecuteSelectAnimatedObject();
|
ExecuteSelectAnimatedObject();
|
||||||
});
|
});
|
||||||
ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
|
ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
|
||||||
@ -1714,19 +1715,30 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
|
|
||||||
// 3. 设置新物体(会触发UpdatePassageSpaceVisualization)
|
// 3. 设置新物体(会触发UpdatePassageSpaceVisualization)
|
||||||
SelectedAnimatedObject = newObject;
|
SelectedAnimatedObject = newObject;
|
||||||
|
_pathAnimationManager?.SetAnimatedObject(newObject);
|
||||||
LogManager.Info($"已选择移动物体: {SelectedAnimatedObject.DisplayName}");
|
LogManager.Info($"已选择移动物体: {SelectedAnimatedObject.DisplayName}");
|
||||||
|
|
||||||
|
// 选择实体物体意味着切换到实体模式,避免后续起点同步仍走虚拟物体分支。
|
||||||
|
if (UseVirtualObject)
|
||||||
|
{
|
||||||
|
UseVirtualObject = false;
|
||||||
|
LogManager.Debug("[选择物体] 已切换到实体物体模式");
|
||||||
|
}
|
||||||
|
|
||||||
// 只有选择不同的物体时,才重置角度修正值
|
// 只有选择不同的物体时,才重置角度修正值
|
||||||
if (!isSameObject)
|
if (!isSameObject)
|
||||||
{
|
{
|
||||||
// 重置 ViewModel 中的角度修正值(会自动同步到 PathAnimationManager)
|
// 重置 ViewModel 中的角度修正值(会自动同步到 PathAnimationManager)
|
||||||
ObjectRotationCorrection = 0.0;
|
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||||
LogManager.Debug($"[选择物体] 已重置角度修正值为0度(新物体)");
|
LogManager.Debug("[选择物体] 已重置角度修正值为0(新物体)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LogManager.Debug($"[选择物体] 保持当前角度修正值(同一物体)");
|
LogManager.Debug($"[选择物体] 保持当前角度修正值(同一物体)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显式同步一次到当前路径起点,避免依赖 setter 分支导致真实物体停留在 CAD 原位。
|
||||||
|
MoveAnimatedObjectToPathStart();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -2151,8 +2163,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 重置角度修正值为0
|
// 2. 重置角度修正值为0
|
||||||
ObjectRotationCorrection = 0.0;
|
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||||
LogManager.Debug("[清除物体] 已重置角度修正值为0度");
|
LogManager.Debug("[清除物体] 已重置角度修正值为0");
|
||||||
|
|
||||||
// 3. 重置属性
|
// 3. 重置属性
|
||||||
SelectedAnimatedObject = null;
|
SelectedAnimatedObject = null;
|
||||||
@ -2182,9 +2194,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
var dialog = new Views.EditRotationWindow(_objectRotationCorrection);
|
var dialog = new Views.EditRotationWindow(_objectRotationCorrection);
|
||||||
if (dialog.ShowDialog() == true)
|
if (dialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
ObjectRotationCorrection = dialog.RotationAngle;
|
ObjectRotationCorrection = dialog.RotationCorrection;
|
||||||
LogManager.Info($"物体角度修正已更新: {_objectRotationCorrection:F1}°");
|
LogManager.Info($"物体角度修正已更新: {_objectRotationCorrection}");
|
||||||
UpdateMainStatus($"物体角度修正: {_objectRotationCorrection:F1}°");
|
UpdateMainStatus($"物体角度修正: {_objectRotationCorrection}");
|
||||||
|
|
||||||
// 应用角度修正到物体
|
// 应用角度修正到物体
|
||||||
UpdateObjectRotation();
|
UpdateObjectRotation();
|
||||||
@ -2211,7 +2223,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
{
|
{
|
||||||
// 将角度修正传递给动画管理器
|
// 将角度修正传递给动画管理器
|
||||||
_pathAnimationManager.SetObjectRotationCorrection(_objectRotationCorrection);
|
_pathAnimationManager.SetObjectRotationCorrection(_objectRotationCorrection);
|
||||||
LogManager.Debug($"[角度修正] 已更新物体旋转: {_objectRotationCorrection:F1}°");
|
LogManager.Debug($"[角度修正] 已更新物体旋转: {_objectRotationCorrection}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -3191,7 +3203,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
IsVirtualObject = UseVirtualObject,
|
IsVirtualObject = UseVirtualObject,
|
||||||
AnimatedObjectName = UseVirtualObject ? "虚拟物体" : SelectedAnimatedObject?.DisplayName,
|
AnimatedObjectName = UseVirtualObject ? "虚拟物体" : SelectedAnimatedObject?.DisplayName,
|
||||||
DetectAllObjects = !IsManualCollisionTargetEnabled,
|
DetectAllObjects = !IsManualCollisionTargetEnabled,
|
||||||
ObjectRotationCorrection = _objectRotationCorrection,
|
ObjectRotationCorrection = _objectRotationCorrection.ZDegrees,
|
||||||
Description = $"{scenarioName}于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
|
Description = $"{scenarioName}于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
|
||||||
TestName = testName,
|
TestName = testName,
|
||||||
TestTime = DateTime.Now,
|
TestTime = DateTime.Now,
|
||||||
@ -3356,7 +3368,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
UseVirtualObject ? VirtualObjectLengthInMeters * factor : (double?)null, // 米 → 模型单位
|
UseVirtualObject ? VirtualObjectLengthInMeters * factor : (double?)null, // 米 → 模型单位
|
||||||
UseVirtualObject ? VirtualObjectWidthInMeters * factor : (double?)null,
|
UseVirtualObject ? VirtualObjectWidthInMeters * factor : (double?)null,
|
||||||
UseVirtualObject ? VirtualObjectHeightInMeters * factor : (double?)null,
|
UseVirtualObject ? VirtualObjectHeightInMeters * factor : (double?)null,
|
||||||
_objectRotationCorrection,
|
_objectRotationCorrection.ZDegrees,
|
||||||
IsManualCollisionTargetEnabled
|
IsManualCollisionTargetEnabled
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -4302,54 +4314,56 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 计算旋转后的有效宽度和长度
|
/// 计算本地三轴预旋转后的有效尺寸
|
||||||
/// 返回值为模型单位(与Navisworks API一致)
|
/// 返回值为模型单位(与Navisworks API一致)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>(有效宽度, 有效长度) - 模型单位</returns>
|
/// <returns>(沿路径, 垂直路径, 法线) - 模型单位</returns>
|
||||||
private (double effectiveWidth, double effectiveLength) CalculateRotatedDimensions()
|
private (double effectiveAlongPath, double effectiveAcrossPath, double effectiveNormalToPath) CalculateRotatedDimensions()
|
||||||
{
|
{
|
||||||
double baseLengthMeters, baseWidthMeters;
|
double forwardSize;
|
||||||
|
double sideSize;
|
||||||
|
double upSize;
|
||||||
|
ModelAxisConvention axisConvention;
|
||||||
|
|
||||||
if (UseVirtualObject)
|
if (UseVirtualObject)
|
||||||
{
|
{
|
||||||
baseLengthMeters = VirtualObjectLengthInMeters;
|
double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||||||
baseWidthMeters = VirtualObjectWidthInMeters;
|
forwardSize = VirtualObjectLengthInMeters * metersToUnits;
|
||||||
|
sideSize = VirtualObjectWidthInMeters * metersToUnits;
|
||||||
|
upSize = VirtualObjectHeightInMeters * metersToUnits;
|
||||||
|
axisConvention = ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||||
}
|
}
|
||||||
else if (SelectedAnimatedObject != null)
|
else if (SelectedAnimatedObject != null)
|
||||||
{
|
{
|
||||||
// 使用保存的原始尺寸(米),避免物体旋转后包围盒尺寸变化
|
double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||||||
baseLengthMeters = _objectOriginalLength;
|
forwardSize = _objectOriginalLength * metersToUnits;
|
||||||
baseWidthMeters = _objectOriginalWidth;
|
sideSize = _objectOriginalWidth * metersToUnits;
|
||||||
|
upSize = _objectOriginalHeight * metersToUnits;
|
||||||
|
|
||||||
|
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||||
|
axisConvention = CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail
|
||||||
|
? ModelAxisConvention.CreateRailAssetConvention()
|
||||||
|
: ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (0, 0);
|
return (0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为模型单位
|
Quaternion correctionQuaternion = CoordinateSystemManager.Instance.CreateHostAdapter()
|
||||||
var metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
.CreateCanonicalRotationCorrection(_objectRotationCorrection);
|
||||||
double baseLength = baseLengthMeters * metersToUnits;
|
var result = RotatedObjectExtentHelper.CalculateProjectedSemanticExtents(
|
||||||
double baseWidth = baseWidthMeters * metersToUnits;
|
axisConvention,
|
||||||
|
forwardSize,
|
||||||
|
sideSize,
|
||||||
|
upSize,
|
||||||
|
correctionQuaternion);
|
||||||
|
double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor();
|
||||||
|
LogManager.Debug(
|
||||||
|
$"[角度修正] 旋转后尺寸: 原始({forwardSize * unitsToMeters:F2}m×{sideSize * unitsToMeters:F2}m×{upSize * unitsToMeters:F2}m) -> " +
|
||||||
|
$"有效({result.forwardExtent * unitsToMeters:F2}m×{result.sideExtent * unitsToMeters:F2}m×{result.upExtent * unitsToMeters:F2}m), 角度={_objectRotationCorrection}");
|
||||||
|
|
||||||
// 如果没有角度修正,直接返回原始尺寸
|
return result;
|
||||||
if (_objectRotationCorrection == 0.0)
|
|
||||||
{
|
|
||||||
return (baseWidth, baseLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算旋转后的有效尺寸(投影)
|
|
||||||
double rotationRad = _objectRotationCorrection * Math.PI / 180.0;
|
|
||||||
double cos = Math.Abs(Math.Cos(rotationRad));
|
|
||||||
double sin = Math.Abs(Math.Sin(rotationRad));
|
|
||||||
|
|
||||||
// 旋转后的宽度 = |宽度*cos| + |长度*sin|
|
|
||||||
double effectiveWidth = baseWidth * cos + baseLength * sin;
|
|
||||||
// 旋转后的长度 = |长度*cos| + |宽度*sin|
|
|
||||||
double effectiveLength = baseLength * cos + baseWidth * sin;
|
|
||||||
|
|
||||||
LogManager.Debug($"[角度修正] 旋转后尺寸: 原始({baseLengthMeters:F2}m×{baseWidthMeters:F2}m) -> 有效({effectiveLength / metersToUnits:F2}m×{effectiveWidth / metersToUnits:F2}m), 角度={_objectRotationCorrection:F1}°");
|
|
||||||
|
|
||||||
return (effectiveWidth, effectiveLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -4368,7 +4382,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 计算旋转后的有效尺寸(考虑角度修正)
|
// 计算旋转后的有效尺寸(考虑角度修正)
|
||||||
(double effectiveWidth, double effectiveLength) = CalculateRotatedDimensions();
|
(double effectiveLength, double effectiveWidth, double effectiveHeight) = CalculateRotatedDimensions();
|
||||||
|
|
||||||
var renderPlugin = PathPointRenderPlugin.Instance;
|
var renderPlugin = PathPointRenderPlugin.Instance;
|
||||||
if (renderPlugin == null)
|
if (renderPlugin == null)
|
||||||
@ -4383,8 +4397,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
// 将安全间隙从米转换为模型单位
|
// 将安全间隙从米转换为模型单位
|
||||||
var metersToUnitsPassage = UnitsConverter.GetMetersToUnitsConversionFactor();
|
var metersToUnitsPassage = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||||||
double safetyMargin = _safetyMarginInMeters * metersToUnitsPassage;
|
double safetyMargin = _safetyMarginInMeters * metersToUnitsPassage;
|
||||||
double virtualObjectHeight = VirtualObjectHeightInMeters * metersToUnitsPassage;
|
|
||||||
|
|
||||||
if (UseVirtualObject)
|
if (UseVirtualObject)
|
||||||
{
|
{
|
||||||
// 虚拟物体的xyz定义:X方向 = 长度(前进方向),Y方向 = 宽度(侧面),Z方向 = 高度
|
// 虚拟物体的xyz定义:X方向 = 长度(前进方向),Y方向 = 宽度(侧面),Z方向 = 高度
|
||||||
@ -4392,11 +4404,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
{
|
{
|
||||||
// 空中路径(空轨或吊装):高度上下都加间隙
|
// 空中路径(空轨或吊装):高度上下都加间隙
|
||||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||||
passageNormalToPath = virtualObjectHeight + 2 * safetyMargin; // Z方向(高度)+ 2*间隙(法线方向)
|
passageNormalToPath = effectiveHeight + 2 * safetyMargin; // 法线方向高度 + 2*间隙
|
||||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||||
// 吊装路径分段参数
|
// 吊装路径分段参数
|
||||||
passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙
|
passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙
|
||||||
passageNormalToPathHorizontal = virtualObjectHeight + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙
|
passageNormalToPathHorizontal = effectiveHeight + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙
|
||||||
LogManager.Debug($"[通行空间可视化] 空中路径使用虚拟物体尺寸: 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
|
LogManager.Debug($"[通行空间可视化] 空中路径使用虚拟物体尺寸: 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
|
||||||
}
|
}
|
||||||
else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground)
|
else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground)
|
||||||
@ -4404,7 +4416,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
// 地面路径:物体的长度方向(X轴,前进方向)朝向路径方向
|
// 地面路径:物体的长度方向(X轴,前进方向)朝向路径方向
|
||||||
// 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面)
|
// 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面)
|
||||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||||
passageNormalToPath = virtualObjectHeight + safetyMargin; // Z方向(高度)+ 间隙(法线方向,只有上方)
|
passageNormalToPath = effectiveHeight + safetyMargin; // 法线方向高度 + 间隙(法线方向,只有上方)
|
||||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||||
// 地面路径不需要分段参数
|
// 地面路径不需要分段参数
|
||||||
passageNormalToPathVertical = passageNormalToPath;
|
passageNormalToPathVertical = passageNormalToPath;
|
||||||
@ -4419,7 +4431,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
// - 轨下安装:顶面贴路径,底面留间隙
|
// - 轨下安装:顶面贴路径,底面留间隙
|
||||||
// - 轨上安装:底面贴路径,顶面留间隙
|
// - 轨上安装:底面贴路径,顶面留间隙
|
||||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||||
passageNormalToPath = virtualObjectHeight + safetyMargin; // 法线方向单侧留间隙
|
passageNormalToPath = effectiveHeight + safetyMargin; // 法线方向单侧留间隙
|
||||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||||
// 空轨路径不需要分段参数
|
// 空轨路径不需要分段参数
|
||||||
passageNormalToPathVertical = passageNormalToPath;
|
passageNormalToPathVertical = passageNormalToPath;
|
||||||
@ -4429,26 +4441,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
else if (SelectedAnimatedObject != null)
|
else if (SelectedAnimatedObject != null)
|
||||||
{
|
{
|
||||||
// 使用选择物体的局部轴语义尺寸(模型单位)
|
|
||||||
var bbox = SelectedAnimatedObject.BoundingBox();
|
|
||||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
|
||||||
var axisConvention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
|
||||||
double sizeAlongPath = axisConvention.GetForwardSize(bbox);
|
|
||||||
double sizeAcrossPath = axisConvention.GetSideSize(bbox);
|
|
||||||
double sizeNormalToPath = axisConvention.GetUpSize(bbox);
|
|
||||||
|
|
||||||
// 根据路径类型确定通行空间的尺寸
|
// 根据路径类型确定通行空间的尺寸
|
||||||
if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Rail || CurrentPathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Rail || CurrentPathRoute.PathType == NavisworksTransport.PathType.Hoisting)
|
||||||
{
|
{
|
||||||
// 空中路径(空轨或吊装):
|
// 空中路径(空轨或吊装):
|
||||||
// 高度上下都加间隙
|
// 高度上下都加间隙
|
||||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||||
passageNormalToPath = sizeNormalToPath + 2 * safetyMargin; // 局部up方向高度 + 2*间隙(法线方向)
|
passageNormalToPath = effectiveHeight + 2 * safetyMargin; // 局部up方向高度 + 2*间隙(法线方向)
|
||||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||||
// 吊装路径分段参数
|
// 吊装路径分段参数
|
||||||
passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙
|
passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙
|
||||||
passageNormalToPathHorizontal = sizeNormalToPath + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙
|
passageNormalToPathHorizontal = effectiveHeight + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙
|
||||||
LogManager.Debug($"[通行空间可视化] 空中路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 局部长度={sizeAlongPath / metersToUnitsPassage:F2}m, 局部宽度={sizeAcrossPath / metersToUnitsPassage:F2}m, 局部高度={sizeNormalToPath / metersToUnitsPassage:F2}m, 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
|
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 if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground)
|
else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground)
|
||||||
{
|
{
|
||||||
@ -4456,12 +4460,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
// 物体的长度方向(X轴,前进方向)朝向路径方向
|
// 物体的长度方向(X轴,前进方向)朝向路径方向
|
||||||
// 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面)
|
// 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面)
|
||||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||||
passageNormalToPath = sizeNormalToPath + safetyMargin; // 局部up方向高度 + 间隙(法线方向,只有上方)
|
passageNormalToPath = effectiveHeight + safetyMargin; // 局部up方向高度 + 间隙(法线方向,只有上方)
|
||||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||||
// 地面路径不需要分段参数
|
// 地面路径不需要分段参数
|
||||||
passageNormalToPathVertical = passageNormalToPath;
|
passageNormalToPathVertical = passageNormalToPath;
|
||||||
passageNormalToPathHorizontal = passageNormalToPath;
|
passageNormalToPathHorizontal = passageNormalToPath;
|
||||||
LogManager.Debug($"[通行空间可视化] 地面路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 局部长度={sizeAlongPath / metersToUnitsPassage:F2}m, 局部宽度={sizeAcrossPath / metersToUnitsPassage:F2}m, 局部高度={sizeNormalToPath / metersToUnitsPassage:F2}m, 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
|
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 // Rail
|
||||||
{
|
{
|
||||||
@ -4472,11 +4476,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
// - 轨下安装:顶面贴路径,底面留间隙
|
// - 轨下安装:顶面贴路径,底面留间隙
|
||||||
// - 轨上安装:底面贴路径,顶面留间隙
|
// - 轨上安装:底面贴路径,顶面留间隙
|
||||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||||
passageNormalToPath = sizeNormalToPath + safetyMargin; // 局部up方向高度 + 单侧间隙(法线方向)
|
passageNormalToPath = effectiveHeight + safetyMargin; // 局部up方向高度 + 单侧间隙(法线方向)
|
||||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||||
passageNormalToPathVertical = passageNormalToPath;
|
passageNormalToPathVertical = passageNormalToPath;
|
||||||
passageNormalToPathHorizontal = passageNormalToPath;
|
passageNormalToPathHorizontal = passageNormalToPath;
|
||||||
LogManager.Debug($"[通行空间可视化] 空轨路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 局部长度={sizeAlongPath / metersToUnitsPassage:F2}m, 局部宽度={sizeAcrossPath / metersToUnitsPassage:F2}m, 局部高度={sizeNormalToPath / metersToUnitsPassage:F2}m, 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
|
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
|
else
|
||||||
@ -4950,8 +4954,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重置角度修正值为0
|
// 重置角度修正值为0
|
||||||
ObjectRotationCorrection = 0.0;
|
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||||
LogManager.Debug("[重置状态] 已重置角度修正值为0度");
|
LogManager.Debug("[重置状态] 已重置角度修正值为0");
|
||||||
|
|
||||||
// 清空选中对象
|
// 清空选中对象
|
||||||
SelectedAnimatedObject = null;
|
SelectedAnimatedObject = null;
|
||||||
@ -5261,7 +5265,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
DetectAllObjects = !IsManualCollisionTargetEnabled,
|
DetectAllObjects = !IsManualCollisionTargetEnabled,
|
||||||
|
|
||||||
// 角度修正配置
|
// 角度修正配置
|
||||||
ObjectRotationCorrection = _objectRotationCorrection,
|
ObjectRotationCorrection = _objectRotationCorrection.ZDegrees,
|
||||||
|
|
||||||
// 🔥 关联的检测记录ID
|
// 🔥 关联的检测记录ID
|
||||||
DetectionRecordId = detectionRecordId
|
DetectionRecordId = detectionRecordId
|
||||||
@ -5346,7 +5350,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
|||||||
$"虚拟物体: {queueItem.IsVirtualObject}, " +
|
$"虚拟物体: {queueItem.IsVirtualObject}, " +
|
||||||
$"帧率: {queueItem.FrameRate}, " +
|
$"帧率: {queueItem.FrameRate}, " +
|
||||||
$"时长: {queueItem.DurationSeconds:F2}秒, " +
|
$"时长: {queueItem.DurationSeconds:F2}秒, " +
|
||||||
$"角度修正: {queueItem.ObjectRotationCorrection:F1}°, " +
|
$"角度修正: {_objectRotationCorrection}, " +
|
||||||
$"ID: {itemId}");
|
$"ID: {itemId}");
|
||||||
|
|
||||||
UpdateMainStatus($"已添加到批处理队列: {CurrentPathRoute.Name}");
|
UpdateMainStatus($"已添加到批处理队列: {CurrentPathRoute.Name}");
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditRotationWindow"
|
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditRotationWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="调整物体角度" Height="320" Width="400"
|
Title="调整物体角度" Height="360" Width="430"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
ResizeMode="NoResize"
|
ResizeMode="NoResize"
|
||||||
ShowInTaskbar="False"
|
ShowInTaskbar="False"
|
||||||
@ -26,23 +26,67 @@
|
|||||||
<Border Grid.Row="0" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,0,0,1" Padding="20,15">
|
<Border Grid.Row="0" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,0,0,1" Padding="20,15">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="调整物体角度" FontWeight="SemiBold" FontSize="14" Foreground="#FF2B579A"/>
|
<TextBlock Text="调整物体角度" FontWeight="SemiBold" FontSize="14" Foreground="#FF2B579A"/>
|
||||||
<TextBlock Text="调整物体在路径起点时的水平旋转角度(顺时针)" FontSize="10" Foreground="#FF666666" Margin="0,3,0,0"/>
|
<TextBlock Text="设置物体在路径起点处绕宿主 X/Y/Z 轴的旋转角度" FontSize="10" Foreground="#FF666666" Margin="0,3,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 输入区域 -->
|
<!-- 输入区域 -->
|
||||||
<Border Grid.Row="1" Padding="20,15">
|
<Border Grid.Row="1" Padding="20,15">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<!-- 角度输入 -->
|
<!-- X轴 -->
|
||||||
<Grid Margin="0,0,0,12">
|
<Grid Margin="0,0,0,8">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="80"/>
|
<ColumnDefinition Width="110"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="50"/>
|
<ColumnDefinition Width="50"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock Text="旋转角度:" VerticalAlignment="Center" FontSize="11" Foreground="#FF333333"/>
|
<TextBlock Text="绕 X 轴:" VerticalAlignment="Center" FontSize="11" Foreground="#FF333333"/>
|
||||||
<TextBox Grid.Column="1"
|
<TextBox Grid.Column="1"
|
||||||
Text="{Binding RotationAngle, StringFormat=0.0, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
x:Name="XAxisTextBox"
|
||||||
|
GotFocus="OnAxisTextBoxGotFocus"
|
||||||
|
Text="{Binding RotationXDegrees, StringFormat=0.0, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Height="28"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Padding="8,0"
|
||||||
|
FontSize="11"
|
||||||
|
BorderBrush="#FFCCCCCC"
|
||||||
|
BorderThickness="1"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="°" VerticalAlignment="Center" FontSize="11" Foreground="#FF666666" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Y轴 -->
|
||||||
|
<Grid Margin="0,0,0,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="110"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="50"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="绕 Y 轴:" VerticalAlignment="Center" FontSize="11" Foreground="#FF333333"/>
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
x:Name="YAxisTextBox"
|
||||||
|
GotFocus="OnAxisTextBoxGotFocus"
|
||||||
|
Text="{Binding RotationYDegrees, StringFormat=0.0, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Height="28"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Padding="8,0"
|
||||||
|
FontSize="11"
|
||||||
|
BorderBrush="#FFCCCCCC"
|
||||||
|
BorderThickness="1"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="°" VerticalAlignment="Center" FontSize="11" Foreground="#FF666666" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Z轴 -->
|
||||||
|
<Grid Margin="0,0,0,12">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="110"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="50"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="绕 Z 轴:" VerticalAlignment="Center" FontSize="11" Foreground="#FF333333"/>
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
x:Name="ZAxisTextBox"
|
||||||
|
GotFocus="OnAxisTextBoxGotFocus"
|
||||||
|
Text="{Binding RotationZDegrees, StringFormat=0.0, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
Height="28"
|
Height="28"
|
||||||
VerticalContentAlignment="Center"
|
VerticalContentAlignment="Center"
|
||||||
Padding="8,0"
|
Padding="8,0"
|
||||||
@ -53,7 +97,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- 快捷按钮 -->
|
<!-- 快捷按钮 -->
|
||||||
<TextBlock Text="快捷角度:" FontSize="10" Foreground="#FF666666" Margin="0,0,0,8"/>
|
<TextBlock Text="快捷角度(作用于当前焦点输入框):" FontSize="10" Foreground="#FF666666" Margin="0,0,0,8"/>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Button Content="0°"
|
<Button Content="0°"
|
||||||
Click="OnQuickAngleClick"
|
Click="OnQuickAngleClick"
|
||||||
|
|||||||
@ -2,33 +2,80 @@ using System;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using NavisworksTransport.Utils.CoordinateSystem;
|
||||||
|
|
||||||
namespace NavisworksTransport.UI.WPF.Views
|
namespace NavisworksTransport.UI.WPF.Views
|
||||||
{
|
{
|
||||||
public partial class EditRotationWindow : Window, INotifyPropertyChanged
|
public partial class EditRotationWindow : Window, INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private double _rotationAngle;
|
private enum RotationAxisTarget
|
||||||
|
|
||||||
public double RotationAngle
|
|
||||||
{
|
{
|
||||||
get => _rotationAngle;
|
X,
|
||||||
|
Y,
|
||||||
|
Z
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _rotationXDegrees;
|
||||||
|
private double _rotationYDegrees;
|
||||||
|
private double _rotationZDegrees;
|
||||||
|
private RotationAxisTarget _activeAxisTarget = RotationAxisTarget.Z;
|
||||||
|
|
||||||
|
public double RotationXDegrees
|
||||||
|
{
|
||||||
|
get => _rotationXDegrees;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (_rotationAngle != value)
|
if (_rotationXDegrees != value)
|
||||||
{
|
{
|
||||||
_rotationAngle = value;
|
_rotationXDegrees = value;
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(RotationCorrection));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EditRotationWindow(double currentAngle)
|
public double RotationYDegrees
|
||||||
|
{
|
||||||
|
get => _rotationYDegrees;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_rotationYDegrees != value)
|
||||||
|
{
|
||||||
|
_rotationYDegrees = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(RotationCorrection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double RotationZDegrees
|
||||||
|
{
|
||||||
|
get => _rotationZDegrees;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_rotationZDegrees != value)
|
||||||
|
{
|
||||||
|
_rotationZDegrees = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(RotationCorrection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalEulerRotationCorrection RotationCorrection =>
|
||||||
|
new LocalEulerRotationCorrection(RotationXDegrees, RotationYDegrees, RotationZDegrees);
|
||||||
|
|
||||||
|
public EditRotationWindow(LocalEulerRotationCorrection currentRotation)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
RotationAngle = currentAngle;
|
RotationXDegrees = currentRotation.XDegrees;
|
||||||
|
RotationYDegrees = currentRotation.YDegrees;
|
||||||
|
RotationZDegrees = currentRotation.ZDegrees;
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
Loaded += (sender, args) => ZAxisTextBox.Focus();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -39,34 +86,57 @@ namespace NavisworksTransport.UI.WPF.Views
|
|||||||
|
|
||||||
private void OnQuickAngleClick(object sender, RoutedEventArgs e)
|
private void OnQuickAngleClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is System.Windows.Controls.Button button)
|
if (sender is Button button)
|
||||||
{
|
{
|
||||||
LogManager.Debug($"[快捷角度] 点击按钮: {button.Content}, Tag类型: {button.Tag?.GetType()}, Tag值: {button.Tag}");
|
LogManager.Debug($"[快捷角度] 点击按钮: {button.Content}, Tag类型: {button.Tag?.GetType()}, Tag值: {button.Tag}");
|
||||||
|
|
||||||
if (button.Tag != null)
|
if (button.Tag != null)
|
||||||
{
|
{
|
||||||
|
double? parsedAngle = null;
|
||||||
if (button.Tag is int intTag)
|
if (button.Tag is int intTag)
|
||||||
{
|
{
|
||||||
RotationAngle = intTag;
|
parsedAngle = intTag;
|
||||||
LogManager.Debug($"[快捷角度] 设置角度为: {intTag}°");
|
|
||||||
}
|
}
|
||||||
else if (button.Tag is string stringTag && double.TryParse(stringTag, out double angle))
|
else if (button.Tag is string stringTag && double.TryParse(stringTag, out double angle))
|
||||||
{
|
{
|
||||||
RotationAngle = angle;
|
parsedAngle = angle;
|
||||||
LogManager.Debug($"[快捷角度] 设置角度为: {angle}°");
|
|
||||||
}
|
}
|
||||||
else if (button.Tag is double doubleTag)
|
else if (button.Tag is double doubleTag)
|
||||||
{
|
{
|
||||||
RotationAngle = doubleTag;
|
parsedAngle = doubleTag;
|
||||||
LogManager.Debug($"[快捷角度] 设置角度为: {doubleTag}°");
|
}
|
||||||
|
|
||||||
|
if (parsedAngle.HasValue)
|
||||||
|
{
|
||||||
|
ApplyQuickAngle(parsedAngle.Value);
|
||||||
|
LogManager.Debug($"[快捷角度] 设置 {_activeAxisTarget} 轴角度为: {parsedAngle.Value}°");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAxisTextBoxGotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(sender, XAxisTextBox))
|
||||||
|
{
|
||||||
|
_activeAxisTarget = RotationAxisTarget.X;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(sender, YAxisTextBox))
|
||||||
|
{
|
||||||
|
_activeAxisTarget = RotationAxisTarget.Y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeAxisTarget = RotationAxisTarget.Z;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnResetClick(object sender, RoutedEventArgs e)
|
private void OnResetClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
RotationAngle = 0.0;
|
RotationXDegrees = 0.0;
|
||||||
|
RotationYDegrees = 0.0;
|
||||||
|
RotationZDegrees = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConfirmClick(object sender, RoutedEventArgs e)
|
private void OnConfirmClick(object sender, RoutedEventArgs e)
|
||||||
@ -87,5 +157,22 @@ namespace NavisworksTransport.UI.WPF.Views
|
|||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyQuickAngle(double angle)
|
||||||
|
{
|
||||||
|
switch (_activeAxisTarget)
|
||||||
|
{
|
||||||
|
case RotationAxisTarget.X:
|
||||||
|
RotationXDegrees = angle;
|
||||||
|
break;
|
||||||
|
case RotationAxisTarget.Y:
|
||||||
|
RotationYDegrees = angle;
|
||||||
|
break;
|
||||||
|
case RotationAxisTarget.Z:
|
||||||
|
default:
|
||||||
|
RotationZDegrees = angle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,6 +35,36 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
Vector3 canonicalUp,
|
Vector3 canonicalUp,
|
||||||
ModelAxisConvention convention,
|
ModelAxisConvention convention,
|
||||||
out Quaternion rotation)
|
out Quaternion rotation)
|
||||||
|
{
|
||||||
|
return TryCreateQuaternionFromForward(
|
||||||
|
desiredForward,
|
||||||
|
canonicalUp,
|
||||||
|
convention,
|
||||||
|
LocalEulerRotationCorrection.Zero,
|
||||||
|
out rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateQuaternionFromForward(
|
||||||
|
Vector3 desiredForward,
|
||||||
|
Vector3 canonicalUp,
|
||||||
|
ModelAxisConvention convention,
|
||||||
|
LocalEulerRotationCorrection correction,
|
||||||
|
out Quaternion rotation)
|
||||||
|
{
|
||||||
|
return TryCreateQuaternionFromForward(
|
||||||
|
desiredForward,
|
||||||
|
canonicalUp,
|
||||||
|
convention,
|
||||||
|
correction.CreateQuaternion(Vector3.UnitX, Vector3.UnitY, Vector3.UnitZ),
|
||||||
|
out rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateQuaternionFromForward(
|
||||||
|
Vector3 desiredForward,
|
||||||
|
Vector3 canonicalUp,
|
||||||
|
ModelAxisConvention convention,
|
||||||
|
Quaternion correctionQuaternion,
|
||||||
|
out Quaternion rotation)
|
||||||
{
|
{
|
||||||
if (convention == null)
|
if (convention == null)
|
||||||
{
|
{
|
||||||
@ -50,7 +80,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vector3 normalizedForward = Vector3.Normalize(projectedForward);
|
Vector3 normalizedForward = Vector3.Normalize(projectedForward);
|
||||||
rotation = convention.CreateQuaternion(normalizedForward, Vector3.Normalize(canonicalUp));
|
Vector3 normalizedUp = Vector3.Normalize(canonicalUp);
|
||||||
|
|
||||||
|
if (Math.Abs(correctionQuaternion.X) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Y) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Z) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.W - 1.0f) < 1e-9f)
|
||||||
|
{
|
||||||
|
rotation = convention.CreateQuaternion(normalizedForward, normalizedUp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalAxisPoseBuilder.ApplyLocalPreRotation(
|
||||||
|
convention,
|
||||||
|
correctionQuaternion,
|
||||||
|
out var correctedLocalForward,
|
||||||
|
out var correctedLocalUp);
|
||||||
|
rotation = LocalAxisPoseBuilder.CreateQuaternionFromLocalAxes(
|
||||||
|
correctedLocalForward,
|
||||||
|
correctedLocalUp,
|
||||||
|
normalizedForward,
|
||||||
|
normalizedUp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -161,7 +161,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
canonicalUp,
|
canonicalUp,
|
||||||
convention,
|
convention,
|
||||||
null,
|
null,
|
||||||
localUpRotationDegrees,
|
Quaternion.CreateFromAxisAngle(Vector3.Normalize(convention.UpUnitVector), (float)(localUpRotationDegrees * Math.PI / 180.0)),
|
||||||
|
out rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateQuaternion(
|
||||||
|
Vector3 previousPoint,
|
||||||
|
Vector3 currentPoint,
|
||||||
|
Vector3 nextPoint,
|
||||||
|
Vector3 canonicalUp,
|
||||||
|
ModelAxisConvention convention,
|
||||||
|
LocalEulerRotationCorrection correction,
|
||||||
|
out Quaternion rotation)
|
||||||
|
{
|
||||||
|
return TryCreateQuaternion(
|
||||||
|
previousPoint,
|
||||||
|
currentPoint,
|
||||||
|
nextPoint,
|
||||||
|
canonicalUp,
|
||||||
|
convention,
|
||||||
|
null,
|
||||||
|
correction.CreateQuaternion(Vector3.UnitX, Vector3.UnitY, Vector3.UnitZ),
|
||||||
out rotation);
|
out rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +194,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
Vector3? preferredNormal,
|
Vector3? preferredNormal,
|
||||||
double localUpRotationDegrees,
|
double localUpRotationDegrees,
|
||||||
out Quaternion rotation)
|
out Quaternion rotation)
|
||||||
|
{
|
||||||
|
return TryCreateQuaternion(
|
||||||
|
previousPoint,
|
||||||
|
currentPoint,
|
||||||
|
nextPoint,
|
||||||
|
canonicalUp,
|
||||||
|
convention,
|
||||||
|
preferredNormal,
|
||||||
|
Quaternion.CreateFromAxisAngle(Vector3.Normalize(convention.UpUnitVector), (float)(localUpRotationDegrees * Math.PI / 180.0)),
|
||||||
|
out rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateQuaternion(
|
||||||
|
Vector3 previousPoint,
|
||||||
|
Vector3 currentPoint,
|
||||||
|
Vector3 nextPoint,
|
||||||
|
Vector3 canonicalUp,
|
||||||
|
ModelAxisConvention convention,
|
||||||
|
Vector3? preferredNormal,
|
||||||
|
Quaternion correctionQuaternion,
|
||||||
|
out Quaternion rotation)
|
||||||
{
|
{
|
||||||
rotation = Quaternion.Identity;
|
rotation = Quaternion.Identity;
|
||||||
|
|
||||||
@ -195,67 +236,22 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.Abs(localUpRotationDegrees) < 1e-9)
|
if (Math.Abs(correctionQuaternion.X) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Y) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Z) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.W - 1.0f) < 1e-9f)
|
||||||
{
|
{
|
||||||
rotation = convention.CreateQuaternion(forward, up);
|
rotation = convention.CreateQuaternion(forward, up);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float correctionRadians = (float)(localUpRotationDegrees * Math.PI / 180.0);
|
LocalAxisPoseBuilder.ApplyLocalPreRotation(
|
||||||
Quaternion localPreRotation = Quaternion.CreateFromAxisAngle(
|
convention,
|
||||||
Vector3.Normalize(convention.UpUnitVector),
|
correctionQuaternion,
|
||||||
correctionRadians);
|
out var correctedLocalForward,
|
||||||
|
out var correctedLocalUp);
|
||||||
Vector3 correctedLocalForward = Vector3.Normalize(Vector3.Transform(convention.ForwardUnitVector, localPreRotation));
|
rotation = LocalAxisPoseBuilder.CreateQuaternionFromLocalAxes(correctedLocalForward, correctedLocalUp, forward, up);
|
||||||
Vector3 correctedLocalUp = Vector3.Normalize(Vector3.Transform(convention.UpUnitVector, localPreRotation));
|
|
||||||
rotation = CreateQuaternionFromLocalAxes(correctedLocalForward, correctedLocalUp, forward, up);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Quaternion CreateQuaternionFromLocalAxes(
|
|
||||||
Vector3 localForward,
|
|
||||||
Vector3 localUp,
|
|
||||||
Vector3 worldForward,
|
|
||||||
Vector3 worldUp)
|
|
||||||
{
|
|
||||||
Vector3 normalizedLocalForward = Vector3.Normalize(localForward);
|
|
||||||
Vector3 normalizedLocalUp = Vector3.Normalize(localUp);
|
|
||||||
Vector3 normalizedWorldForward = Vector3.Normalize(worldForward);
|
|
||||||
Vector3 normalizedWorldUp = Vector3.Normalize(worldUp);
|
|
||||||
|
|
||||||
Vector3 localRemaining = Vector3.Normalize(Vector3.Cross(normalizedLocalForward, normalizedLocalUp));
|
|
||||||
Vector3 worldRemaining = Vector3.Normalize(Vector3.Cross(normalizedWorldForward, normalizedWorldUp));
|
|
||||||
|
|
||||||
Vector3 worldX = MapOriginalLocalAxis(Vector3.UnitX, normalizedLocalForward, normalizedLocalUp, localRemaining, normalizedWorldForward, normalizedWorldUp, worldRemaining);
|
|
||||||
Vector3 worldY = MapOriginalLocalAxis(Vector3.UnitY, normalizedLocalForward, normalizedLocalUp, localRemaining, normalizedWorldForward, normalizedWorldUp, worldRemaining);
|
|
||||||
Vector3 worldZ = MapOriginalLocalAxis(Vector3.UnitZ, normalizedLocalForward, normalizedLocalUp, localRemaining, normalizedWorldForward, normalizedWorldUp, worldRemaining);
|
|
||||||
|
|
||||||
Matrix4x4 linear = new Matrix4x4(
|
|
||||||
worldX.X, worldY.X, worldZ.X, 0f,
|
|
||||||
worldX.Y, worldY.Y, worldZ.Y, 0f,
|
|
||||||
worldX.Z, worldY.Z, worldZ.Z, 0f,
|
|
||||||
0f, 0f, 0f, 1f);
|
|
||||||
return Quaternion.CreateFromRotationMatrix(linear);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector3 MapOriginalLocalAxis(
|
|
||||||
Vector3 originalAxis,
|
|
||||||
Vector3 localForward,
|
|
||||||
Vector3 localUp,
|
|
||||||
Vector3 localRemaining,
|
|
||||||
Vector3 worldForward,
|
|
||||||
Vector3 worldUp,
|
|
||||||
Vector3 worldRemaining)
|
|
||||||
{
|
|
||||||
float forwardWeight = Vector3.Dot(originalAxis, localForward);
|
|
||||||
float upWeight = Vector3.Dot(originalAxis, localUp);
|
|
||||||
float remainingWeight = Vector3.Dot(originalAxis, localRemaining);
|
|
||||||
|
|
||||||
return Vector3.Normalize(
|
|
||||||
forwardWeight * worldForward +
|
|
||||||
upWeight * worldUp +
|
|
||||||
remainingWeight * worldRemaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -188,6 +188,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
hostLinear.M31, hostLinear.M32, hostLinear.M33);
|
hostLinear.M31, hostLinear.M32, hostLinear.M33);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将宿主 X/Y/Z 三轴旋转角转换为内部 Canonical Space 中的旋转四元数。
|
||||||
|
/// 约定:先绕宿主 X,再绕宿主 Y,最后绕宿主 Z。
|
||||||
|
/// </summary>
|
||||||
|
public Quaternion CreateCanonicalRotationCorrection(LocalEulerRotationCorrection correction)
|
||||||
|
{
|
||||||
|
if (correction.IsZero)
|
||||||
|
{
|
||||||
|
return Quaternion.Identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 canonicalHostX = Vector3.Normalize(ToCanonicalVector3(Vector3.UnitX));
|
||||||
|
Vector3 canonicalHostY = Vector3.Normalize(ToCanonicalVector3(Vector3.UnitY));
|
||||||
|
Vector3 canonicalHostZ = Vector3.Normalize(ToCanonicalVector3(Vector3.UnitZ));
|
||||||
|
|
||||||
|
Quaternion qx = Quaternion.CreateFromAxisAngle(canonicalHostX, DegreesToRadians(correction.XDegrees));
|
||||||
|
Quaternion qy = Quaternion.CreateFromAxisAngle(canonicalHostY, DegreesToRadians(correction.YDegrees));
|
||||||
|
Quaternion qz = Quaternion.CreateFromAxisAngle(canonicalHostZ, DegreesToRadians(correction.ZDegrees));
|
||||||
|
return Quaternion.Normalize(qz * qy * qx);
|
||||||
|
}
|
||||||
|
|
||||||
private static Rotation3D CreateRotationFromLinearComponents(
|
private static Rotation3D CreateRotationFromLinearComponents(
|
||||||
double m00, double m01, double m02,
|
double m00, double m01, double m02,
|
||||||
double m10, double m11, double m12,
|
double m10, double m11, double m12,
|
||||||
@ -340,5 +361,10 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
{
|
{
|
||||||
return new Vector3D(vector.X, vector.Y, vector.Z);
|
return new Vector3D(vector.X, vector.Y, vector.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float DegreesToRadians(double degrees)
|
||||||
|
{
|
||||||
|
return (float)(degrees * Math.PI / 180.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/Utils/CoordinateSystem/LocalAxisPoseBuilder.cs
Normal file
76
src/Utils/CoordinateSystem/LocalAxisPoseBuilder.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace NavisworksTransport.Utils.CoordinateSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 根据模型局部轴和世界目标 forward/up 构造姿态。
|
||||||
|
/// </summary>
|
||||||
|
public static class LocalAxisPoseBuilder
|
||||||
|
{
|
||||||
|
public static Quaternion CreateQuaternionFromLocalAxes(
|
||||||
|
Vector3 localForward,
|
||||||
|
Vector3 localUp,
|
||||||
|
Vector3 worldForward,
|
||||||
|
Vector3 worldUp)
|
||||||
|
{
|
||||||
|
Vector3 normalizedLocalForward = Vector3.Normalize(localForward);
|
||||||
|
Vector3 normalizedLocalUp = Vector3.Normalize(localUp);
|
||||||
|
Vector3 normalizedWorldForward = Vector3.Normalize(worldForward);
|
||||||
|
Vector3 normalizedWorldUp = Vector3.Normalize(worldUp);
|
||||||
|
|
||||||
|
Vector3 localRemaining = Vector3.Normalize(Vector3.Cross(normalizedLocalForward, normalizedLocalUp));
|
||||||
|
Vector3 worldRemaining = Vector3.Normalize(Vector3.Cross(normalizedWorldForward, normalizedWorldUp));
|
||||||
|
|
||||||
|
Vector3 worldX = MapOriginalLocalAxis(Vector3.UnitX, normalizedLocalForward, normalizedLocalUp, localRemaining, normalizedWorldForward, normalizedWorldUp, worldRemaining);
|
||||||
|
Vector3 worldY = MapOriginalLocalAxis(Vector3.UnitY, normalizedLocalForward, normalizedLocalUp, localRemaining, normalizedWorldForward, normalizedWorldUp, worldRemaining);
|
||||||
|
Vector3 worldZ = MapOriginalLocalAxis(Vector3.UnitZ, normalizedLocalForward, normalizedLocalUp, localRemaining, normalizedWorldForward, normalizedWorldUp, worldRemaining);
|
||||||
|
|
||||||
|
Matrix4x4 linear = new Matrix4x4(
|
||||||
|
worldX.X, worldY.X, worldZ.X, 0f,
|
||||||
|
worldX.Y, worldY.Y, worldZ.Y, 0f,
|
||||||
|
worldX.Z, worldY.Z, worldZ.Z, 0f,
|
||||||
|
0f, 0f, 0f, 1f);
|
||||||
|
return Quaternion.CreateFromRotationMatrix(linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ApplyLocalPreRotation(
|
||||||
|
ModelAxisConvention convention,
|
||||||
|
Quaternion correctionQuaternion,
|
||||||
|
out Vector3 correctedLocalForward,
|
||||||
|
out Vector3 correctedLocalUp)
|
||||||
|
{
|
||||||
|
if (Math.Abs(correctionQuaternion.X) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Y) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Z) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.W - 1.0f) < 1e-9f)
|
||||||
|
{
|
||||||
|
correctedLocalForward = convention.ForwardUnitVector;
|
||||||
|
correctedLocalUp = convention.UpUnitVector;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
correctedLocalForward = Vector3.Normalize(Vector3.Transform(convention.ForwardUnitVector, correctionQuaternion));
|
||||||
|
correctedLocalUp = Vector3.Normalize(Vector3.Transform(convention.UpUnitVector, correctionQuaternion));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 MapOriginalLocalAxis(
|
||||||
|
Vector3 originalAxis,
|
||||||
|
Vector3 localForward,
|
||||||
|
Vector3 localUp,
|
||||||
|
Vector3 localRemaining,
|
||||||
|
Vector3 worldForward,
|
||||||
|
Vector3 worldUp,
|
||||||
|
Vector3 worldRemaining)
|
||||||
|
{
|
||||||
|
float forwardWeight = Vector3.Dot(originalAxis, localForward);
|
||||||
|
float upWeight = Vector3.Dot(originalAxis, localUp);
|
||||||
|
float remainingWeight = Vector3.Dot(originalAxis, localRemaining);
|
||||||
|
|
||||||
|
return Vector3.Normalize(
|
||||||
|
forwardWeight * worldForward +
|
||||||
|
upWeight * worldUp +
|
||||||
|
remainingWeight * worldRemaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/Utils/CoordinateSystem/LocalEulerRotationCorrection.cs
Normal file
111
src/Utils/CoordinateSystem/LocalEulerRotationCorrection.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace NavisworksTransport.Utils.CoordinateSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 三轴旋转修正角(单位:度)。
|
||||||
|
/// 该结构本身只保存 X/Y/Z 三个角度值,不直接绑定宿主/内部/资产语义。
|
||||||
|
/// </summary>
|
||||||
|
public struct LocalEulerRotationCorrection : IEquatable<LocalEulerRotationCorrection>
|
||||||
|
{
|
||||||
|
public static readonly LocalEulerRotationCorrection Zero = new LocalEulerRotationCorrection(0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
public LocalEulerRotationCorrection(double xDegrees, double yDegrees, double zDegrees)
|
||||||
|
{
|
||||||
|
XDegrees = xDegrees;
|
||||||
|
YDegrees = yDegrees;
|
||||||
|
ZDegrees = zDegrees;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double XDegrees { get; }
|
||||||
|
public double YDegrees { get; }
|
||||||
|
public double ZDegrees { get; }
|
||||||
|
|
||||||
|
public bool IsZero =>
|
||||||
|
Math.Abs(XDegrees) < 1e-9 &&
|
||||||
|
Math.Abs(YDegrees) < 1e-9 &&
|
||||||
|
Math.Abs(ZDegrees) < 1e-9;
|
||||||
|
|
||||||
|
public Quaternion CreateQuaternion(Vector3 axisX, Vector3 axisY, Vector3 axisZ)
|
||||||
|
{
|
||||||
|
Quaternion qx = Quaternion.CreateFromAxisAngle(Vector3.Normalize(axisX), DegreesToRadians(XDegrees));
|
||||||
|
Quaternion qy = Quaternion.CreateFromAxisAngle(Vector3.Normalize(axisY), DegreesToRadians(YDegrees));
|
||||||
|
Quaternion qz = Quaternion.CreateFromAxisAngle(Vector3.Normalize(axisZ), DegreesToRadians(ZDegrees));
|
||||||
|
return Quaternion.Normalize(qz * qy * qx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalEulerRotationCorrection FromConventionUpDegrees(ModelAxisConvention convention, double upDegrees)
|
||||||
|
{
|
||||||
|
if (convention == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(convention));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (convention.UpAxis)
|
||||||
|
{
|
||||||
|
case LocalAxisDirection.PositiveX:
|
||||||
|
case LocalAxisDirection.NegativeX:
|
||||||
|
return new LocalEulerRotationCorrection(upDegrees, 0.0, 0.0);
|
||||||
|
case LocalAxisDirection.PositiveY:
|
||||||
|
case LocalAxisDirection.NegativeY:
|
||||||
|
return new LocalEulerRotationCorrection(0.0, upDegrees, 0.0);
|
||||||
|
case LocalAxisDirection.PositiveZ:
|
||||||
|
case LocalAxisDirection.NegativeZ:
|
||||||
|
default:
|
||||||
|
return new LocalEulerRotationCorrection(0.0, 0.0, upDegrees);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"X={0:F1}°,Y={1:F1}°,Z={2:F1}°",
|
||||||
|
XDegrees,
|
||||||
|
YDegrees,
|
||||||
|
ZDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(LocalEulerRotationCorrection other)
|
||||||
|
{
|
||||||
|
return XDegrees.Equals(other.XDegrees) &&
|
||||||
|
YDegrees.Equals(other.YDegrees) &&
|
||||||
|
ZDegrees.Equals(other.ZDegrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is LocalEulerRotationCorrection other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int hash = 17;
|
||||||
|
hash = hash * 31 + XDegrees.GetHashCode();
|
||||||
|
hash = hash * 31 + YDegrees.GetHashCode();
|
||||||
|
hash = hash * 31 + ZDegrees.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(LocalEulerRotationCorrection left, LocalEulerRotationCorrection right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(LocalEulerRotationCorrection left, LocalEulerRotationCorrection right)
|
||||||
|
{
|
||||||
|
return !left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float DegreesToRadians(double degrees)
|
||||||
|
{
|
||||||
|
return (float)(degrees * Math.PI / 180.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
|||||||
{
|
{
|
||||||
public static class ObjectSpaceOrientationHelper
|
public static class ObjectSpaceOrientationHelper
|
||||||
{
|
{
|
||||||
|
public static bool TryCalculateAxes(
|
||||||
|
Vector3 segmentDirection,
|
||||||
|
Vector3 upReference,
|
||||||
|
out (Vector3 right, Vector3 up) axes,
|
||||||
|
Vector3? horizontalDirection = null)
|
||||||
|
{
|
||||||
|
double segmentLength = Math.Sqrt(
|
||||||
|
segmentDirection.X * segmentDirection.X +
|
||||||
|
segmentDirection.Y * segmentDirection.Y +
|
||||||
|
segmentDirection.Z * segmentDirection.Z);
|
||||||
|
|
||||||
|
if (segmentLength < 1e-9)
|
||||||
|
{
|
||||||
|
axes = (Vector3.Zero, Vector3.Zero);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
axes = CalculateAxes(segmentDirection, upReference, horizontalDirection);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static (Vector3D right, Vector3D up) CalculateAxes(
|
public static (Vector3D right, Vector3D up) CalculateAxes(
|
||||||
Point3D startPoint,
|
Point3D startPoint,
|
||||||
Point3D endPoint,
|
Point3D endPoint,
|
||||||
|
|||||||
58
src/Utils/CoordinateSystem/RotatedObjectExtentHelper.cs
Normal file
58
src/Utils/CoordinateSystem/RotatedObjectExtentHelper.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace NavisworksTransport.Utils.CoordinateSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 计算物体本地预旋转后,在 forward/side/up 语义下的投影尺寸。
|
||||||
|
/// </summary>
|
||||||
|
public static class RotatedObjectExtentHelper
|
||||||
|
{
|
||||||
|
public static (double forwardExtent, double sideExtent, double upExtent) CalculateProjectedSemanticExtents(
|
||||||
|
ModelAxisConvention convention,
|
||||||
|
double forwardSize,
|
||||||
|
double sideSize,
|
||||||
|
double upSize,
|
||||||
|
Quaternion correctionQuaternion)
|
||||||
|
{
|
||||||
|
if (convention == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(convention));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.Abs(correctionQuaternion.X) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Y) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.Z) < 1e-9f &&
|
||||||
|
Math.Abs(correctionQuaternion.W - 1.0f) < 1e-9f)
|
||||||
|
{
|
||||||
|
return (forwardSize, sideSize, upSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 localSize = convention.CreateScaleVector3(forwardSize, sideSize, upSize);
|
||||||
|
Vector3 rotatedLocalX = Vector3.Normalize(Vector3.Transform(Vector3.UnitX, correctionQuaternion));
|
||||||
|
Vector3 rotatedLocalY = Vector3.Normalize(Vector3.Transform(Vector3.UnitY, correctionQuaternion));
|
||||||
|
Vector3 rotatedLocalZ = Vector3.Normalize(Vector3.Transform(Vector3.UnitZ, correctionQuaternion));
|
||||||
|
|
||||||
|
Vector3 semanticForward = Vector3.Normalize(convention.ForwardUnitVector);
|
||||||
|
Vector3 semanticUp = Vector3.Normalize(convention.UpUnitVector);
|
||||||
|
Vector3 semanticSide = Vector3.Normalize(Vector3.Cross(semanticForward, semanticUp));
|
||||||
|
|
||||||
|
return (
|
||||||
|
ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, semanticForward),
|
||||||
|
ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, semanticSide),
|
||||||
|
ProjectExtent(localSize, rotatedLocalX, rotatedLocalY, rotatedLocalZ, semanticUp));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -218,6 +218,25 @@ namespace NavisworksTransport.Utils
|
|||||||
ModelAxisConvention axisConvention,
|
ModelAxisConvention axisConvention,
|
||||||
double localUpRotationDegrees,
|
double localUpRotationDegrees,
|
||||||
out Matrix3 linearTransform)
|
out Matrix3 linearTransform)
|
||||||
|
{
|
||||||
|
return TryCreateRailLinearTransform(
|
||||||
|
route,
|
||||||
|
previousPoint,
|
||||||
|
currentPoint,
|
||||||
|
nextPoint,
|
||||||
|
axisConvention,
|
||||||
|
LocalEulerRotationCorrection.FromConventionUpDegrees(axisConvention, localUpRotationDegrees),
|
||||||
|
out linearTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateRailLinearTransform(
|
||||||
|
PathRoute route,
|
||||||
|
Point3D previousPoint,
|
||||||
|
Point3D currentPoint,
|
||||||
|
Point3D nextPoint,
|
||||||
|
ModelAxisConvention axisConvention,
|
||||||
|
LocalEulerRotationCorrection correction,
|
||||||
|
out Matrix3 linearTransform)
|
||||||
{
|
{
|
||||||
linearTransform = null;
|
linearTransform = null;
|
||||||
|
|
||||||
@ -245,6 +264,7 @@ namespace NavisworksTransport.Utils
|
|||||||
var canonicalForward = frame.Forward;
|
var canonicalForward = frame.Forward;
|
||||||
var canonicalLateral = frame.Lateral;
|
var canonicalLateral = frame.Lateral;
|
||||||
var canonicalUp = frame.Normal;
|
var canonicalUp = frame.Normal;
|
||||||
|
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(correction);
|
||||||
Quaternion canonicalRotation = CanonicalRailPoseBuilder.TryCreateQuaternion(
|
Quaternion canonicalRotation = CanonicalRailPoseBuilder.TryCreateQuaternion(
|
||||||
canonicalPreviousPoint,
|
canonicalPreviousPoint,
|
||||||
canonicalCurrentPoint,
|
canonicalCurrentPoint,
|
||||||
@ -252,7 +272,7 @@ namespace NavisworksTransport.Utils
|
|||||||
HostCoordinateAdapter.CanonicalUpVector3,
|
HostCoordinateAdapter.CanonicalUpVector3,
|
||||||
axisConvention,
|
axisConvention,
|
||||||
ResolvePreferredNormal(route),
|
ResolvePreferredNormal(route),
|
||||||
localUpRotationDegrees,
|
correctionQuaternion,
|
||||||
out var correctedRotation)
|
out var correctedRotation)
|
||||||
? correctedRotation
|
? correctedRotation
|
||||||
: axisConvention.CreateQuaternion(canonicalForward, canonicalUp);
|
: axisConvention.CreateQuaternion(canonicalForward, canonicalUp);
|
||||||
@ -276,7 +296,7 @@ namespace NavisworksTransport.Utils
|
|||||||
$"Canonical侧向=({canonicalLateral.X:F4},{canonicalLateral.Y:F4},{canonicalLateral.Z:F4}), " +
|
$"Canonical侧向=({canonicalLateral.X:F4},{canonicalLateral.Y:F4},{canonicalLateral.Z:F4}), " +
|
||||||
$"Canonical法向=({canonicalUp.X:F4},{canonicalUp.Y:F4},{canonicalUp.Z:F4}), " +
|
$"Canonical法向=({canonicalUp.X:F4},{canonicalUp.Y:F4},{canonicalUp.Z:F4}), " +
|
||||||
$"Host法向=({hostZAxis.X:F4},{hostZAxis.Y:F4},{hostZAxis.Z:F4}), " +
|
$"Host法向=({hostZAxis.X:F4},{hostZAxis.Y:F4},{hostZAxis.Z:F4}), " +
|
||||||
$"模型Forward={axisConvention.ForwardAxis}, 模型Up={axisConvention.UpAxis}, 本地预旋转={localUpRotationDegrees:F1}°");
|
$"模型Forward={axisConvention.ForwardAxis}, 模型Up={axisConvention.UpAxis}, 宿主轴旋转={correction}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,6 +376,25 @@ namespace NavisworksTransport.Utils
|
|||||||
ModelAxisConvention axisConvention,
|
ModelAxisConvention axisConvention,
|
||||||
double localUpRotationDegrees,
|
double localUpRotationDegrees,
|
||||||
out Rotation3D rotation)
|
out Rotation3D rotation)
|
||||||
|
{
|
||||||
|
return TryCreateRailRotation(
|
||||||
|
route,
|
||||||
|
previousPoint,
|
||||||
|
currentPoint,
|
||||||
|
nextPoint,
|
||||||
|
axisConvention,
|
||||||
|
LocalEulerRotationCorrection.FromConventionUpDegrees(axisConvention, localUpRotationDegrees),
|
||||||
|
out rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryCreateRailRotation(
|
||||||
|
PathRoute route,
|
||||||
|
Point3D previousPoint,
|
||||||
|
Point3D currentPoint,
|
||||||
|
Point3D nextPoint,
|
||||||
|
ModelAxisConvention axisConvention,
|
||||||
|
LocalEulerRotationCorrection correction,
|
||||||
|
out Rotation3D rotation)
|
||||||
{
|
{
|
||||||
rotation = Rotation3D.Identity;
|
rotation = Rotation3D.Identity;
|
||||||
LogRotationConstructorConventionOnce();
|
LogRotationConstructorConventionOnce();
|
||||||
@ -366,7 +405,7 @@ namespace NavisworksTransport.Utils
|
|||||||
currentPoint,
|
currentPoint,
|
||||||
nextPoint,
|
nextPoint,
|
||||||
axisConvention,
|
axisConvention,
|
||||||
localUpRotationDegrees,
|
correction,
|
||||||
out var linearTransform))
|
out var linearTransform))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user