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`
|
||||
- 需要路径相关工具?→ 使用 `PathHelper`
|
||||
|
||||
#### 5. 临时定位代码必须回收
|
||||
|
||||
为定位问题临时加入的兜底调用、强制刷新、额外同步、绕过分支等代码,如果后续确认**不是真正根因**,在根因修复后必须恢复或删除,不能把这类临时补丁长期保留在正式代码中。
|
||||
|
||||
```csharp
|
||||
// ❌ 错误:为掩盖问题长期保留的强制调用
|
||||
DoNormalUpdate();
|
||||
ForceRefreshAgain(); // 只是为了“看起来好了”
|
||||
|
||||
// ✅ 正确:先定位根因,再移除临时补丁
|
||||
DoNormalUpdate();
|
||||
```
|
||||
|
||||
### 单位系统 - 极其重要
|
||||
|
||||
**所有网格地图和路径规划计算必须使用模型单位,严禁混用米制单位。**
|
||||
|
||||
@ -334,11 +334,14 @@
|
||||
<Compile Include="src\Utils\CoordinateSystem\CoordinateSystemType.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\ICoordinateSystem.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\CanonicalRailOffsetResolver.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalRailPoseBuilder.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\CanonicalTrackedPositionResolver.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\HoistingCoordinateHelper.cs" />
|
||||
<Compile Include="src\Utils\CoordinateSystem\LocalAxisDirection.cs" />
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
@ -55,6 +56,52 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
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)
|
||||
{
|
||||
switch (column)
|
||||
@ -79,5 +126,32 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
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);
|
||||
}
|
||||
|
||||
[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]
|
||||
public void PreferredNormal_ShouldOverrideCanonicalUpForRailFrame()
|
||||
{
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using Autodesk.Navisworks.Api;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
@ -143,6 +144,20 @@ namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
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)
|
||||
{
|
||||
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.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';" ^
|
||||
"$targetDir = [System.IO.Path]::GetFullPath('%TARGET_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) {" ^
|
||||
" if (-not (Test-Path $path)) { return $true }" ^
|
||||
" try {" ^
|
||||
@ -39,14 +54,22 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"foreach ($keyFile in $keyTargetFiles) { Wait-FileUnlocked $keyFile $deadline }" ^
|
||||
"" ^
|
||||
"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')) {" ^
|
||||
" New-Item -ItemType Directory -Force -Path (Join-Path $targetDir 'resources') | Out-Null;" ^
|
||||
" Copy-Item (Join-Path $buildDir 'resources\\*') (Join-Path $targetDir 'resources') -Recurse -Force;" ^
|
||||
"}" ^
|
||||
"" ^
|
||||
"$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')) {" ^
|
||||
" $sourceFiles += Get-ChildItem (Join-Path $buildDir 'resources') -File;" ^
|
||||
"}" ^
|
||||
|
||||
@ -41,6 +41,7 @@
|
||||
- Navisworks 世界坐标统一视为**外部坐标**
|
||||
- 程序内部统一使用一套**规范内部坐标**
|
||||
- 工程语义使用独立的**业务基准坐标**
|
||||
- 只有插件自带资源才允许引入**资产坐标系**
|
||||
- 坐标转换只发生在少数边界入口
|
||||
- 业务逻辑禁止直接依赖宿主坐标语义
|
||||
|
||||
@ -95,6 +96,7 @@ flowchart LR
|
||||
特点:
|
||||
|
||||
- 是程序的输入/输出坐标
|
||||
- UI 文本框、对话框、日志、鼠标拾取结果,统一按宿主坐标系解释和显示
|
||||
- 可能来自 `Y-up` 项目,也可能来自 `Z-up` 项目
|
||||
- 不应直接当成内部计算坐标
|
||||
|
||||
@ -118,6 +120,29 @@ flowchart LR
|
||||
- 项目 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”的旧假设。
|
||||
|
||||
### 3.4 模型局部轴约定是独立层
|
||||
### 3.4 资产坐标系是独立层
|
||||
|
||||
除了宿主坐标系和内部统一坐标系,还必须区分**模型局部轴约定**。
|
||||
除了宿主坐标系和内部统一坐标系,还必须区分**资产坐标系**。
|
||||
|
||||
注意,这一层**不是所有模型都有**。当前项目里只有插件自带资源需要它:
|
||||
|
||||
- 虚拟物体资源
|
||||
- 单位圆柱体参考杆资源
|
||||
|
||||
这是另一层独立定义,至少包含:
|
||||
|
||||
- `LocalForwardAxis`
|
||||
- `LocalUpAxis`
|
||||
- `AssetForwardAxis`
|
||||
- `AssetUpAxis`
|
||||
|
||||
例如:
|
||||
|
||||
- 虚拟物体资源通常按程序约定构建,可能是 `Local X = Forward, Local Z = Up`
|
||||
- 真实模型如果来自 `Y-up` 项目,则很可能是 `Local X = Forward, Local Y = Up`
|
||||
- 虚拟物体资源通常按程序约定构建,可能是 `Asset X = Forward, Asset Z = Up`
|
||||
- 单位圆柱体参考杆资源当前约定为 `Asset X = 杆轴方向, Asset Z = 截面 Up`
|
||||
|
||||
而对于客户真实模型:
|
||||
|
||||
- 不单独引入“资产坐标系”概念
|
||||
- UI 和数据输入输出仍只认宿主坐标系
|
||||
- 内部算法如需统一姿态,先进入 Canonical Space,再叠加业务参考信息
|
||||
|
||||
这意味着:
|
||||
|
||||
- 即使宿主坐标系转换已经正确
|
||||
- 如果模型局部轴约定没有显式处理
|
||||
- 如果资产坐标系语义没有显式处理
|
||||
- 动画和姿态仍然会出现“路径对了、通行空间对了、模型自己站歪了”的现象
|
||||
|
||||
因此,后续姿态系统必须显式区分:
|
||||
@ -179,7 +215,7 @@ flowchart LR
|
||||
1. 宿主坐标系定义
|
||||
2. 内部统一坐标系定义
|
||||
3. 业务基准定义
|
||||
4. 模型局部轴约定
|
||||
4. 资产坐标系约定(仅限虚拟物体和单位圆柱体等插件自带资源)
|
||||
|
||||
### 3.5 Rail 局部坐标系与动画跟踪点
|
||||
|
||||
@ -599,6 +635,8 @@ WPF 插件的最终部署,必须依赖完整主项目构建产物,而不应
|
||||
- **Navisworks 世界坐标统一视为外部坐标**
|
||||
- **程序内部统一使用 Canonical Space(Z-up)**
|
||||
- **业务基准点单独建模**
|
||||
- **UI 输入输出统一使用宿主坐标系**
|
||||
- **资产坐标系只属于插件自带资源,不得泛化成“所有模型都有本地轴”**
|
||||
- **坐标转换只发生在边界层**
|
||||
|
||||
这是一条更接近业界三维软件成熟做法的路线。
|
||||
|
||||
@ -2,9 +2,14 @@
|
||||
|
||||
## 功能点
|
||||
|
||||
### [2026/3/20]
|
||||
|
||||
1. [x] (优化)实现Yup模型兼容
|
||||
2. [x] (功能)运动物体在起点支持三个方向的旋转
|
||||
|
||||
### [2026/3/18]
|
||||
|
||||
1. [x](功能)增加轨上路径(角度可倾斜)
|
||||
1. [x] (功能)增加终端仿真需要的轨上和轨下路径(角度可倾斜)
|
||||
|
||||
### [2026/3/11]
|
||||
|
||||
|
||||
@ -88,12 +88,19 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
}
|
||||
|
||||
internal enum AnimatedObjectMode
|
||||
{
|
||||
None,
|
||||
RealObject,
|
||||
VirtualObject
|
||||
}
|
||||
|
||||
public class PathAnimationManager
|
||||
{
|
||||
private static PathAnimationManager _instance;
|
||||
private static readonly Dictionary<string, int> _animationHashToRecordId = new Dictionary<string, int>(); // 动画配置哈希到检测记录ID的映射
|
||||
private ModelItem _animatedObject;
|
||||
private bool _isVirtualObject = false; // 是否使用虚拟物体
|
||||
private AnimatedObjectMode _animatedObjectMode = AnimatedObjectMode.None;
|
||||
private double _virtualObjectLength = 0; // 虚拟物体长度(模型单位)
|
||||
private double _virtualObjectWidth = 0; // 虚拟物体宽度(模型单位)
|
||||
private double _virtualObjectHeight = 0; // 虚拟物体高度(模型单位)
|
||||
@ -105,7 +112,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
private bool _manualCollisionOverrideEnabled = false;
|
||||
|
||||
// === 角度修正 ===
|
||||
private double _objectRotationCorrection = 0.0; // 物体角度修正值(度,顺时针)
|
||||
private LocalEulerRotationCorrection _objectRotationCorrection = LocalEulerRotationCorrection.Zero; // 物体绕宿主 X/Y/Z 轴的角度修正
|
||||
|
||||
// === 碰撞排除列表缓存管理(从LogisticsAnimationManager迁移)===
|
||||
private ModelItem _currentCachedAnimationObject;
|
||||
@ -182,6 +189,12 @@ namespace NavisworksTransport.Core.Animation
|
||||
private bool _savedObjectHasCustomRotation = 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>
|
||||
@ -397,7 +410,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
|
||||
// 🔥 支持虚拟物体的恢复
|
||||
ModelItem objectToRestore = null;
|
||||
bool isVirtual = _isVirtualObject;
|
||||
bool isVirtual = IsVirtualObjectMode;
|
||||
|
||||
if (isVirtual)
|
||||
{
|
||||
@ -458,7 +471,19 @@ namespace NavisworksTransport.Core.Animation
|
||||
public void SetAnimatedObject(ModelItem 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>
|
||||
@ -467,7 +492,9 @@ namespace NavisworksTransport.Core.Animation
|
||||
/// </summary>
|
||||
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; // 模型单位
|
||||
_virtualObjectWidth = width; // 模型单位
|
||||
_virtualObjectHeight = height; // 模型单位
|
||||
@ -592,8 +619,8 @@ namespace NavisworksTransport.Core.Animation
|
||||
{
|
||||
// 🔥 重要:先恢复物体到原始状态(CAD位置和原始朝向),确保每次计算都从相同的状态开始
|
||||
// 这样可以避免之前旋转导致的包围盒尺寸计算不准确的问题
|
||||
// 注意:也要处理虚拟物体(_isVirtualObject为true时_animatedObject可能为null)
|
||||
if (_animatedObject != null || _isVirtualObject)
|
||||
// 注意:也要处理虚拟物体(当前控制对象可能不在 _animatedObject 中)
|
||||
if (CurrentControlledObject != null || IsVirtualObjectMode)
|
||||
{
|
||||
RestoreObjectToCADPosition();
|
||||
LogManager.Info($"[移动到起点] 已恢复物体到原始状态, _currentYaw={_currentYaw * 180 / Math.PI:F2}°");
|
||||
@ -603,6 +630,12 @@ namespace NavisworksTransport.Core.Animation
|
||||
if (animatedObject != null)
|
||||
{
|
||||
_animatedObject = animatedObject;
|
||||
bool isVirtualObject =
|
||||
VirtualObjectManager.Instance.IsVirtualObjectActive &&
|
||||
ReferenceEquals(animatedObject, VirtualObjectManager.Instance.CurrentVirtualObject);
|
||||
_animatedObjectMode = isVirtualObject
|
||||
? AnimatedObjectMode.VirtualObject
|
||||
: AnimatedObjectMode.RealObject;
|
||||
_originalTransform = animatedObject.Transform;
|
||||
_originalCenter = animatedObject.BoundingBox().Center;
|
||||
_trackedPosition = GetTrackedObjectPosition(animatedObject);
|
||||
@ -611,6 +644,9 @@ namespace NavisworksTransport.Core.Animation
|
||||
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
|
||||
_trackedRotation = _originalTransform.Factor().Rotation;
|
||||
_hasTrackedRotation = true;
|
||||
|
||||
LogManager.Info(
|
||||
$"[移动到起点] 更新动画对象内部状态: 对象={animatedObject.DisplayName}, 虚拟物体={IsVirtualObjectMode}");
|
||||
}
|
||||
|
||||
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 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)
|
||||
@ -646,12 +682,12 @@ namespace NavisworksTransport.Core.Animation
|
||||
LogManager.Debug($"[移动到起点] 地面/空轨路径使用路径方向: {yaw * 180 / Math.PI:F2}度");
|
||||
}
|
||||
|
||||
// 应用角度修正值(顺时针,转换为弧度)
|
||||
if (_objectRotationCorrection != 0.0)
|
||||
// 旧 yaw 链仅保留给未进入完整三维姿态的遗留路径。
|
||||
if (!_objectRotationCorrection.IsZero)
|
||||
{
|
||||
double correctionRad = _objectRotationCorrection * Math.PI / 180.0;
|
||||
double correctionRad = _objectRotationCorrection.ZDegrees * Math.PI / 180.0;
|
||||
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}");
|
||||
|
||||
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
|
||||
@ -1548,7 +1584,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
{
|
||||
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 中调用,这里只是日志记录
|
||||
@ -1829,7 +1865,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
_detectionTolerance,
|
||||
_currentRouteId,
|
||||
_animatedObject,
|
||||
_isVirtualObject,
|
||||
IsVirtualObjectMode,
|
||||
_animationFrameRate,
|
||||
_animationDuration,
|
||||
_virtualObjectLength,
|
||||
@ -2411,8 +2447,8 @@ namespace NavisworksTransport.Core.Animation
|
||||
{
|
||||
try
|
||||
{
|
||||
bool isRailRealObject = _route?.PathType == PathType.Rail && !_isVirtualObject && _animatedObject != null;
|
||||
if (_isVirtualObject)
|
||||
bool isRailRealObject = _route?.PathType == PathType.Rail && IsRealObjectMode && _animatedObject != null;
|
||||
if (IsVirtualObjectMode)
|
||||
{
|
||||
VirtualObjectManager.Instance.MoveVirtualObject(newPosition, newRotation);
|
||||
}
|
||||
@ -2475,7 +2511,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
else if (_route?.PathType == PathType.Ground || _route?.PathType == PathType.Hoisting)
|
||||
{
|
||||
LogHostActualPoseAxes(_isVirtualObject ? VirtualObjectManager.Instance.CurrentVirtualObject : _animatedObject, "[平面姿态应用后宿主姿态]", false);
|
||||
LogHostActualPoseAxes(CurrentControlledObject, "[平面姿态应用后宿主姿态]", false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -2688,7 +2724,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_isVirtualObject)
|
||||
if (IsVirtualObjectMode)
|
||||
{
|
||||
return VirtualObjectManager.Instance.IsVirtualObjectActive &&
|
||||
ReferenceEquals(VirtualObjectManager.Instance.CurrentVirtualObject, obj);
|
||||
@ -2723,7 +2759,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
try
|
||||
{
|
||||
var originalAnimatedObject = _animatedObject;
|
||||
if (!_isVirtualObject)
|
||||
if (!IsVirtualObjectMode)
|
||||
{
|
||||
_animatedObject = obj;
|
||||
}
|
||||
@ -3243,7 +3279,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
/// </summary>
|
||||
private double GetAnimatedObjectHeight()
|
||||
{
|
||||
if (_isVirtualObject)
|
||||
if (IsVirtualObjectMode)
|
||||
{
|
||||
return _virtualObjectHeight;
|
||||
}
|
||||
@ -3334,7 +3370,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
|
||||
_pathName = pathName;
|
||||
_currentRouteId = routeId;
|
||||
_isVirtualObject = isVirtualObject; // 设置是否使用虚拟物体
|
||||
_animatedObjectMode = isVirtualObject ? AnimatedObjectMode.VirtualObject : AnimatedObjectMode.RealObject;
|
||||
_virtualObjectLength = virtualObjectLength; // 模型单位
|
||||
_virtualObjectWidth = virtualObjectWidth; // 模型单位
|
||||
_virtualObjectHeight = virtualObjectHeight; // 模型单位
|
||||
@ -3351,12 +3387,12 @@ namespace NavisworksTransport.Core.Animation
|
||||
|
||||
// 保持当前的 _currentYaw(因为物体可能已经被 MoveObjectToPathStart 旋转)
|
||||
// 不要从 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旋转到起点
|
||||
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);
|
||||
LogManager.Debug($"[CreateAnimation结束] _currentYaw之后={_currentYaw * 180 / Math.PI:F2}度");
|
||||
// 设置动画状态为Ready(动画已生成,可以播放)
|
||||
@ -3405,7 +3441,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
|
||||
private ModelAxisConvention GetCurrentRailModelAxisConvention()
|
||||
{
|
||||
if (_isVirtualObject)
|
||||
if (IsVirtualObjectMode)
|
||||
{
|
||||
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||
}
|
||||
@ -3413,14 +3449,14 @@ namespace NavisworksTransport.Core.Animation
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
var convention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
||||
LogManager.Info(
|
||||
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={_isVirtualObject}, " +
|
||||
$"[Rail姿态修正] Host={adapter.HostType}, 虚拟物体={IsVirtualObjectMode}, " +
|
||||
$"Forward={convention.ForwardAxis}, Up={convention.UpAxis}");
|
||||
return convention;
|
||||
}
|
||||
|
||||
private ModelAxisConvention GetCurrentModelAxisConvention()
|
||||
{
|
||||
if (_isVirtualObject)
|
||||
if (IsVirtualObjectMode)
|
||||
{
|
||||
return ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||
}
|
||||
@ -3487,6 +3523,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
Vector3 canonicalPrevious = ToNumerics(adapter.ToCanonicalPoint(previousPoint));
|
||||
Vector3 canonicalCurrent = ToNumerics(adapter.ToCanonicalPoint(currentPoint));
|
||||
Vector3 canonicalNext = ToNumerics(adapter.ToCanonicalPoint(nextPoint));
|
||||
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection);
|
||||
|
||||
Vector3 forward = canonicalNext - canonicalPrevious;
|
||||
if (forward.LengthSquared() < 1e-6f)
|
||||
@ -3494,12 +3531,11 @@ namespace NavisworksTransport.Core.Animation
|
||||
forward = canonicalNext - canonicalCurrent;
|
||||
}
|
||||
|
||||
forward = ApplyPlanarRotationCorrection(forward);
|
||||
|
||||
if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
|
||||
forward,
|
||||
HostCoordinateAdapter.CanonicalUpVector3,
|
||||
convention,
|
||||
correctionQuaternion,
|
||||
out var quaternion))
|
||||
{
|
||||
return false;
|
||||
@ -3516,12 +3552,12 @@ namespace NavisworksTransport.Core.Animation
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
var convention = GetCurrentModelAxisConvention();
|
||||
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(
|
||||
canonicalForward,
|
||||
HostCoordinateAdapter.CanonicalUpVector3,
|
||||
convention,
|
||||
correctionQuaternion,
|
||||
out var quaternion))
|
||||
{
|
||||
return false;
|
||||
@ -3531,25 +3567,10 @@ namespace NavisworksTransport.Core.Animation
|
||||
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>
|
||||
/// 设置物体角度修正值(度,顺时针)
|
||||
/// 设置物体绕宿主 X/Y/Z 轴的角度修正
|
||||
/// </summary>
|
||||
/// <param name="rotationCorrection">角度修正值(度)</param>
|
||||
public void SetObjectRotationCorrection(double rotationCorrection)
|
||||
public void SetObjectRotationCorrection(LocalEulerRotationCorrection rotationCorrection)
|
||||
{
|
||||
_objectRotationCorrection = rotationCorrection;
|
||||
|
||||
@ -3560,7 +3581,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
{
|
||||
// 重新计算并应用朝向
|
||||
MoveObjectToPathStart();
|
||||
LogManager.Info($"[角度修正] 物体角度已更新: {_objectRotationCorrection:F1}°");
|
||||
LogManager.Info($"[角度修正] 物体角度已更新: {_objectRotationCorrection}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -3570,13 +3591,36 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接设置物体角度修正值(不触发物体旋转)
|
||||
/// 保留给旧单轴调用方:按当前宿主 up 轴映射成三轴角度修正。
|
||||
/// </summary>
|
||||
/// <param name="rotationCorrection">角度修正值(度)</param>
|
||||
public void SetObjectRotationCorrectionDirect(double rotationCorrection)
|
||||
public void SetObjectRotationCorrection(double rotationCorrection)
|
||||
{
|
||||
SetObjectRotationCorrection(CreateLegacyUpAxisCorrection(rotationCorrection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接设置物体绕宿主 X/Y/Z 轴的角度修正(不触发物体旋转)
|
||||
/// </summary>
|
||||
public void SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection 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 动画实现方法
|
||||
@ -3732,11 +3776,6 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
|
||||
double actualYaw = frameData.YawRadians;
|
||||
if (_objectRotationCorrection != 0.0)
|
||||
{
|
||||
double correctionRad = _objectRotationCorrection * Math.PI / 180.0;
|
||||
actualYaw += correctionRad;
|
||||
}
|
||||
|
||||
if (frameData.HasCustomRotation)
|
||||
{
|
||||
@ -3858,7 +3897,7 @@ namespace NavisworksTransport.Core.Animation
|
||||
sb.Append("|");
|
||||
|
||||
// 包含角度修正(确保角度修正改变时重新检测)
|
||||
sb.Append($"RotationCorrection:{_objectRotationCorrection:F2}deg");
|
||||
sb.Append($"RotationCorrection:{_objectRotationCorrection}");
|
||||
sb.Append("|");
|
||||
|
||||
// 包含手工检测对象列表(确保手工指定模式的目标变化时重新检测)
|
||||
|
||||
@ -1886,6 +1886,19 @@ namespace NavisworksTransport
|
||||
{
|
||||
var startPoint = sortedPoints[i];
|
||||
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;
|
||||
@ -1911,11 +1924,7 @@ namespace NavisworksTransport
|
||||
|
||||
// 沿宿主 up 判断垂直段,避免把世界 Z 误当成向上轴
|
||||
var hostUp = GetHostUpVector();
|
||||
double dx = endPoint.Position.X - startPoint.Position.X;
|
||||
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 upDelta = segmentDx * hostUp.X + segmentDy * hostUp.Y + segmentDz * hostUp.Z;
|
||||
double horizontalDist = Math.Sqrt(Math.Max(0.0, segmentLength * segmentLength - upDelta * upDelta));
|
||||
// 如果沿宿主 up 的变化占主导(垂直段),或者是第一个/最后一个线段
|
||||
bool isZDominant = Math.Abs(upDelta) > horizontalDist * 2.0;
|
||||
|
||||
@ -213,7 +213,9 @@ namespace NavisworksTransport.Core
|
||||
try
|
||||
{
|
||||
LogVirtualObjectGeometry("[虚拟物体姿态] 应用前");
|
||||
ModelItemTransformHelper.MoveItemToPositionAndRotation(_virtualObjectModelItem, position, rotation);
|
||||
// 虚拟物体当前尺寸是通过永久变换中的缩放实现的。
|
||||
// 应用完整姿态时必须保留当前缩放,否则会破坏虚拟物体的资产姿态和几何语义。
|
||||
ModelItemTransformHelper.MoveItemToPositionAndRotationWithCurrentScale(_virtualObjectModelItem, position, rotation);
|
||||
var actualBounds = _virtualObjectModelItem.BoundingBox();
|
||||
Point3D actualCenter = actualBounds?.Center ?? new Point3D(0, 0, 0);
|
||||
LogManager.Info(
|
||||
|
||||
@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@ -355,7 +356,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
private double _safetyMarginInMeters; // 检测间隙(米),从路径编辑同步
|
||||
|
||||
// 角度修正相关字段
|
||||
private double _objectRotationCorrection; // 物体角度修正值(度,顺时针)
|
||||
private LocalEulerRotationCorrection _objectRotationCorrection = LocalEulerRotationCorrection.Zero; // 物体绕宿主 X/Y/Z 轴的角度修正
|
||||
|
||||
// 移动物体原始尺寸(米,在选择物体时保存)
|
||||
private double _objectOriginalLength; // 物体原始长度(X方向)
|
||||
@ -708,8 +709,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
VirtualObjectLengthInMeters, VirtualObjectWidthInMeters, VirtualObjectHeightInMeters);
|
||||
|
||||
// 重置角度修正值(虚拟物体重新选择时重置)
|
||||
ObjectRotationCorrection = 0.0;
|
||||
LogManager.Debug($"[切换虚拟物体] 已重置角度修正值为0度");
|
||||
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||
LogManager.Debug("[切换虚拟物体] 已重置角度修正值为0");
|
||||
|
||||
// 当切换到虚拟物体模式时,根据路径类型打开通行空间可视化
|
||||
UpdatePassageSpaceVisualization();
|
||||
@ -792,9 +793,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物体角度修正值(度,顺时针)
|
||||
/// 物体绕宿主 X/Y/Z 轴的角度修正。
|
||||
/// </summary>
|
||||
public double ObjectRotationCorrection
|
||||
public LocalEulerRotationCorrection ObjectRotationCorrection
|
||||
{
|
||||
get => _objectRotationCorrection;
|
||||
set
|
||||
@ -1261,8 +1262,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
SelectAnimatedObjectCommand = new RelayCommand(() =>
|
||||
{
|
||||
// 重置角度修正值(每个物体的旋转独立)
|
||||
ObjectRotationCorrection = 0.0;
|
||||
LogManager.Debug($"[选择物体] 已重置角度修正值为0度");
|
||||
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||
LogManager.Debug("[选择物体] 已重置角度修正值为0");
|
||||
ExecuteSelectAnimatedObject();
|
||||
});
|
||||
ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
|
||||
@ -1714,19 +1715,30 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
// 3. 设置新物体(会触发UpdatePassageSpaceVisualization)
|
||||
SelectedAnimatedObject = newObject;
|
||||
_pathAnimationManager?.SetAnimatedObject(newObject);
|
||||
LogManager.Info($"已选择移动物体: {SelectedAnimatedObject.DisplayName}");
|
||||
|
||||
// 选择实体物体意味着切换到实体模式,避免后续起点同步仍走虚拟物体分支。
|
||||
if (UseVirtualObject)
|
||||
{
|
||||
UseVirtualObject = false;
|
||||
LogManager.Debug("[选择物体] 已切换到实体物体模式");
|
||||
}
|
||||
|
||||
// 只有选择不同的物体时,才重置角度修正值
|
||||
if (!isSameObject)
|
||||
{
|
||||
// 重置 ViewModel 中的角度修正值(会自动同步到 PathAnimationManager)
|
||||
ObjectRotationCorrection = 0.0;
|
||||
LogManager.Debug($"[选择物体] 已重置角度修正值为0度(新物体)");
|
||||
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||
LogManager.Debug("[选择物体] 已重置角度修正值为0(新物体)");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Debug($"[选择物体] 保持当前角度修正值(同一物体)");
|
||||
}
|
||||
|
||||
// 显式同步一次到当前路径起点,避免依赖 setter 分支导致真实物体停留在 CAD 原位。
|
||||
MoveAnimatedObjectToPathStart();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -2151,8 +2163,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
// 2. 重置角度修正值为0
|
||||
ObjectRotationCorrection = 0.0;
|
||||
LogManager.Debug("[清除物体] 已重置角度修正值为0度");
|
||||
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||
LogManager.Debug("[清除物体] 已重置角度修正值为0");
|
||||
|
||||
// 3. 重置属性
|
||||
SelectedAnimatedObject = null;
|
||||
@ -2182,9 +2194,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
var dialog = new Views.EditRotationWindow(_objectRotationCorrection);
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
ObjectRotationCorrection = dialog.RotationAngle;
|
||||
LogManager.Info($"物体角度修正已更新: {_objectRotationCorrection:F1}°");
|
||||
UpdateMainStatus($"物体角度修正: {_objectRotationCorrection:F1}°");
|
||||
ObjectRotationCorrection = dialog.RotationCorrection;
|
||||
LogManager.Info($"物体角度修正已更新: {_objectRotationCorrection}");
|
||||
UpdateMainStatus($"物体角度修正: {_objectRotationCorrection}");
|
||||
|
||||
// 应用角度修正到物体
|
||||
UpdateObjectRotation();
|
||||
@ -2211,7 +2223,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
// 将角度修正传递给动画管理器
|
||||
_pathAnimationManager.SetObjectRotationCorrection(_objectRotationCorrection);
|
||||
LogManager.Debug($"[角度修正] 已更新物体旋转: {_objectRotationCorrection:F1}°");
|
||||
LogManager.Debug($"[角度修正] 已更新物体旋转: {_objectRotationCorrection}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -3191,7 +3203,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
IsVirtualObject = UseVirtualObject,
|
||||
AnimatedObjectName = UseVirtualObject ? "虚拟物体" : SelectedAnimatedObject?.DisplayName,
|
||||
DetectAllObjects = !IsManualCollisionTargetEnabled,
|
||||
ObjectRotationCorrection = _objectRotationCorrection,
|
||||
ObjectRotationCorrection = _objectRotationCorrection.ZDegrees,
|
||||
Description = $"{scenarioName}于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
|
||||
TestName = testName,
|
||||
TestTime = DateTime.Now,
|
||||
@ -3356,7 +3368,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
UseVirtualObject ? VirtualObjectLengthInMeters * factor : (double?)null, // 米 → 模型单位
|
||||
UseVirtualObject ? VirtualObjectWidthInMeters * factor : (double?)null,
|
||||
UseVirtualObject ? VirtualObjectHeightInMeters * factor : (double?)null,
|
||||
_objectRotationCorrection,
|
||||
_objectRotationCorrection.ZDegrees,
|
||||
IsManualCollisionTargetEnabled
|
||||
);
|
||||
|
||||
@ -4302,54 +4314,56 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算旋转后的有效宽度和长度
|
||||
/// 计算本地三轴预旋转后的有效尺寸
|
||||
/// 返回值为模型单位(与Navisworks API一致)
|
||||
/// </summary>
|
||||
/// <returns>(有效宽度, 有效长度) - 模型单位</returns>
|
||||
private (double effectiveWidth, double effectiveLength) CalculateRotatedDimensions()
|
||||
/// <returns>(沿路径, 垂直路径, 法线) - 模型单位</returns>
|
||||
private (double effectiveAlongPath, double effectiveAcrossPath, double effectiveNormalToPath) CalculateRotatedDimensions()
|
||||
{
|
||||
double baseLengthMeters, baseWidthMeters;
|
||||
double forwardSize;
|
||||
double sideSize;
|
||||
double upSize;
|
||||
ModelAxisConvention axisConvention;
|
||||
|
||||
if (UseVirtualObject)
|
||||
{
|
||||
baseLengthMeters = VirtualObjectLengthInMeters;
|
||||
baseWidthMeters = VirtualObjectWidthInMeters;
|
||||
double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||||
forwardSize = VirtualObjectLengthInMeters * metersToUnits;
|
||||
sideSize = VirtualObjectWidthInMeters * metersToUnits;
|
||||
upSize = VirtualObjectHeightInMeters * metersToUnits;
|
||||
axisConvention = ModelAxisConvention.CreateVirtualObjectAssetConvention();
|
||||
}
|
||||
else if (SelectedAnimatedObject != null)
|
||||
{
|
||||
// 使用保存的原始尺寸(米),避免物体旋转后包围盒尺寸变化
|
||||
baseLengthMeters = _objectOriginalLength;
|
||||
baseWidthMeters = _objectOriginalWidth;
|
||||
double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||||
forwardSize = _objectOriginalLength * metersToUnits;
|
||||
sideSize = _objectOriginalWidth * metersToUnits;
|
||||
upSize = _objectOriginalHeight * metersToUnits;
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
axisConvention = CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail
|
||||
? ModelAxisConvention.CreateRailAssetConvention()
|
||||
: ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (0, 0);
|
||||
return (0, 0, 0);
|
||||
}
|
||||
|
||||
// 转换为模型单位
|
||||
var metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||||
double baseLength = baseLengthMeters * metersToUnits;
|
||||
double baseWidth = baseWidthMeters * metersToUnits;
|
||||
Quaternion correctionQuaternion = CoordinateSystemManager.Instance.CreateHostAdapter()
|
||||
.CreateCanonicalRotationCorrection(_objectRotationCorrection);
|
||||
var result = RotatedObjectExtentHelper.CalculateProjectedSemanticExtents(
|
||||
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}");
|
||||
|
||||
// 如果没有角度修正,直接返回原始尺寸
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
if (renderPlugin == null)
|
||||
@ -4383,8 +4397,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// 将安全间隙从米转换为模型单位
|
||||
var metersToUnitsPassage = UnitsConverter.GetMetersToUnitsConversionFactor();
|
||||
double safetyMargin = _safetyMarginInMeters * metersToUnitsPassage;
|
||||
double virtualObjectHeight = VirtualObjectHeightInMeters * metersToUnitsPassage;
|
||||
|
||||
if (UseVirtualObject)
|
||||
{
|
||||
// 虚拟物体的xyz定义:X方向 = 长度(前进方向),Y方向 = 宽度(侧面),Z方向 = 高度
|
||||
@ -4392,11 +4404,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
// 空中路径(空轨或吊装):高度上下都加间隙
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||
passageNormalToPath = virtualObjectHeight + 2 * safetyMargin; // Z方向(高度)+ 2*间隙(法线方向)
|
||||
passageNormalToPath = effectiveHeight + 2 * safetyMargin; // 法线方向高度 + 2*间隙
|
||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||
// 吊装路径分段参数
|
||||
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");
|
||||
}
|
||||
else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground)
|
||||
@ -4404,7 +4416,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// 地面路径:物体的长度方向(X轴,前进方向)朝向路径方向
|
||||
// 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面)
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||
passageNormalToPath = virtualObjectHeight + safetyMargin; // Z方向(高度)+ 间隙(法线方向,只有上方)
|
||||
passageNormalToPath = effectiveHeight + safetyMargin; // 法线方向高度 + 间隙(法线方向,只有上方)
|
||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||
// 地面路径不需要分段参数
|
||||
passageNormalToPathVertical = passageNormalToPath;
|
||||
@ -4419,7 +4431,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// - 轨下安装:顶面贴路径,底面留间隙
|
||||
// - 轨上安装:底面贴路径,顶面留间隙
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||
passageNormalToPath = virtualObjectHeight + safetyMargin; // 法线方向单侧留间隙
|
||||
passageNormalToPath = effectiveHeight + safetyMargin; // 法线方向单侧留间隙
|
||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||
// 空轨路径不需要分段参数
|
||||
passageNormalToPathVertical = passageNormalToPath;
|
||||
@ -4429,26 +4441,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
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)
|
||||
{
|
||||
// 空中路径(空轨或吊装):
|
||||
// 高度上下都加间隙
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||
passageNormalToPath = sizeNormalToPath + 2 * safetyMargin; // 局部up方向高度 + 2*间隙(法线方向)
|
||||
passageNormalToPath = effectiveHeight + 2 * safetyMargin; // 局部up方向高度 + 2*间隙(法线方向)
|
||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||
// 吊装路径分段参数
|
||||
passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙
|
||||
passageNormalToPathHorizontal = sizeNormalToPath + 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");
|
||||
passageNormalToPathHorizontal = effectiveHeight + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙
|
||||
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)
|
||||
{
|
||||
@ -4456,12 +4460,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// 物体的长度方向(X轴,前进方向)朝向路径方向
|
||||
// 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面)
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||
passageNormalToPath = sizeNormalToPath + safetyMargin; // 局部up方向高度 + 间隙(法线方向,只有上方)
|
||||
passageNormalToPath = effectiveHeight + safetyMargin; // 局部up方向高度 + 间隙(法线方向,只有上方)
|
||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||
// 地面路径不需要分段参数
|
||||
passageNormalToPathVertical = 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
|
||||
{
|
||||
@ -4472,11 +4476,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// - 轨下安装:顶面贴路径,底面留间隙
|
||||
// - 轨上安装:底面贴路径,顶面留间隙
|
||||
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
|
||||
passageNormalToPath = sizeNormalToPath + safetyMargin; // 局部up方向高度 + 单侧间隙(法线方向)
|
||||
passageNormalToPath = effectiveHeight + safetyMargin; // 局部up方向高度 + 单侧间隙(法线方向)
|
||||
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
|
||||
passageNormalToPathVertical = 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
|
||||
@ -4950,8 +4954,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
// 重置角度修正值为0
|
||||
ObjectRotationCorrection = 0.0;
|
||||
LogManager.Debug("[重置状态] 已重置角度修正值为0度");
|
||||
ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
|
||||
LogManager.Debug("[重置状态] 已重置角度修正值为0");
|
||||
|
||||
// 清空选中对象
|
||||
SelectedAnimatedObject = null;
|
||||
@ -5261,7 +5265,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
DetectAllObjects = !IsManualCollisionTargetEnabled,
|
||||
|
||||
// 角度修正配置
|
||||
ObjectRotationCorrection = _objectRotationCorrection,
|
||||
ObjectRotationCorrection = _objectRotationCorrection.ZDegrees,
|
||||
|
||||
// 🔥 关联的检测记录ID
|
||||
DetectionRecordId = detectionRecordId
|
||||
@ -5346,7 +5350,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
$"虚拟物体: {queueItem.IsVirtualObject}, " +
|
||||
$"帧率: {queueItem.FrameRate}, " +
|
||||
$"时长: {queueItem.DurationSeconds:F2}秒, " +
|
||||
$"角度修正: {queueItem.ObjectRotationCorrection:F1}°, " +
|
||||
$"角度修正: {_objectRotationCorrection}, " +
|
||||
$"ID: {itemId}");
|
||||
|
||||
UpdateMainStatus($"已添加到批处理队列: {CurrentPathRoute.Name}");
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditRotationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="调整物体角度" Height="320" Width="400"
|
||||
Title="调整物体角度" Height="360" Width="430"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="NoResize"
|
||||
ShowInTaskbar="False"
|
||||
@ -26,23 +26,67 @@
|
||||
<Border Grid.Row="0" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,0,0,1" Padding="20,15">
|
||||
<StackPanel>
|
||||
<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>
|
||||
</Border>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<Border Grid.Row="1" Padding="20,15">
|
||||
<StackPanel>
|
||||
<!-- 角度输入 -->
|
||||
<Grid Margin="0,0,0,12">
|
||||
<!-- X轴 -->
|
||||
<Grid Margin="0,0,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="110"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="旋转角度:" VerticalAlignment="Center" FontSize="11" Foreground="#FF333333"/>
|
||||
<TextBlock Text="绕 X 轴:" VerticalAlignment="Center" FontSize="11" Foreground="#FF333333"/>
|
||||
<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"
|
||||
VerticalContentAlignment="Center"
|
||||
Padding="8,0"
|
||||
@ -53,7 +97,7 @@
|
||||
</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">
|
||||
<Button Content="0°"
|
||||
Click="OnQuickAngleClick"
|
||||
|
||||
@ -2,33 +2,80 @@ using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
public partial class EditRotationWindow : Window, INotifyPropertyChanged
|
||||
{
|
||||
private double _rotationAngle;
|
||||
|
||||
public double RotationAngle
|
||||
private enum RotationAxisTarget
|
||||
{
|
||||
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
|
||||
{
|
||||
if (_rotationAngle != value)
|
||||
if (_rotationXDegrees != value)
|
||||
{
|
||||
_rotationAngle = value;
|
||||
_rotationXDegrees = value;
|
||||
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
|
||||
{
|
||||
InitializeComponent();
|
||||
RotationAngle = currentAngle;
|
||||
RotationXDegrees = currentRotation.XDegrees;
|
||||
RotationYDegrees = currentRotation.YDegrees;
|
||||
RotationZDegrees = currentRotation.ZDegrees;
|
||||
DataContext = this;
|
||||
Loaded += (sender, args) => ZAxisTextBox.Focus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -39,34 +86,57 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
|
||||
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}");
|
||||
|
||||
if (button.Tag != null)
|
||||
{
|
||||
double? parsedAngle = null;
|
||||
if (button.Tag is int intTag)
|
||||
{
|
||||
RotationAngle = intTag;
|
||||
LogManager.Debug($"[快捷角度] 设置角度为: {intTag}°");
|
||||
parsedAngle = intTag;
|
||||
}
|
||||
else if (button.Tag is string stringTag && double.TryParse(stringTag, out double angle))
|
||||
{
|
||||
RotationAngle = angle;
|
||||
LogManager.Debug($"[快捷角度] 设置角度为: {angle}°");
|
||||
parsedAngle = angle;
|
||||
}
|
||||
else if (button.Tag is double doubleTag)
|
||||
{
|
||||
RotationAngle = doubleTag;
|
||||
LogManager.Debug($"[快捷角度] 设置角度为: {doubleTag}°");
|
||||
parsedAngle = 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)
|
||||
{
|
||||
RotationAngle = 0.0;
|
||||
RotationXDegrees = 0.0;
|
||||
RotationYDegrees = 0.0;
|
||||
RotationZDegrees = 0.0;
|
||||
}
|
||||
|
||||
private void OnConfirmClick(object sender, RoutedEventArgs e)
|
||||
@ -87,5 +157,22 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
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,
|
||||
ModelAxisConvention convention,
|
||||
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)
|
||||
{
|
||||
@ -50,7 +80,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,7 +161,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
canonicalUp,
|
||||
convention,
|
||||
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);
|
||||
}
|
||||
|
||||
@ -174,6 +194,27 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
Vector3? preferredNormal,
|
||||
double localUpRotationDegrees,
|
||||
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;
|
||||
|
||||
@ -195,67 +236,22 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
float correctionRadians = (float)(localUpRotationDegrees * Math.PI / 180.0);
|
||||
Quaternion localPreRotation = Quaternion.CreateFromAxisAngle(
|
||||
Vector3.Normalize(convention.UpUnitVector),
|
||||
correctionRadians);
|
||||
|
||||
Vector3 correctedLocalForward = Vector3.Normalize(Vector3.Transform(convention.ForwardUnitVector, localPreRotation));
|
||||
Vector3 correctedLocalUp = Vector3.Normalize(Vector3.Transform(convention.UpUnitVector, localPreRotation));
|
||||
rotation = CreateQuaternionFromLocalAxes(correctedLocalForward, correctedLocalUp, forward, up);
|
||||
LocalAxisPoseBuilder.ApplyLocalPreRotation(
|
||||
convention,
|
||||
correctionQuaternion,
|
||||
out var correctedLocalForward,
|
||||
out var correctedLocalUp);
|
||||
rotation = LocalAxisPoseBuilder.CreateQuaternionFromLocalAxes(correctedLocalForward, correctedLocalUp, forward, up);
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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(
|
||||
double m00, double m01, double m02,
|
||||
double m10, double m11, double m12,
|
||||
@ -340,5 +361,10 @@ namespace NavisworksTransport.Utils.CoordinateSystem
|
||||
{
|
||||
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 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(
|
||||
Point3D startPoint,
|
||||
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,
|
||||
double localUpRotationDegrees,
|
||||
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;
|
||||
|
||||
@ -245,6 +264,7 @@ namespace NavisworksTransport.Utils
|
||||
var canonicalForward = frame.Forward;
|
||||
var canonicalLateral = frame.Lateral;
|
||||
var canonicalUp = frame.Normal;
|
||||
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(correction);
|
||||
Quaternion canonicalRotation = CanonicalRailPoseBuilder.TryCreateQuaternion(
|
||||
canonicalPreviousPoint,
|
||||
canonicalCurrentPoint,
|
||||
@ -252,7 +272,7 @@ namespace NavisworksTransport.Utils
|
||||
HostCoordinateAdapter.CanonicalUpVector3,
|
||||
axisConvention,
|
||||
ResolvePreferredNormal(route),
|
||||
localUpRotationDegrees,
|
||||
correctionQuaternion,
|
||||
out var correctedRotation)
|
||||
? correctedRotation
|
||||
: axisConvention.CreateQuaternion(canonicalForward, canonicalUp);
|
||||
@ -276,7 +296,7 @@ namespace NavisworksTransport.Utils
|
||||
$"Canonical侧向=({canonicalLateral.X:F4},{canonicalLateral.Y:F4},{canonicalLateral.Z:F4}), " +
|
||||
$"Canonical法向=({canonicalUp.X:F4},{canonicalUp.Y:F4},{canonicalUp.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;
|
||||
}
|
||||
|
||||
@ -356,6 +376,25 @@ namespace NavisworksTransport.Utils
|
||||
ModelAxisConvention axisConvention,
|
||||
double localUpRotationDegrees,
|
||||
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;
|
||||
LogRotationConstructorConventionOnce();
|
||||
@ -366,7 +405,7 @@ namespace NavisworksTransport.Utils
|
||||
currentPoint,
|
||||
nextPoint,
|
||||
axisConvention,
|
||||
localUpRotationDegrees,
|
||||
correction,
|
||||
out var linearTransform))
|
||||
{
|
||||
return false;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user