Finalize canonical rail coordinate integration

This commit is contained in:
tian 2026-03-21 19:19:13 +08:00
parent 3fd184934b
commit c53db7a6fd
15 changed files with 699 additions and 393 deletions

View File

@ -476,6 +476,33 @@ else
- 碰撞恢复
- 辅助线/通行空间渲染
阶段 2 的两个重要约束:
1. 真实物体物理尺寸必须固定
- 起点贴合、动画帧生成、通行空间尺寸、碰撞恢复
- 必须共用同一份固定物理尺寸
- 不允许在对象已经旋转后,再从当前世界 AABB 重新推导“真实高度”
否则会产生典型错误:
- 起点贴合正确
- 动画第一帧立刻出现固定间隙
2. 渲染与业务计算必须同时改
- 不能只改参考点、中心点、偏移量
- 还必须同步改渲染几何局部轴:
- `right`
- `up`
- `normal`
- `height axis`
否则会出现:
- 业务点位正确
- 但通行空间/辅助杆/长方体仍按旧 `Z-up` 轴构造
### 阶段 3
再逐步改造:
@ -485,6 +512,27 @@ else
- 坡度分析
- 旧二维路径辅助逻辑
### 部署约束
WPF 插件的最终部署,必须依赖完整主项目构建产物,而不应默认复用测试顺带生成的程序集。
原因:
- DLL 时间戳正确,不代表插件可运行
- 如果 `TransportPlugin.g.resources` 不完整Navisworks 在创建面板时仍会因缺少 `.baml` 崩溃
必须保证:
- 主项目完整构建成功
- 关键视图资源已经编入程序集
最低检查集:
- `LogisticsControlPanel.baml`
- `PathEditingView.baml`
- `AnimationControlView.baml`
- `LayerManagementView.baml`
## 9. 关键结论
本项目如果要长期稳定支持 `Y-up``Z-up` 项目,正确方向不是:

View File

@ -2437,4 +2437,90 @@ DialogHelper.ShowDialog(new MyDialog());
---
## 18. 基础框架改造的开发顺序
### 原则
当功能涉及以下任一类基础语义时,禁止直接在业务代码里试错式修补:
- 坐标系
- 三维姿态
- 几何局部轴
- 单位系统
- 动画/碰撞恢复的定位基准
正确顺序必须是:
1. 先抽出框架层/数学层
2. 先写最小可验证测试
3. 测试通过后再接回业务模块
### 原因
这类问题一旦直接在业务链路里反复修补,最容易出现:
- 虚拟物体正确、真实物体错误
- 起点正确、动画第一帧错误
- 动画正确、碰撞恢复错误
- 中心点正确、渲染几何局部轴错误
根因通常不是“又差一个补偿”,而是底层语义没有先被验证。
### 本项目实证经验
本项目在 Rail 三维姿态与坐标系改造中,采用以下顺序后明显更稳:
1. 先建立 `HostCoordinateAdapter / ModelAxisConvention / CanonicalRailPoseBuilder`
2. 再写单元测试验证 `Y-up / Z-up`、局部 `forward/up`、四元数/线性姿态
3. 最后再接入:
- 终端安装仿真
- Rail 姿态
- 动画播放
- 碰撞恢复
结论:
- 对基础语义改造,**测试先行接业务** 是推荐流程
- 没有测试支撑时,不应在业务代码里靠补偿和 fallback 猜结果
### 额外经验 1物体物理尺寸必须固定
对于真实物体动画,物体的物理尺寸一旦确定,就必须固定存储并重复使用:
- 起点贴合使用的尺寸
- 动画帧预计算使用的尺寸
- 通行空间尺寸语义
- 碰撞恢复使用的尺寸
这些必须来自**同一份固定物理尺寸**,不能在动画过程中反复从“当前已旋转的 AABB”重新推导。
否则会出现典型问题:
- 起点贴合正确
- 动画第一帧立刻拉开固定间隙
### 额外经验 2部署必须校验 WPF 资源完整性
对 WPF 插件来说,仅校验 DLL 被复制成功还不够。
必须确认构建产物包含完整的 `.g.resources / .baml` 资源;否则会出现:
- 插件 DLL 存在
- Navisworks 能加载程序集
- 但一打开面板就因缺少 `*.baml` 崩溃
本项目已实际遇到:
- `TransportPlugin.dll` 成功部署
- 但缺少 `LogisticsControlPanel.baml / PathEditingView.baml / AnimationControlView.baml`
- 最终表现为“插件布局丢失”或“手工打开插件窗口即崩溃”
因此经验是:
- 单元测试顺带产出的 DLL 不能默认用于最终部署
- 最终部署前应至少确认主项目完整构建成功
- 必要时校验关键 WPF 视图资源是否真的编入程序集
---
*本文档将随着项目发展持续更新,确保设计指导的有效性和实用性。*

View File

@ -240,6 +240,8 @@
验收:
- Rail 虚拟物体与真实模型在 Y-up / Z-up 项目中的姿态语义一致
- 真实物体起点贴合和动画第一帧使用同一份固定物理尺寸语义
- 不再允许从“已旋转后的当前 AABB”重新推导 Rail 物体高度
### Task 7.1 明确模型局部轴约定
@ -282,6 +284,11 @@
- Rail 碰撞报告恢复位置和动画实际姿态一致
- 二维路径不被三维逻辑污染
实施提示:
- 对真实物体,碰撞恢复和动画播放必须共用同一份固定物理尺寸语义
- 不允许恢复链路偷偷退回“重新读取当前 AABB 再算高度/底面”
### Task 9. 改造渲染层
文件:
@ -308,6 +315,29 @@
- `Normal` 是否仍偷用世界 `Z`
- 不能只修改 `ApplyVerticalOffset(...)` 或中心点偏移,而保留旧的 `XY + Z` 轴构造公式
### Task 9.1 通行空间与物体贴合语义统一
涉及文件:
- [AnimationControlViewModel.cs](/C:/Users/Tellme/apps/NavisworksTransport-rail-mount-modes/src/UI/WPF/ViewModels/AnimationControlViewModel.cs)
- [PathAnimationManager.cs](/C:/Users/Tellme/apps/NavisworksTransport-rail-mount-modes/src/Core/Animation/PathAnimationManager.cs)
目标:
- 通行空间显示、起点贴合、动画帧贴合使用统一尺寸语义
- Rail 轨上/轨下模式下,贴合面与留缝面保持一致
经验约束:
- `轨下安装`:顶面贴路径,底面留单侧间隙
- `轨上安装`:底面贴路径,顶面留单侧间隙
- 真实物体尺寸必须在选择物体时固定下来,并传入动画管理器
验收:
- 起点贴合正确后,动画第一帧不再出现固定间隙
- 通行空间与真实物体的贴合面语义一致
## 5. 已知问题与注意事项
### 5.1 当前不应优先展开的模块
@ -333,6 +363,32 @@
必须让错误显式暴露。
### 5.3 不能使用不完整构建产物部署 WPF 插件
实施过程中必须区分:
- 单元测试所需的程序集产物
- 可用于 Navisworks 运行的完整 WPF 插件产物
对于本项目:
- 仅有 `TransportPlugin.dll` 被复制成功,不代表插件可用
- 还必须确保 `TransportPlugin.g.resources` 中包含完整的主视图 `.baml`
最低要求:
- 主项目构建成功后再部署
- 如遇异常,应检查关键资源是否存在:
- `LogisticsControlPanel.baml`
- `PathEditingView.baml`
- `AnimationControlView.baml`
- `LayerManagementView.baml`
否则可能出现:
- 插件布局恢复失败
- 手工打开插件窗口即崩溃
## 6. 当前实施优先顺序
建议实际推进顺序如下:

View File

@ -1,117 +0,0 @@
{
"PathPlanningData": {
"version": "1.0",
"generator": "NavisworksTransport",
"timestamp": "2025-11-07T15:30:00",
"ProjectInfo": {
"name": "测试物流路径",
"description": "用于测试JSON导入导出功能",
"units": "meters",
"coordinateSystem": "Global"
},
"Routes": [
{
"id": "route001",
"name": "测试路径1",
"description": "从入口到仓库的路径",
"totalLength": 45.6,
"objectLimits": {
"maxLength": 2.0,
"maxWidth": 1.5,
"maxHeight": 2.0,
"safetyMargin": 0.25
},
"gridSize": 0.5,
"created": "2025-11-07T10:00:00",
"points": [
{
"id": "point001",
"name": "起点",
"type": "StartPoint",
"index": 0,
"x": 0.0,
"y": 0.0,
"z": 0.0,
"created": "2025-11-07T10:00:00"
},
{
"id": "point002",
"name": "路径点1",
"type": "WayPoint",
"index": 1,
"x": 10.0,
"y": 0.0,
"z": 0.0,
"created": "2025-11-07T10:00:01"
},
{
"id": "point003",
"name": "路径点2",
"type": "WayPoint",
"index": 2,
"x": 20.0,
"y": 5.0,
"z": 0.0,
"created": "2025-11-07T10:00:02"
},
{
"id": "point004",
"name": "终点",
"type": "EndPoint",
"index": 3,
"x": 30.0,
"y": 10.0,
"z": 0.0,
"created": "2025-11-07T10:00:03"
}
]
},
{
"id": "route002",
"name": "测试路径2",
"description": "从仓库到出口的路径",
"totalLength": 32.4,
"objectLimits": {
"maxLength": 2.0,
"maxWidth": 1.5,
"maxHeight": 2.0,
"safetyMargin": 0.25
},
"gridSize": 0.5,
"created": "2025-11-07T10:05:00",
"points": [
{
"id": "point005",
"name": "起点",
"type": "StartPoint",
"index": 0,
"x": 30.0,
"y": 10.0,
"z": 0.0,
"created": "2025-11-07T10:05:00"
},
{
"id": "point006",
"name": "路径点1",
"type": "WayPoint",
"index": 1,
"x": 25.0,
"y": 8.0,
"z": 0.0,
"created": "2025-11-07T10:05:01"
},
{
"id": "point007",
"name": "终点",
"type": "EndPoint",
"index": 2,
"x": 20.0,
"y": 5.0,
"z": 0.0,
"created": "2025-11-07T10:05:02"
}
]
}
]
}
}

View File

@ -9,6 +9,7 @@ using Autodesk.Navisworks.Api.Clash;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
using NavisworksTransport.UI.WPF.Views;
namespace NavisworksTransport.Commands
@ -367,11 +368,11 @@ namespace NavisworksTransport.Commands
var virtualObject = VirtualObjectManager.Instance.CurrentVirtualObject;
if (virtualObject != null)
{
var bounds = virtualObject.BoundingBox();
var pam = PathAnimationManager.GetInstance();
var position = GetAnimatedObjectReportedPosition(virtualObject, pam);
LogManager.Info(string.Format("[默认截图前] 虚拟物体位置: ({0:F2},{1:F2},{2:F2}), PAM记录朝向: {3:F2}°, customRotation={4}",
bounds.Center.X, bounds.Center.Y, bounds.Min.Z,
pam.CurrentYaw * 180 / Math.PI, pam.HasCurrentRotation));
position.X, position.Y, position.Z,
pam.CurrentYaw * 180 / Math.PI, pam.HasTrackedRotation));
}
else
{
@ -397,11 +398,11 @@ namespace NavisworksTransport.Commands
virtualObject = VirtualObjectManager.Instance.CurrentVirtualObject;
if (virtualObject != null)
{
var bounds = virtualObject.BoundingBox();
var pam = PathAnimationManager.GetInstance();
var position = GetAnimatedObjectReportedPosition(virtualObject, pam);
LogManager.Info(string.Format("[默认截图后] 虚拟物体位置: ({0:F2},{1:F2},{2:F2}), PAM记录朝向: {3:F2}°, customRotation={4}",
bounds.Center.X, bounds.Center.Y, bounds.Min.Z,
pam.CurrentYaw * 180 / Math.PI, pam.HasCurrentRotation));
position.X, position.Y, position.Z,
pam.CurrentYaw * 180 / Math.PI, pam.HasTrackedRotation));
}
if (screenshotPath != null)
@ -609,6 +610,23 @@ namespace NavisworksTransport.Commands
return PathPlanningResult<CollisionReportResult>.Success(result, message);
}
private static Point3D GetAnimatedObjectReportedPosition(ModelItem animatedObject, PathAnimationManager pam)
{
if (animatedObject == null)
{
return new Point3D(0, 0, 0);
}
if (pam != null && pam.ControlsAnimatedObject(animatedObject))
{
return pam.GetObjectCurrentPosition(animatedObject).Position;
}
var bounds = animatedObject.BoundingBox();
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
return ModelItemTransformHelper.GetHostBottomAnchorPoint(bounds, adapter);
}
/// <summary>

View File

@ -96,6 +96,9 @@ namespace NavisworksTransport.Core.Animation
private double _virtualObjectLength = 0; // 虚拟物体长度(模型单位)
private double _virtualObjectWidth = 0; // 虚拟物体宽度(模型单位)
private double _virtualObjectHeight = 0; // 虚拟物体高度(模型单位)
private double _realObjectLength = 0; // 真实物体长度(模型单位,固定物理尺寸)
private double _realObjectWidth = 0; // 真实物体宽度(模型单位,固定物理尺寸)
private double _realObjectHeight = 0; // 真实物体高度(模型单位,固定物理尺寸)
private List<Point3D> _pathPoints;
private List<ModelItem> _manualCollisionTargets = new List<ModelItem>();
private bool _manualCollisionOverrideEnabled = false;
@ -157,12 +160,12 @@ namespace NavisworksTransport.Core.Animation
private Transform3D _originalTransform;
private Point3D _originalCenter; // 存储部件的原始中心位置
private Point3D _currentPosition; // 存储部件的当前位置
private Point3D _trackedPosition; // 动画管理器内部跟踪的位置状态,不等于宿主即时读回位置
private AnimationState _currentState = AnimationState.Idle;
private double _pausedProgress = 0.0; // 暂停时的进度0-1之间
private double _currentYaw = 0.0; // 当前偏航角(弧度)
private Rotation3D _currentRotation = Rotation3D.Identity; // 当前完整姿态
private bool _hasCurrentRotation = false; // 当前是否使用完整姿态
private Rotation3D _trackedRotation = Rotation3D.Identity; // 动画管理器内部跟踪的完整姿态
private bool _hasTrackedRotation = false; // 当前是否使用完整姿态
// TimeLiner 集成
private TimeLinerIntegrationManager _timeLinerManager;
@ -422,11 +425,11 @@ namespace NavisworksTransport.Core.Animation
// 2. 重置内部跟踪变量(同步到原始状态)
// 注意ResetPermanentTransform 后物体的 Transform 属性会自动变回原始值
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(objectToRestore.Transform);
_currentRotation = objectToRestore.Transform.Factor().Rotation;
_hasCurrentRotation = true;
_trackedRotation = objectToRestore.Transform.Factor().Rotation;
_hasTrackedRotation = true;
var originalBoundingBox = objectToRestore.BoundingBox();
_currentPosition = GetTrackedObjectPosition(objectToRestore, _route?.PathType == PathType.Rail && !isVirtual);
_trackedPosition = GetTrackedObjectPosition(objectToRestore, _route?.PathType == PathType.Rail && !isVirtual);
string objectName = isVirtual ? "虚拟物体" : objectToRestore.DisplayName;
LogManager.Info($"[归位] {objectName} 已彻底恢复到原始位置, yaw={_currentYaw:F3}");
@ -469,6 +472,18 @@ namespace NavisworksTransport.Core.Animation
_virtualObjectHeight = height; // 模型单位
}
/// <summary>
/// 设置真实物体的固定物理尺寸(模型单位)。
/// 一旦动画开始生成,后续 Rail 帧和起点贴合应使用同一份尺寸语义,
/// 不能再从已经旋转后的当前 AABB 重新推导高度。
/// </summary>
public void SetRealObjectDimensions(double length, double width, double height)
{
_realObjectLength = length;
_realObjectWidth = width;
_realObjectHeight = height;
}
/// <summary>
/// 设置动画参数(批处理专用)
/// </summary>
@ -589,12 +604,12 @@ namespace NavisworksTransport.Core.Animation
_animatedObject = animatedObject;
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_currentPosition = GetTrackedObjectPosition(animatedObject, _route?.PathType == PathType.Rail && !_isVirtualObject);
_trackedPosition = GetTrackedObjectPosition(animatedObject, _route?.PathType == PathType.Rail && !_isVirtualObject);
// 统一逻辑:从当前 Transform 中提取实际朝向(无论虚拟物体还是普通物体)
_currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform);
_currentRotation = _originalTransform.Factor().Rotation;
_hasCurrentRotation = true;
_trackedRotation = _originalTransform.Factor().Rotation;
_hasTrackedRotation = true;
}
if (pathPoints != null)
@ -835,12 +850,18 @@ namespace NavisworksTransport.Core.Animation
}
// 3. 生成每一帧
double distancePerFrameInModelUnits = totalLengthInModelUnits / totalFrames;
double distancePerFrameInModelUnits = totalFrames > 1
? totalLengthInModelUnits / (totalFrames - 1)
: 0.0;
LogManager.Info($"总帧数: {totalFrames}, 总长度: {totalLengthInModelUnits / metersToModelUnits:F2}米, 每帧移动距离: {distancePerFrameInModelUnits / metersToModelUnits:F4}米");
for (int i = 0; i < totalFrames; i++)
{
double targetDistance = i * distancePerFrameInModelUnits; // 按距离采样(模型单位),不是按进度
double targetDistance = i * distancePerFrameInModelUnits; // 按距离采样(模型单位),最后一帧应精确到终点
if (targetDistance > totalLengthInModelUnits)
{
targetDistance = totalLengthInModelUnits;
}
Point3D framePosition;
Point3D previousFramePoint = Point3D.Origin;
@ -1091,10 +1112,10 @@ namespace NavisworksTransport.Core.Animation
(virtualBoundingBox.Min.Y + virtualBoundingBox.Max.Y + colliderBox.Min.Y + colliderBox.Max.Y) / 4,
(virtualBoundingBox.Min.Z + virtualBoundingBox.Max.Z + colliderBox.Min.Z + colliderBox.Max.Z) / 4
),
// 碰撞结果中的 Item1Position 必须与后续恢复链路的基准点语义一致:
// 碰撞结果中的 AnimatedObjectTrackedPosition 必须与后续恢复链路的动画跟踪点语义一致:
// 1. 二维 yaw 路径沿用成熟旧逻辑,记录包围盒中心;
// 2. 三维 customRotation 路径记录动画跟踪点framePosition
Item1Position = frame.HasCustomRotation
AnimatedObjectTrackedPosition = frame.HasCustomRotation
? new Point3D(
framePosition.X,
framePosition.Y,
@ -1104,9 +1125,9 @@ namespace NavisworksTransport.Core.Animation
framePosition.Y,
framePosition.Z + (virtualBoundingBox.Max.Z - virtualBoundingBox.Min.Z) / 2),
Item2Position = GetObjectPosition(collider),
Item1YawRadians = actualYawRadians, // 记录运动物体实际播放朝向
Item1Rotation = frame.HasCustomRotation ? frame.Rotation : Rotation3D.Identity,
Item1HasCustomRotation = frame.HasCustomRotation,
AnimatedObjectTrackedYawRadians = actualYawRadians, // 记录动画跟踪点对应的运动物体实际播放朝向
AnimatedObjectTrackedRotation = frame.HasCustomRotation ? frame.Rotation : Rotation3D.Identity,
AnimatedObjectHasTrackedRotation = frame.HasCustomRotation,
HasPositionInfo = true
};
@ -1116,7 +1137,7 @@ namespace NavisworksTransport.Core.Animation
$"原始Yaw={yawRadians * 180.0 / Math.PI:F2}°, " +
$"实际Yaw={actualYawRadians * 180.0 / Math.PI:F2}°, " +
$"customRotation={frame.HasCustomRotation}, " +
$"保存位置=({collisionResult.Item1Position.X:F3}, {collisionResult.Item1Position.Y:F3}, {collisionResult.Item1Position.Z:F3})");
$"保存位置=({collisionResult.AnimatedObjectTrackedPosition.X:F3}, {collisionResult.AnimatedObjectTrackedPosition.Y:F3}, {collisionResult.AnimatedObjectTrackedPosition.Z:F3})");
frame.Collisions.Add(collisionResult);
_allCollisionResults.Add(collisionResult);
@ -1130,7 +1151,7 @@ namespace NavisworksTransport.Core.Animation
var framesWithCollision = _animationFrames.Count(f => f.HasCollision);
var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count);
// 检查碰撞结果是否包含位置信息
var collisionsWithPosition = _allCollisionResults.Count(c => c.HasPositionInfo && c.Item1Position != null);
var collisionsWithPosition = _allCollisionResults.Count(c => c.HasPositionInfo && c.AnimatedObjectTrackedPosition != null);
LogManager.Info($"=== 预计算完成 ===");
LogManager.Info($"总帧数: {_animationFrames.Count}");
@ -1363,12 +1384,12 @@ namespace NavisworksTransport.Core.Animation
if (firstFrame.HasCustomRotation && _route?.PathType == PathType.Rail)
{
var dx = _currentPosition.X - firstFrame.Position.X;
var dy = _currentPosition.Y - firstFrame.Position.Y;
var dz = _currentPosition.Z - firstFrame.Position.Z;
var dx = _trackedPosition.X - firstFrame.Position.X;
var dy = _trackedPosition.Y - firstFrame.Position.Y;
var dz = _trackedPosition.Z - firstFrame.Position.Z;
distanceToStart = Math.Sqrt(dx * dx + dy * dy + dz * dz);
var currentLinear = new Transform3D(_currentRotation).Linear;
var currentLinear = new Transform3D(_trackedRotation).Linear;
var targetLinear = new Transform3D(firstFrame.Rotation).Linear;
double rotationDiff =
Math.Abs(currentLinear.Get(0, 0) - targetLinear.Get(0, 0)) +
@ -1701,6 +1722,7 @@ namespace NavisworksTransport.Core.Animation
// 动画自然结束时保持物体在最终位置(不移回起点)
LogManager.Info("动画播放自然结束,物体保持在最终位置");
LogRailEndAlignmentDiagnostics();
// 直接设置为完成状态,避免中间状态切换
SetState(AnimationState.Finished);
@ -1774,6 +1796,85 @@ namespace NavisworksTransport.Core.Animation
}
}
/// <summary>
/// 输出 Rail 路径动画终点对齐诊断。
/// 只做日志,不改变业务行为,用于确认:
/// 1. 路径终点锚点
/// 2. 最后一帧动画跟踪点
/// 3. 动画结束时的实际跟踪点
/// 4. 沿路径前进方向的终点偏差
/// </summary>
private void LogRailEndAlignmentDiagnostics()
{
try
{
if (_route == null || _route.PathType != PathType.Rail || _pathPoints == null || _pathPoints.Count < 2 || _animationFrames == null || _animationFrames.Count == 0)
{
return;
}
Point3D terminalAnchorPoint = _pathPoints[_pathPoints.Count - 1];
Point3D previousAnchorPoint = _pathPoints[_pathPoints.Count - 2];
var lastFrame = _animationFrames[_animationFrames.Count - 1];
Point3D finalTrackedPoint = _trackedPosition;
double objectHeight = GetAnimatedObjectHeight();
Point3D expectedTrackedEndPoint = RailPathPoseHelper.ResolveBottomPosition(
_route,
terminalAnchorPoint,
previousAnchorPoint,
terminalAnchorPoint,
objectHeight);
Vector3D forward = new Vector3D(
terminalAnchorPoint.X - previousAnchorPoint.X,
terminalAnchorPoint.Y - previousAnchorPoint.Y,
terminalAnchorPoint.Z - previousAnchorPoint.Z);
double forwardLength = Math.Sqrt(forward.X * forward.X + forward.Y * forward.Y + forward.Z * forward.Z);
if (forwardLength < 1e-9)
{
return;
}
Vector3D normalizedForward = new Vector3D(
forward.X / forwardLength,
forward.Y / forwardLength,
forward.Z / forwardLength);
Vector3D frameDelta = new Vector3D(
lastFrame.Position.X - expectedTrackedEndPoint.X,
lastFrame.Position.Y - expectedTrackedEndPoint.Y,
lastFrame.Position.Z - expectedTrackedEndPoint.Z);
Vector3D finalDelta = new Vector3D(
finalTrackedPoint.X - expectedTrackedEndPoint.X,
finalTrackedPoint.Y - expectedTrackedEndPoint.Y,
finalTrackedPoint.Z - expectedTrackedEndPoint.Z);
double frameForwardDelta = Dot(frameDelta, normalizedForward);
double finalForwardDelta = Dot(finalDelta, normalizedForward);
LogManager.Info(
$"[Rail终点诊断] 终点锚点=({terminalAnchorPoint.X:F3},{terminalAnchorPoint.Y:F3},{terminalAnchorPoint.Z:F3}), " +
$"期望跟踪点=({expectedTrackedEndPoint.X:F3},{expectedTrackedEndPoint.Y:F3},{expectedTrackedEndPoint.Z:F3}), " +
$"最后一帧跟踪点=({lastFrame.Position.X:F3},{lastFrame.Position.Y:F3},{lastFrame.Position.Z:F3}), " +
$"动画结束跟踪点=({finalTrackedPoint.X:F3},{finalTrackedPoint.Y:F3},{finalTrackedPoint.Z:F3})");
LogManager.Info(
$"[Rail终点诊断] 前进方向=({normalizedForward.X:F4},{normalizedForward.Y:F4},{normalizedForward.Z:F4}), " +
$"最后一帧偏差=({frameDelta.X:F3},{frameDelta.Y:F3},{frameDelta.Z:F3}), 沿前进轴={frameForwardDelta:F3}, " +
$"结束偏差=({finalDelta.X:F3},{finalDelta.Y:F3},{finalDelta.Z:F3}), 沿前进轴={finalForwardDelta:F3}");
}
catch (Exception ex)
{
LogManager.Warning($"[Rail终点诊断] 输出失败: {ex.Message}");
}
}
private static double Dot(Vector3D a, Vector3D b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
}
/// <summary>
/// 暂停动画
/// </summary>
@ -2147,9 +2248,9 @@ namespace NavisworksTransport.Core.Animation
// 计算平移和旋转的增量
var deltaPos = new Vector3D(
newPosition.X - _currentPosition.X,
newPosition.Y - _currentPosition.Y,
newPosition.Z - _currentPosition.Z
newPosition.X - _trackedPosition.X,
newPosition.Y - _trackedPosition.Y,
newPosition.Z - _trackedPosition.Z
);
Transform3D incrementalTransform;
@ -2164,18 +2265,18 @@ namespace NavisworksTransport.Core.Animation
//LogManager.Debug($"[UpdateObjectPosition] 当前yaw={_currentYaw * 180 / Math.PI:F2}度, 目标yaw={newYaw * 180 / Math.PI:F2}度, 旋转增量deltaYaw={deltaYaw * 180 / Math.PI:F2}度");
// 计算绕当前位置旋转的等效变换:
// 1. 如果绕原点旋转deltaYaw当前位置_currentPosition会移动到哪里
// 1. 如果绕原点旋转deltaYaw当前位置_trackedPosition会移动到哪里
double cos = Math.Cos(deltaYaw);
double sin = Math.Sin(deltaYaw);
double rotatedX = _currentPosition.X * cos - _currentPosition.Y * sin;
double rotatedY = _currentPosition.X * sin + _currentPosition.Y * cos;
double rotatedX = _trackedPosition.X * cos - _trackedPosition.Y * sin;
double rotatedY = _trackedPosition.X * sin + _trackedPosition.Y * cos;
// 2. 但我们希望物体绕自己旋转位置移动到newPosition
// 所以需要的平移 = newPosition - (旋转后的位置)
var compensatedTranslation = new Vector3D(
newPosition.X - rotatedX,
newPosition.Y - rotatedY,
newPosition.Z - _currentPosition.Z // Z保持deltaPos
newPosition.Z - _trackedPosition.Z // Z保持deltaPos
);
// 3. 组合:先旋转(绕原点),再平移(补偿+目标位置)
@ -2187,8 +2288,8 @@ namespace NavisworksTransport.Core.Animation
incrementalTransform = components.Combine();
_currentYaw = newYaw;
_currentRotation = Rotation3D.Identity;
_hasCurrentRotation = false;
_trackedRotation = Rotation3D.Identity;
_hasTrackedRotation = false;
}
else
{
@ -2200,8 +2301,8 @@ namespace NavisworksTransport.Core.Animation
// 应用增量变换false = 增量模式)
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
// 更新当前位置
_currentPosition = newPosition;
// 更新跟踪位置
_trackedPosition = newPosition;
}
catch (Exception ex)
{
@ -2223,16 +2324,28 @@ namespace NavisworksTransport.Core.Animation
}
else if (_animatedObject != null)
{
var currentRotation = _hasCurrentRotation
? _currentRotation
Point3D currentPositionForTransform = _trackedPosition;
var currentRotation = _hasTrackedRotation
? _trackedRotation
: _animatedObject.Transform.Factor().Rotation;
try
{
var actualHostPosition = GetTrackedObjectPosition(_animatedObject, _route?.PathType == PathType.Rail && !_isVirtualObject);
currentPositionForTransform = actualHostPosition;
LogManager.Info(
$"[动画姿态入口] {_animatedObject.DisplayName} 宿主即时读回点=({actualHostPosition.X:F3},{actualHostPosition.Y:F3},{actualHostPosition.Z:F3})");
}
catch (Exception ex)
{
LogManager.Warning($"[动画姿态入口] 读取宿主实际状态失败: {ex.Message}");
}
var currentLinear = new Transform3D(currentRotation).Linear;
var targetLinear = new Transform3D(newRotation).Linear;
LogManager.Info(
$"[动画姿态入口] {_animatedObject.DisplayName} 当前点=({_currentPosition.X:F3},{_currentPosition.Y:F3},{_currentPosition.Z:F3}), " +
$"[动画姿态入口] {_animatedObject.DisplayName} 跟踪点=({_trackedPosition.X:F3},{_trackedPosition.Y:F3},{_trackedPosition.Z:F3}), " +
$"目标点=({newPosition.X:F3},{newPosition.Y:F3},{newPosition.Z:F3})");
LogManager.Info(
$"[动画姿态入口] {_animatedObject.DisplayName} 当前姿态: " +
$"[动画姿态入口] {_animatedObject.DisplayName} 跟踪姿态: " +
$"X=({currentLinear.Get(0, 0):F4},{currentLinear.Get(1, 0):F4},{currentLinear.Get(2, 0):F4}), " +
$"Y=({currentLinear.Get(0, 1):F4},{currentLinear.Get(1, 1):F4},{currentLinear.Get(2, 1):F4}), " +
$"Z=({currentLinear.Get(0, 2):F4},{currentLinear.Get(1, 2):F4},{currentLinear.Get(2, 2):F4})");
@ -2243,7 +2356,7 @@ namespace NavisworksTransport.Core.Animation
$"Z=({targetLinear.Get(0, 2):F4},{targetLinear.Get(1, 2):F4},{targetLinear.Get(2, 2):F4})");
ModelItemTransformHelper.MoveItemIncrementallyToPositionAndRotation(
_animatedObject,
_currentPosition,
currentPositionForTransform,
currentRotation,
newPosition,
newRotation);
@ -2253,9 +2366,9 @@ namespace NavisworksTransport.Core.Animation
return;
}
_currentPosition = newPosition;
_currentRotation = newRotation;
_hasCurrentRotation = true;
_trackedPosition = newPosition;
_trackedRotation = newRotation;
_hasTrackedRotation = true;
_currentYaw = ModelItemTransformHelper.GetYawFromRotation(newRotation);
}
catch (Exception ex)
@ -2353,7 +2466,7 @@ namespace NavisworksTransport.Core.Animation
return (null, 0);
}
var position = _currentPosition;
var position = _trackedPosition;
// 🔥 关键:使用 _currentYaw实际当前朝向而不是 Transform 的 CAD 原始朝向
// Transform 返回的是 CAD 设计时的原始朝向,不是动画后的实际朝向
return (position, _currentYaw);
@ -2375,8 +2488,8 @@ namespace NavisworksTransport.Core.Animation
var (position, yaw) = GetObjectCurrentPosition(obj);
_savedObjectPosition = position;
_savedObjectYaw = yaw;
_savedObjectRotation = _currentRotation;
_savedObjectHasCustomRotation = _hasCurrentRotation;
_savedObjectRotation = _trackedRotation;
_savedObjectHasCustomRotation = _hasTrackedRotation;
_hasSavedObjectState = true;
LogManager.Info($"已保存物体状态: pos=({position.X:F2},{position.Y:F2},{position.Z:F2}), yaw={yaw * 180 / Math.PI:F2}度, customRotation={_savedObjectHasCustomRotation}");
}
@ -2409,10 +2522,10 @@ namespace NavisworksTransport.Core.Animation
var originalAnimatedObject = _animatedObject;
_animatedObject = obj;
_currentPosition = _savedObjectPosition;
_trackedPosition = _savedObjectPosition;
_currentYaw = _savedObjectYaw;
_currentRotation = _savedObjectRotation;
_hasCurrentRotation = _savedObjectHasCustomRotation;
_trackedRotation = _savedObjectRotation;
_hasTrackedRotation = _savedObjectHasCustomRotation;
if (_savedObjectHasCustomRotation)
{
@ -2753,7 +2866,7 @@ namespace NavisworksTransport.Core.Animation
// 清空对象引用
_animatedObject = null;
_pathPoints?.Clear();
_currentPosition = new Point3D(0, 0, 0);
_trackedPosition = new Point3D(0, 0, 0);
_originalCenter = new Point3D(0, 0, 0);
LogManager.Info("[PathAnimationManager] 对象引用已清理");
@ -2955,8 +3068,8 @@ namespace NavisworksTransport.Core.Animation
/// 用于保存物体当前状态因为Transform返回的是CAD原始值
/// </summary>
public double CurrentYaw => _currentYaw;
public Rotation3D CurrentRotation => _currentRotation;
public bool HasCurrentRotation => _hasCurrentRotation;
public Rotation3D TrackedRotation => _trackedRotation;
public bool HasTrackedRotation => _hasTrackedRotation;
/// <summary>
/// 获取当前动画物体高度(模型单位)
@ -2968,6 +3081,11 @@ namespace NavisworksTransport.Core.Animation
return _virtualObjectHeight;
}
if (_realObjectHeight > 0.0)
{
return _realObjectHeight;
}
if (_animatedObject == null)
{
throw new InvalidOperationException("动画对象为空,无法获取物体高度");
@ -3062,7 +3180,7 @@ namespace NavisworksTransport.Core.Animation
{
_originalTransform = animatedObject.Transform;
_originalCenter = animatedObject.BoundingBox().Center;
_currentPosition = GetTrackedObjectPosition(animatedObject, _route?.PathType == PathType.Rail && !_isVirtualObject);
_trackedPosition = GetTrackedObjectPosition(animatedObject, _route?.PathType == PathType.Rail && !_isVirtualObject);
// 保持当前的 _currentYaw因为物体可能已经被 MoveObjectToPathStart 旋转)
// 不要从 Transform 中提取,因为 Transform 返回的是原始值,不是当前值
@ -3087,13 +3205,8 @@ namespace NavisworksTransport.Core.Animation
}
var bounds = item.BoundingBox();
if (useStableRailAnchor)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
return ModelItemTransformHelper.GetHostBottomAnchorPoint(bounds, adapter);
}
return new Point3D(bounds.Center.X, bounds.Center.Y, bounds.Min.Z);
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
return ModelItemTransformHelper.GetHostBottomAnchorPoint(bounds, adapter);
}
/// <summary>

View File

@ -7,6 +7,7 @@ using Autodesk.Navisworks.Api.Clash;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport
{
@ -174,11 +175,11 @@ namespace NavisworksTransport
/// </summary>
private class CollisionPositionInfo
{
public Point3D Item1Position { get; set; }
public Point3D AnimatedObjectTrackedPosition { get; set; }
public Point3D Item2Position { get; set; }
public double Item1YawRadians { get; set; }
public Rotation3D Item1Rotation { get; set; }
public bool Item1HasCustomRotation { get; set; }
public double AnimatedObjectTrackedYawRadians { get; set; }
public Rotation3D AnimatedObjectTrackedRotation { get; set; }
public bool AnimatedObjectHasTrackedRotation { get; set; }
public bool HasPositionInfo { get; set; }
}
@ -271,20 +272,20 @@ namespace NavisworksTransport
};
// 保存碰撞时运动物体的位置和朝向(如果可用)
if (collision.HasPositionInfo && collision.Item1Position != null)
if (collision.HasPositionInfo && collision.AnimatedObjectTrackedPosition != null)
{
collisionRecord.Item1PosX = collision.Item1Position.X;
collisionRecord.Item1PosY = collision.Item1Position.Y;
collisionRecord.Item1PosZ = collision.Item1Position.Z;
collisionRecord.Item1YawRadians = collision.Item1YawRadians;
collisionRecord.Item1RotA = collision.Item1Rotation?.A;
collisionRecord.Item1RotB = collision.Item1Rotation?.B;
collisionRecord.Item1RotC = collision.Item1Rotation?.C;
collisionRecord.Item1RotD = collision.Item1Rotation?.D;
collisionRecord.Item1HasCustomRotation = collision.Item1HasCustomRotation;
collisionRecord.AnimatedObjectTrackedPosX = collision.AnimatedObjectTrackedPosition.X;
collisionRecord.AnimatedObjectTrackedPosY = collision.AnimatedObjectTrackedPosition.Y;
collisionRecord.AnimatedObjectTrackedPosZ = collision.AnimatedObjectTrackedPosition.Z;
collisionRecord.AnimatedObjectTrackedYawRadians = collision.AnimatedObjectTrackedYawRadians;
collisionRecord.AnimatedObjectTrackedRotA = collision.AnimatedObjectTrackedRotation?.A;
collisionRecord.AnimatedObjectTrackedRotB = collision.AnimatedObjectTrackedRotation?.B;
collisionRecord.AnimatedObjectTrackedRotC = collision.AnimatedObjectTrackedRotation?.C;
collisionRecord.AnimatedObjectTrackedRotD = collision.AnimatedObjectTrackedRotation?.D;
collisionRecord.AnimatedObjectHasTrackedRotation = collision.AnimatedObjectHasTrackedRotation;
collisionRecord.HasPositionInfo = true;
LogManager.Debug($"[保存碰撞对象] 记录运动物体位置: ({collisionRecord.Item1PosX:F2}, {collisionRecord.Item1PosY:F2}, {collisionRecord.Item1PosZ:F2}), yaw={collisionRecord.Item1YawRadians:F2} rad, customRotation={collisionRecord.Item1HasCustomRotation}");
LogManager.Debug($"[保存碰撞对象] 记录运动物体位置: ({collisionRecord.AnimatedObjectTrackedPosX:F2}, {collisionRecord.AnimatedObjectTrackedPosY:F2}, {collisionRecord.AnimatedObjectTrackedPosZ:F2}), yaw={collisionRecord.AnimatedObjectTrackedYawRadians:F2} rad, customRotation={collisionRecord.AnimatedObjectHasTrackedRotation}");
}
collisionObjects.Add(collisionRecord);
@ -495,26 +496,26 @@ namespace NavisworksTransport
};
// 恢复碰撞时运动物体的位置和朝向(如果可用)
if (obj.HasPositionInfo && obj.Item1PosX.HasValue && obj.Item1PosY.HasValue && obj.Item1PosZ.HasValue)
if (obj.HasPositionInfo && obj.AnimatedObjectTrackedPosX.HasValue && obj.AnimatedObjectTrackedPosY.HasValue && obj.AnimatedObjectTrackedPosZ.HasValue)
{
collisionResult.Item1Position = new Point3D(obj.Item1PosX.Value, obj.Item1PosY.Value, obj.Item1PosZ.Value);
collisionResult.Item1YawRadians = obj.Item1YawRadians ?? 0.0;
collisionResult.Item1HasCustomRotation = obj.Item1HasCustomRotation;
if (obj.Item1HasCustomRotation &&
obj.Item1RotA.HasValue &&
obj.Item1RotB.HasValue &&
obj.Item1RotC.HasValue &&
obj.Item1RotD.HasValue)
collisionResult.AnimatedObjectTrackedPosition = new Point3D(obj.AnimatedObjectTrackedPosX.Value, obj.AnimatedObjectTrackedPosY.Value, obj.AnimatedObjectTrackedPosZ.Value);
collisionResult.AnimatedObjectTrackedYawRadians = obj.AnimatedObjectTrackedYawRadians ?? 0.0;
collisionResult.AnimatedObjectHasTrackedRotation = obj.AnimatedObjectHasTrackedRotation;
if (obj.AnimatedObjectHasTrackedRotation &&
obj.AnimatedObjectTrackedRotA.HasValue &&
obj.AnimatedObjectTrackedRotB.HasValue &&
obj.AnimatedObjectTrackedRotC.HasValue &&
obj.AnimatedObjectTrackedRotD.HasValue)
{
collisionResult.Item1Rotation = new Rotation3D(
obj.Item1RotA.Value,
obj.Item1RotB.Value,
obj.Item1RotC.Value,
obj.Item1RotD.Value);
collisionResult.AnimatedObjectTrackedRotation = new Rotation3D(
obj.AnimatedObjectTrackedRotA.Value,
obj.AnimatedObjectTrackedRotB.Value,
obj.AnimatedObjectTrackedRotC.Value,
obj.AnimatedObjectTrackedRotD.Value);
}
collisionResult.HasPositionInfo = true;
LogManager.Debug($"[加载碰撞结果] 恢复运动物体位置: ({obj.Item1PosX:F2}, {obj.Item1PosY:F2}, {obj.Item1PosZ:F2}), yaw={obj.Item1YawRadians:F2} rad, customRotation={obj.Item1HasCustomRotation}");
LogManager.Debug($"[加载碰撞结果] 恢复运动物体位置: ({obj.AnimatedObjectTrackedPosX:F2}, {obj.AnimatedObjectTrackedPosY:F2}, {obj.AnimatedObjectTrackedPosZ:F2}), yaw={obj.AnimatedObjectTrackedYawRadians:F2} rad, customRotation={obj.AnimatedObjectHasTrackedRotation}");
}
results.Add(collisionResult);
@ -796,22 +797,27 @@ namespace NavisworksTransport
{
var testAnimatedObject = candidate.Item1;
var modelItems = new ModelItemCollection { testAnimatedObject };
var targetPosition = candidate.Item1Position;
var targetYaw = candidate.Item1YawRadians;
var targetRotation = candidate.Item1Rotation;
var targetPosition = candidate.AnimatedObjectTrackedPosition;
var targetYaw = candidate.AnimatedObjectTrackedYawRadians;
var targetRotation = candidate.AnimatedObjectTrackedRotation;
// 🔥 关键为了最高精度先回到CAD原始位置再移动到目标位置
// 避免累积误差影响碰撞检测精度
doc.Models.ResetPermanentTransform(modelItems);
if (candidate.Item1HasCustomRotation && targetRotation != null)
if (candidate.AnimatedObjectHasTrackedRotation && targetRotation != null)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var originalBounds = testAnimatedObject.BoundingBox();
var originalCenter = originalBounds.Center;
var originalTrackedPosition = ModelItemTransformHelper.GetHostBottomAnchorPoint(originalBounds, adapter);
var originalRotation = testAnimatedObject.Transform.Factor().Rotation;
LogManager.Info(
$"[ClashDetective验证] 三维候选恢复: " +
$"当前跟踪点=({originalTrackedPosition.X:F3},{originalTrackedPosition.Y:F3},{originalTrackedPosition.Z:F3}), " +
$"目标跟踪点=({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3})");
ModelItemTransformHelper.MoveItemIncrementallyToPositionAndRotation(
testAnimatedObject,
originalCenter,
originalTrackedPosition,
originalRotation,
targetPosition,
targetRotation);
@ -921,14 +927,14 @@ namespace NavisworksTransport
var confirmedPositionKey = GetCollisionObjectKey(candidate.Item1, candidate.Item2);
_confirmedCollisionPositions[confirmedPositionKey] = new CollisionPositionInfo
{
Item1Position = candidate.Item1Position,
AnimatedObjectTrackedPosition = candidate.AnimatedObjectTrackedPosition,
Item2Position = candidate.Item2Position,
Item1YawRadians = candidate.Item1YawRadians,
Item1Rotation = candidate.Item1Rotation,
Item1HasCustomRotation = candidate.Item1HasCustomRotation,
AnimatedObjectTrackedYawRadians = candidate.AnimatedObjectTrackedYawRadians,
AnimatedObjectTrackedRotation = candidate.AnimatedObjectTrackedRotation,
AnimatedObjectHasTrackedRotation = candidate.AnimatedObjectHasTrackedRotation,
HasPositionInfo = candidate.HasPositionInfo
};
LogManager.Debug($"[ClashDetective确认] 记录碰撞位置: {confirmedPositionKey}, 位置: ({candidate.Item1Position.X:F2}, {candidate.Item1Position.Y:F2}, {candidate.Item1Position.Z:F2})");
LogManager.Debug($"[ClashDetective确认] 记录碰撞位置: {confirmedPositionKey}, 位置: ({candidate.AnimatedObjectTrackedPosition.X:F2}, {candidate.AnimatedObjectTrackedPosition.Y:F2}, {candidate.AnimatedObjectTrackedPosition.Z:F2})");
int subResultIndex = 1;
// 🔥 优化:预获取运动物体名称,避免在循环中重复获取
@ -1037,11 +1043,11 @@ namespace NavisworksTransport
Distance = clashResult.Distance,
CreatedTime = DateTime.Now,
// 🔥 使用ClashDetective确认时的位置信息
Item1Position = confirmedPosition?.Item1Position,
AnimatedObjectTrackedPosition = confirmedPosition?.AnimatedObjectTrackedPosition,
Item2Position = confirmedPosition?.Item2Position,
Item1YawRadians = confirmedPosition?.Item1YawRadians ?? 0,
Item1Rotation = confirmedPosition?.Item1Rotation,
Item1HasCustomRotation = confirmedPosition?.Item1HasCustomRotation ?? false,
AnimatedObjectTrackedYawRadians = confirmedPosition?.AnimatedObjectTrackedYawRadians ?? 0,
AnimatedObjectTrackedRotation = confirmedPosition?.AnimatedObjectTrackedRotation,
AnimatedObjectHasTrackedRotation = confirmedPosition?.AnimatedObjectHasTrackedRotation ?? false,
HasPositionInfo = confirmedPosition != null
};
clashResults.Add(collisionResult);
@ -1056,7 +1062,7 @@ namespace NavisworksTransport
.ToList();
// 🔥 日志:统计位置信息保留情况
var withPositionCount = finalClashResults.Count(c => c.HasPositionInfo && c.Item1Position != null);
var withPositionCount = finalClashResults.Count(c => c.HasPositionInfo && c.AnimatedObjectTrackedPosition != null);
LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对,其中 {withPositionCount} 个包含位置信息");
// 🔥 优化:在去重后为结果设置详细的 DisplayName
@ -1132,7 +1138,7 @@ namespace NavisworksTransport
LogManager.Info($"[预计算数据] 共有 {precomputedCollisions.Count} 个碰撞记录");
// 检查预计算碰撞结果是否包含位置信息
var collisionsWithPosition = precomputedCollisions.Count(c => c.HasPositionInfo && c.Item1Position != null);
var collisionsWithPosition = precomputedCollisions.Count(c => c.HasPositionInfo && c.AnimatedObjectTrackedPosition != null);
LogManager.Info($"[预计算数据] 包含位置信息的碰撞: {collisionsWithPosition}/{precomputedCollisions.Count}");
// 直接使用所有预计算结果,只过滤有效对象(不去重)
@ -2168,11 +2174,11 @@ namespace NavisworksTransport
public DateTime CreatedTime { get; set; }
// 位置和朝向信息用于还原碰撞场景
public Point3D Item1Position { get; set; }
public Point3D AnimatedObjectTrackedPosition { get; set; }
public Point3D Item2Position { get; set; }
public double Item1YawRadians { get; set; } // 运动物体朝向(弧度)
public Rotation3D Item1Rotation { get; set; } // 运动物体完整三维姿态
public bool Item1HasCustomRotation { get; set; }
public double AnimatedObjectTrackedYawRadians { get; set; } // 动画跟踪点对应的运动物体朝向(弧度)
public Rotation3D AnimatedObjectTrackedRotation { get; set; } // 动画跟踪点对应的运动物体完整三维姿态
public bool AnimatedObjectHasTrackedRotation { get; set; }
public bool HasPositionInfo { get; set; }
// IEquatable<CollisionResult> 实现:基于碰撞对象进行去重

View File

@ -653,14 +653,14 @@ namespace NavisworksTransport.Core
var collisions = new List<CollisionResult>();
foreach (var obj in collisionObjects)
{
if (obj.Item1PosX.HasValue && obj.Item1PosY.HasValue && obj.Item1PosZ.HasValue)
if (obj.AnimatedObjectTrackedPosX.HasValue && obj.AnimatedObjectTrackedPosY.HasValue && obj.AnimatedObjectTrackedPosZ.HasValue)
{
collisions.Add(new CollisionResult
{
Center = new Point3D(
obj.Item1PosX.Value,
obj.Item1PosY.Value,
obj.Item1PosZ.Value),
obj.AnimatedObjectTrackedPosX.Value,
obj.AnimatedObjectTrackedPosY.Value,
obj.AnimatedObjectTrackedPosZ.Value),
Item2 = ModelItemProxyHelper.CreateProxy(obj.DisplayName)
});
}

View File

@ -234,16 +234,16 @@ namespace NavisworksTransport
PathId TEXT,
DisplayName TEXT,
ObjectName TEXT,
--
Item1PosX REAL,
Item1PosY REAL,
Item1PosZ REAL,
Item1YawRadians REAL,
Item1RotA REAL,
Item1RotB REAL,
Item1RotC REAL,
Item1RotD REAL,
Item1HasCustomRotation INTEGER DEFAULT 0,
-- 姿
AnimatedObjectTrackedPosX REAL,
AnimatedObjectTrackedPosY REAL,
AnimatedObjectTrackedPosZ REAL,
AnimatedObjectTrackedYawRadians REAL,
AnimatedObjectTrackedRotA REAL,
AnimatedObjectTrackedRotB REAL,
AnimatedObjectTrackedRotC REAL,
AnimatedObjectTrackedRotD REAL,
AnimatedObjectHasTrackedRotation INTEGER DEFAULT 0,
HasPositionInfo INTEGER DEFAULT 0,
FOREIGN KEY(DetectionRecordId) REFERENCES CollisionDetectionRecords(Id) ON DELETE CASCADE
)
@ -300,8 +300,8 @@ namespace NavisworksTransport
// 12. 设置数据库版本SQLite内置user_version
// 版本号格式:主版本*10000 + 次版本*100 + 修订号
// 例如2.1.4 = 20104
ExecuteNonQuery("PRAGMA user_version = 20104"); // v2.1.4 - ClashDetective碰撞对象位姿字段扩展与Rail三维恢复链路调整
// 例如2.1.5 = 20105
ExecuteNonQuery("PRAGMA user_version = 20105"); // v2.1.5 - 碰撞对象位姿字段命名统一为动画跟踪点语义
// 创建索引
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_reports_route ON CollisionReports(RouteId)");
@ -1114,11 +1114,11 @@ namespace NavisworksTransport
var sql = @"
INSERT INTO ClashDetectiveCollisionObjects
(DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName,
Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians,
Item1RotA, Item1RotB, Item1RotC, Item1RotD, Item1HasCustomRotation, HasPositionInfo)
AnimatedObjectTrackedPosX, AnimatedObjectTrackedPosY, AnimatedObjectTrackedPosZ, AnimatedObjectTrackedYawRadians,
AnimatedObjectTrackedRotA, AnimatedObjectTrackedRotB, AnimatedObjectTrackedRotC, AnimatedObjectTrackedRotD, AnimatedObjectHasTrackedRotation, HasPositionInfo)
VALUES (@detectionRecordId, @modelIndex, @pathId, @displayName, @objectName,
@item1PosX, @item1PosY, @item1PosZ, @item1YawRadians,
@item1RotA, @item1RotB, @item1RotC, @item1RotD, @item1HasCustomRotation, @hasPositionInfo)
@trackedPosX, @trackedPosY, @trackedPosZ, @trackedYawRadians,
@trackedRotA, @trackedRotB, @trackedRotC, @trackedRotD, @hasTrackedRotation, @hasPositionInfo)
";
foreach (var obj in objects)
@ -1130,15 +1130,15 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@pathId", obj.PathId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@displayName", obj.DisplayName ?? "");
cmd.Parameters.AddWithValue("@objectName", obj.ObjectName ?? "");
cmd.Parameters.AddWithValue("@item1PosX", obj.Item1PosX.HasValue ? (object)obj.Item1PosX.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1PosY", obj.Item1PosY.HasValue ? (object)obj.Item1PosY.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1PosZ", obj.Item1PosZ.HasValue ? (object)obj.Item1PosZ.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1YawRadians", obj.Item1YawRadians.HasValue ? (object)obj.Item1YawRadians.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1RotA", obj.Item1RotA.HasValue ? (object)obj.Item1RotA.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1RotB", obj.Item1RotB.HasValue ? (object)obj.Item1RotB.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1RotC", obj.Item1RotC.HasValue ? (object)obj.Item1RotC.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1RotD", obj.Item1RotD.HasValue ? (object)obj.Item1RotD.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1HasCustomRotation", obj.Item1HasCustomRotation ? 1 : 0);
cmd.Parameters.AddWithValue("@trackedPosX", obj.AnimatedObjectTrackedPosX.HasValue ? (object)obj.AnimatedObjectTrackedPosX.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@trackedPosY", obj.AnimatedObjectTrackedPosY.HasValue ? (object)obj.AnimatedObjectTrackedPosY.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@trackedPosZ", obj.AnimatedObjectTrackedPosZ.HasValue ? (object)obj.AnimatedObjectTrackedPosZ.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@trackedYawRadians", obj.AnimatedObjectTrackedYawRadians.HasValue ? (object)obj.AnimatedObjectTrackedYawRadians.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@trackedRotA", obj.AnimatedObjectTrackedRotA.HasValue ? (object)obj.AnimatedObjectTrackedRotA.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@trackedRotB", obj.AnimatedObjectTrackedRotB.HasValue ? (object)obj.AnimatedObjectTrackedRotB.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@trackedRotC", obj.AnimatedObjectTrackedRotC.HasValue ? (object)obj.AnimatedObjectTrackedRotC.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@trackedRotD", obj.AnimatedObjectTrackedRotD.HasValue ? (object)obj.AnimatedObjectTrackedRotD.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@hasTrackedRotation", obj.AnimatedObjectHasTrackedRotation ? 1 : 0);
cmd.Parameters.AddWithValue("@hasPositionInfo", obj.HasPositionInfo ? 1 : 0);
cmd.ExecuteNonQuery();
}
@ -1166,8 +1166,8 @@ namespace NavisworksTransport
{
var sql = @"
SELECT Id, DetectionRecordId, ModelIndex, PathId, DisplayName, ObjectName,
Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians,
Item1RotA, Item1RotB, Item1RotC, Item1RotD, Item1HasCustomRotation, HasPositionInfo
AnimatedObjectTrackedPosX, AnimatedObjectTrackedPosY, AnimatedObjectTrackedPosZ, AnimatedObjectTrackedYawRadians,
AnimatedObjectTrackedRotA, AnimatedObjectTrackedRotB, AnimatedObjectTrackedRotC, AnimatedObjectTrackedRotD, AnimatedObjectHasTrackedRotation, HasPositionInfo
FROM ClashDetectiveCollisionObjects
WHERE DetectionRecordId = @detectionRecordId
";
@ -1187,15 +1187,15 @@ namespace NavisworksTransport
PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
DisplayName = reader["DisplayName"]?.ToString(),
ObjectName = reader["ObjectName"]?.ToString(),
Item1PosX = reader["Item1PosX"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosX"]) : (double?)null,
Item1PosY = reader["Item1PosY"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosY"]) : (double?)null,
Item1PosZ = reader["Item1PosZ"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosZ"]) : (double?)null,
Item1YawRadians = reader["Item1YawRadians"] != DBNull.Value ? Convert.ToDouble(reader["Item1YawRadians"]) : (double?)null,
Item1RotA = reader["Item1RotA"] != DBNull.Value ? Convert.ToDouble(reader["Item1RotA"]) : (double?)null,
Item1RotB = reader["Item1RotB"] != DBNull.Value ? Convert.ToDouble(reader["Item1RotB"]) : (double?)null,
Item1RotC = reader["Item1RotC"] != DBNull.Value ? Convert.ToDouble(reader["Item1RotC"]) : (double?)null,
Item1RotD = reader["Item1RotD"] != DBNull.Value ? Convert.ToDouble(reader["Item1RotD"]) : (double?)null,
Item1HasCustomRotation = reader["Item1HasCustomRotation"] != DBNull.Value && Convert.ToInt32(reader["Item1HasCustomRotation"]) == 1,
AnimatedObjectTrackedPosX = reader["AnimatedObjectTrackedPosX"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosX"]) : (double?)null,
AnimatedObjectTrackedPosY = reader["AnimatedObjectTrackedPosY"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosY"]) : (double?)null,
AnimatedObjectTrackedPosZ = reader["AnimatedObjectTrackedPosZ"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedPosZ"]) : (double?)null,
AnimatedObjectTrackedYawRadians = reader["AnimatedObjectTrackedYawRadians"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedYawRadians"]) : (double?)null,
AnimatedObjectTrackedRotA = reader["AnimatedObjectTrackedRotA"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotA"]) : (double?)null,
AnimatedObjectTrackedRotB = reader["AnimatedObjectTrackedRotB"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotB"]) : (double?)null,
AnimatedObjectTrackedRotC = reader["AnimatedObjectTrackedRotC"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotC"]) : (double?)null,
AnimatedObjectTrackedRotD = reader["AnimatedObjectTrackedRotD"] != DBNull.Value ? Convert.ToDouble(reader["AnimatedObjectTrackedRotD"]) : (double?)null,
AnimatedObjectHasTrackedRotation = reader["AnimatedObjectHasTrackedRotation"] != DBNull.Value && Convert.ToInt32(reader["AnimatedObjectHasTrackedRotation"]) == 1,
HasPositionInfo = reader["HasPositionInfo"] != DBNull.Value && Convert.ToInt32(reader["HasPositionInfo"]) == 1
});
}
@ -3561,16 +3561,16 @@ namespace NavisworksTransport
public string DisplayName { get; set; }
public string ObjectName { get; set; }
// 碰撞时运动物体的位置和朝向(用于还原碰撞场景)
public double? Item1PosX { get; set; }
public double? Item1PosY { get; set; }
public double? Item1PosZ { get; set; }
public double? Item1YawRadians { get; set; }
public double? Item1RotA { get; set; }
public double? Item1RotB { get; set; }
public double? Item1RotC { get; set; }
public double? Item1RotD { get; set; }
public bool Item1HasCustomRotation { get; set; }
// 碰撞时运动物体的动画跟踪点位置和姿态(用于还原碰撞场景)
public double? AnimatedObjectTrackedPosX { get; set; }
public double? AnimatedObjectTrackedPosY { get; set; }
public double? AnimatedObjectTrackedPosZ { get; set; }
public double? AnimatedObjectTrackedYawRadians { get; set; }
public double? AnimatedObjectTrackedRotA { get; set; }
public double? AnimatedObjectTrackedRotB { get; set; }
public double? AnimatedObjectTrackedRotC { get; set; }
public double? AnimatedObjectTrackedRotD { get; set; }
public bool AnimatedObjectHasTrackedRotation { get; set; }
public bool HasPositionInfo { get; set; }
}

View File

@ -18,6 +18,7 @@ using NavisworksTransport.Commands;
using NavisworksTransport.UI.WPF.Collections;
using NavisworksTransport.UI.WPF.Views;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.UI.WPF.ViewModels
{
@ -178,11 +179,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand HighlightCommand { get; set; }
public ICommand ClearHighlightCommand { get; set; }
// 碰撞位置信息(用于还原碰撞场景)
public Point3D Item1Position { get; set; }
public double Item1YawRadians { get; set; }
public Rotation3D Item1Rotation { get; set; }
public bool Item1HasCustomRotation { get; set; }
// 动画跟踪点位置信息(用于还原碰撞场景)
public Point3D AnimatedObjectTrackedPosition { get; set; }
public double AnimatedObjectTrackedYawRadians { get; set; }
public Rotation3D AnimatedObjectTrackedRotation { get; set; }
public bool AnimatedObjectHasTrackedRotation { get; set; }
public bool HasPositionInfo { get; set; }
}
@ -1700,9 +1701,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 2. 先保存物体原始尺寸在设置SelectedAnimatedObject之前
var bbox = newObject.BoundingBox();
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
_objectOriginalLength = (bbox.Max.X - bbox.Min.X) / metersToModelUnits;
_objectOriginalWidth = (bbox.Max.Y - bbox.Min.Y) / metersToModelUnits;
_objectOriginalHeight = (bbox.Max.Z - bbox.Min.Z) / metersToModelUnits;
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var axisConvention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
double objectLength = axisConvention.GetForwardSize(bbox);
double objectWidth = axisConvention.GetSideSize(bbox);
double objectHeight = axisConvention.GetUpSize(bbox);
_objectOriginalLength = objectLength / metersToModelUnits;
_objectOriginalWidth = objectWidth / metersToModelUnits;
_objectOriginalHeight = objectHeight / metersToModelUnits;
_pathAnimationManager?.SetRealObjectDimensions(objectLength, objectWidth, objectHeight);
LogManager.Debug($"[选择物体] 保存原始尺寸: 长度={_objectOriginalLength:F2}m, 宽度={_objectOriginalWidth:F2}m, 高度={_objectOriginalHeight:F2}m");
// 3. 设置新物体会触发UpdatePassageSpaceVisualization
@ -2508,10 +2515,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CollisionSceneHelper.MoveToCollisionAndFocus(
collisionObject.ModelItem,
null,
collisionObject.Item1Position,
collisionObject.Item1YawRadians,
collisionObject.Item1Rotation,
collisionObject.Item1HasCustomRotation,
collisionObject.AnimatedObjectTrackedPosition,
collisionObject.AnimatedObjectTrackedYawRadians,
collisionObject.AnimatedObjectTrackedRotation,
collisionObject.AnimatedObjectHasTrackedRotation,
false);
return;
}
@ -2548,10 +2555,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CollisionSceneHelper.MoveToCollisionAndFocus(
collisionObject.ModelItem,
animatedObject,
collisionObject.Item1Position,
collisionObject.Item1YawRadians,
collisionObject.Item1Rotation,
collisionObject.Item1HasCustomRotation,
collisionObject.AnimatedObjectTrackedPosition,
collisionObject.AnimatedObjectTrackedYawRadians,
collisionObject.AnimatedObjectTrackedRotation,
collisionObject.AnimatedObjectHasTrackedRotation,
collisionObject.HasPositionInfo);
// 🔥 虚拟物体缩放已通过MoveItemToPositionAndYawWithCurrentScale保留
@ -3680,21 +3687,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels
ModelItem = modelItem,
HighlightCommand = new RelayCommand(() => resultViewModel.ExecuteHighlightCollisionObject(objViewModel)),
// 填充碰撞位置信息
Item1Position = objRecord.HasPositionInfo && objRecord.Item1PosX.HasValue ?
new Point3D(objRecord.Item1PosX.Value, objRecord.Item1PosY.Value, objRecord.Item1PosZ.Value) : null,
Item1YawRadians = objRecord.Item1YawRadians ?? 0,
Item1Rotation = objRecord.Item1HasCustomRotation &&
objRecord.Item1RotA.HasValue &&
objRecord.Item1RotB.HasValue &&
objRecord.Item1RotC.HasValue &&
objRecord.Item1RotD.HasValue
AnimatedObjectTrackedPosition = objRecord.HasPositionInfo && objRecord.AnimatedObjectTrackedPosX.HasValue ?
new Point3D(objRecord.AnimatedObjectTrackedPosX.Value, objRecord.AnimatedObjectTrackedPosY.Value, objRecord.AnimatedObjectTrackedPosZ.Value) : null,
AnimatedObjectTrackedYawRadians = objRecord.AnimatedObjectTrackedYawRadians ?? 0,
AnimatedObjectTrackedRotation = objRecord.AnimatedObjectHasTrackedRotation &&
objRecord.AnimatedObjectTrackedRotA.HasValue &&
objRecord.AnimatedObjectTrackedRotB.HasValue &&
objRecord.AnimatedObjectTrackedRotC.HasValue &&
objRecord.AnimatedObjectTrackedRotD.HasValue
? new Rotation3D(
objRecord.Item1RotA.Value,
objRecord.Item1RotB.Value,
objRecord.Item1RotC.Value,
objRecord.Item1RotD.Value)
objRecord.AnimatedObjectTrackedRotA.Value,
objRecord.AnimatedObjectTrackedRotB.Value,
objRecord.AnimatedObjectTrackedRotC.Value,
objRecord.AnimatedObjectTrackedRotD.Value)
: Rotation3D.Identity,
Item1HasCustomRotation = objRecord.Item1HasCustomRotation,
AnimatedObjectHasTrackedRotation = objRecord.AnimatedObjectHasTrackedRotation,
HasPositionInfo = objRecord.HasPositionInfo
};
resultViewModel.CollisionObjects.Add(objViewModel);
@ -4410,9 +4417,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
else // Rail
{
// 空轨路径物体的长度方向X轴前进方向朝向路径方向
// 通行空间沿路径方向 = X方向尺寸长度垂直于路径方向 = Y方向尺寸宽度高度上下都加间隙在空中
// 通行空间沿路径方向 = X方向尺寸长度垂直于路径方向 = Y方向尺寸宽度
// 法线方向只在远离轨道的一侧留间隙:
// - 轨下安装:顶面贴路径,底面留间隙
// - 轨上安装:底面贴路径,顶面留间隙
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
passageNormalToPath = virtualObjectHeight + 2 * safetyMargin; // Z方向高度+ 2*间隙(法线方向,上下都加)
passageNormalToPath = virtualObjectHeight + safetyMargin; // 法线方向单侧留间隙
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
// 空轨路径不需要分段参数
passageNormalToPathVertical = passageNormalToPath;
@ -4422,13 +4432,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
else if (SelectedAnimatedObject != null)
{
// 使用选择物体的包围盒尺寸(模型单位)
// 使用选择物体的局部轴语义尺寸(模型单位)
var bbox = SelectedAnimatedObject.BoundingBox();
// 包围盒尺寸已经是模型单位
double sizeX = bbox.Max.X - bbox.Min.X; // X方向 = 长度(前进方向)
double sizeY = bbox.Max.Y - bbox.Min.Y; // Y方向 = 宽度(侧面)
double sizeZ = bbox.Max.Z - bbox.Min.Z; // Z方向 = 高度
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)
@ -4436,12 +4446,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 空中路径(空轨或吊装):
// 高度上下都加间隙
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
passageNormalToPath = sizeZ + 2 * safetyMargin; // Z方向高度+ 2*间隙(法线方向)
passageNormalToPath = sizeNormalToPath + 2 * safetyMargin; // 局部up方向高度 + 2*间隙(法线方向)
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
// 吊装路径分段参数
passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙
passageNormalToPathHorizontal = sizeZ + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙
LogManager.Debug($"[通行空间可视化] 空中路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 高度={sizeZ / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
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");
}
else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground)
{
@ -4449,24 +4459,27 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 物体的长度方向X轴前进方向朝向路径方向
// 通行空间沿路径方向 = X方向尺寸长度垂直于路径方向 = Y方向尺寸宽度高度上方加间隙下方不需要因为有地面
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
passageNormalToPath = sizeZ + safetyMargin; // Z方向高度+ 间隙(法线方向,只有上方)
passageNormalToPath = sizeNormalToPath + safetyMargin; // 局部up方向高度 + 间隙(法线方向,只有上方)
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
// 地面路径不需要分段参数
passageNormalToPathVertical = passageNormalToPath;
passageNormalToPathHorizontal = passageNormalToPath;
LogManager.Debug($"[通行空间可视化] 地面路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
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");
}
else // Rail
{
// 空轨路径(可能有坡度):
// 物体的长度方向X轴前进方向朝向路径方向
// 通行空间沿路径方向 = X方向尺寸长度垂直于路径方向 = Y方向尺寸宽度高度上下都加间隙在空中
// 通行空间沿路径方向 = X方向尺寸长度垂直于路径方向 = Y方向尺寸宽度
// 法线方向只在远离轨道的一侧留间隙:
// - 轨下安装:顶面贴路径,底面留间隙
// - 轨上安装:底面贴路径,顶面留间隙
passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向)
passageNormalToPath = sizeZ + 2 * safetyMargin; // Z方向高度+ 2*间隙(法线方向,上下都加)
passageNormalToPath = sizeNormalToPath + safetyMargin; // 局部up方向高度 + 单侧间隙(法线方向
passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向)
passageNormalToPathVertical = passageNormalToPath;
passageNormalToPathHorizontal = passageNormalToPath;
LogManager.Debug($"[通行空间可视化] 空轨路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m");
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");
}
}
else

View File

@ -1389,7 +1389,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
var collisionData = collisionItem.CollisionData;
// 首次查看碰撞时保存运动物体状态
if (collisionData.Item1 != null && collisionData.HasPositionInfo && collisionData.Item1Position != null)
if (collisionData.Item1 != null && collisionData.HasPositionInfo && collisionData.AnimatedObjectTrackedPosition != null)
{
if (_savedAnimatedObjectState == null || _currentAnimatedObject != collisionData.Item1)
{
@ -1432,4 +1432,4 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#endregion
}
}
}

View File

@ -1508,11 +1508,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter);
var bounds = _assemblyTerminalObject.BoundingBox();
Point3D canonicalCenter = adapter.ToCanonicalPoint(bounds.Center);
Vector3D canonicalUp = adapter.ToCanonicalVector(
ModelItemTransformHelper.GetUpDirectionFromTransform(_assemblyTerminalObject.Transform));
ModelItemTransformHelper.GetDirectionFromTransform(
_assemblyTerminalObject.Transform,
projectFrame.DefaultModelAxisConvention.UpAxis));
double direction = GetAssemblyAnchorDirection();
double verticalOffset = UnitsConverter.ConvertFromMeters(AssemblyAnchorVerticalOffsetInMeters);
@ -1652,9 +1655,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return;
}
HostCoordinateAdapter adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
ProjectReferenceFrame projectFrame = CreateAssemblyProjectReferenceFrame(adapter);
var bounds = _assemblyTerminalObject.BoundingBox();
Vector3D localSize = ModelItemTransformHelper.EstimateLocalBoxSize(bounds, _assemblyTerminalObject.Transform);
double suggestedOffset = UnitsConverter.ConvertToMeters(Math.Max(0.0, localSize.Z / 2.0));
double suggestedOffset = UnitsConverter.ConvertToMeters(
Math.Max(0.0, projectFrame.DefaultModelAxisConvention.GetUpSize(bounds) / 2.0));
_assemblyAnchorVerticalOffsetInMeters = suggestedOffset;
OnPropertyChanged(nameof(AssemblyAnchorVerticalOffsetInMeters));
}

View File

@ -2,6 +2,7 @@ using System;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Core;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.Utils
{
@ -20,8 +21,8 @@ namespace NavisworksTransport.Utils
{
if (collision == null) return;
MoveToCollisionAndFocus(collision.Item2 ?? collision.Item1, animatedObject,
collision.Item1Position, collision.Item1YawRadians, collision.Item1Rotation,
collision.Item1HasCustomRotation, collision.HasPositionInfo);
collision.AnimatedObjectTrackedPosition, collision.AnimatedObjectTrackedYawRadians, collision.AnimatedObjectTrackedRotation,
collision.AnimatedObjectHasTrackedRotation, collision.HasPositionInfo);
}
/// <summary>
@ -29,14 +30,14 @@ namespace NavisworksTransport.Utils
/// </summary>
/// <param name="hitObject">被撞对象</param>
/// <param name="animatedObject">动画物体(运动物体)</param>
/// <param name="item1Position">碰撞位置</param>
/// <param name="item1YawRadians">碰撞朝向</param>
/// <param name="item1Rotation">碰撞完整姿态</param>
/// <param name="item1HasCustomRotation">是否有完整姿态</param>
/// <param name="animatedObjectTrackedPosition">动画跟踪点位置</param>
/// <param name="animatedObjectTrackedYawRadians">动画跟踪点对应的碰撞朝向</param>
/// <param name="animatedObjectTrackedRotation">动画跟踪点对应的碰撞完整姿态</param>
/// <param name="animatedObjectHasTrackedRotation">是否有完整姿态</param>
/// <param name="hasPositionInfo">是否有位置信息</param>
public static void MoveToCollisionAndFocus(ModelItem hitObject, ModelItem animatedObject,
Point3D item1Position, double item1YawRadians, Rotation3D item1Rotation,
bool item1HasCustomRotation, bool hasPositionInfo)
Point3D animatedObjectTrackedPosition, double animatedObjectTrackedYawRadians, Rotation3D animatedObjectTrackedRotation,
bool animatedObjectHasTrackedRotation, bool hasPositionInfo)
{
try
{
@ -55,60 +56,30 @@ namespace NavisworksTransport.Utils
}
// 移动动画物体到碰撞位置
if (animatedObject != null && hasPositionInfo && item1Position != null)
if (animatedObject != null && hasPositionInfo && animatedObjectTrackedPosition != null)
{
var pam = PathAnimationManager.GetInstance();
bool useAnimationPipeline = pam != null &&
pam.ControlsAnimatedObject(animatedObject) &&
item1HasCustomRotation &&
item1Rotation != null;
// 检查是否是虚拟物体
bool isVirtual = VirtualObjectManager.Instance.IsVirtualObjectActive &&
VirtualObjectManager.Instance.CurrentVirtualObject == animatedObject;
pam.ControlsAnimatedObject(animatedObject);
if (useAnimationPipeline)
{
pam.MoveAnimatedObjectToPose(
animatedObject,
item1Position,
item1YawRadians,
item1Rotation,
true);
animatedObjectTrackedPosition,
animatedObjectTrackedYawRadians,
animatedObjectTrackedRotation ?? Rotation3D.Identity,
animatedObjectHasTrackedRotation && animatedObjectTrackedRotation != null);
LogManager.Info(string.Format(
"运动物体已通过动画链路移动到碰撞位置: 位置=({0:F2}, {1:F2}, {2:F2}), customRotation={3}",
item1Position.X, item1Position.Y, item1Position.Z,
true));
}
else if (item1HasCustomRotation && item1Rotation != null)
{
throw new InvalidOperationException(
$"三维碰撞点恢复必须复用动画主链路,当前对象未受 PathAnimationManager 控制: {animatedObject.DisplayName}");
animatedObjectTrackedPosition.X, animatedObjectTrackedPosition.Y, animatedObjectTrackedPosition.Z,
animatedObjectHasTrackedRotation && animatedObjectTrackedRotation != null));
}
else
{
var bounds = animatedObject.BoundingBox();
double halfHeight = (bounds.Max.Z - bounds.Min.Z) / 2.0;
var targetGroundPosition = new Point3D(
item1Position.X,
item1Position.Y,
item1Position.Z - halfHeight
);
if (isVirtual)
{
ModelItemTransformHelper.MoveItemToPositionAndYawWithCurrentScale(
animatedObject, targetGroundPosition, item1YawRadians);
}
else
{
ModelItemTransformHelper.MoveItemToPositionAndYaw(animatedObject, targetGroundPosition, item1YawRadians);
}
LogManager.Info(string.Format("运动物体已移动到碰撞位置: ({0:F2}, {1:F2}, {2:F2}), 朝向: {3:F2}°",
targetGroundPosition.X, targetGroundPosition.Y, targetGroundPosition.Z,
item1YawRadians * 180 / Math.PI));
throw new InvalidOperationException(
$"碰撞点恢复必须复用动画主链路,当前对象未受 PathAnimationManager 控制: {animatedObject.DisplayName}");
}
}
}
@ -132,8 +103,8 @@ namespace NavisworksTransport.Utils
// 从PathAnimationManager获取当前实际朝向
var pam = PathAnimationManager.GetInstance();
var currentYaw = pam.CurrentYaw;
var currentRotation = pam.CurrentRotation;
var hasCustomRotation = pam.HasCurrentRotation;
var currentRotation = pam.TrackedRotation;
var hasCustomRotation = pam.HasTrackedRotation;
var currentState = pam.GetObjectCurrentPosition(animatedObject);
pam.SaveObjectState(animatedObject);
@ -169,15 +140,18 @@ namespace NavisworksTransport.Utils
try
{
var pam = PathAnimationManager.GetInstance();
if (pam != null && pam.ControlsAnimatedObject(animatedObject))
{
pam.RestoreAnimatedObjectState(animatedObject);
LogManager.Info(string.Format(
"动画物体已通过PathAnimationManager恢复状态: {0}, customRotation={1}",
animatedObject.DisplayName,
state.HasCustomRotation));
return;
}
if (state.HasCustomRotation)
{
if (pam != null && ReferenceEquals(pam.AnimatedObject, animatedObject))
{
pam.RestoreAnimatedObjectState(animatedObject);
LogManager.Info(string.Format("动画物体已通过PathAnimationManager恢复三维姿态: {0}", animatedObject.DisplayName));
return;
}
bool isVirtualWithCustomRotation = VirtualObjectManager.Instance.IsVirtualObjectActive &&
VirtualObjectManager.Instance.CurrentVirtualObject == animatedObject;
if (isVirtualWithCustomRotation)

View File

@ -34,6 +34,7 @@ namespace NavisworksTransport.Utils.CoordinateSystem
public Vector3 UpUnitVector => _upUnitVector;
public Vector3D ForwardVector => ToNavVector(_forwardUnitVector);
public Vector3D UpVector => ToNavVector(_upUnitVector);
public LocalAxisDirection SideAxis => GetAxisDirection(Normalize(Vector3.Cross(_forwardUnitVector, _upUnitVector)));
/// <summary>
/// 根据模型局部轴约定,直接构造“本地 forward/up 对齐到目标世界 forward/up”的线性姿态。
@ -107,6 +108,21 @@ namespace NavisworksTransport.Utils.CoordinateSystem
}
}
public double GetForwardSize(BoundingBox3D bounds)
{
return GetAxisExtent(bounds, ForwardAxis);
}
public double GetUpSize(BoundingBox3D bounds)
{
return GetAxisExtent(bounds, UpAxis);
}
public double GetSideSize(BoundingBox3D bounds)
{
return GetAxisExtent(bounds, SideAxis);
}
private static Vector3 GetAxisVector3(LocalAxisDirection axis)
{
switch (axis)
@ -229,5 +245,23 @@ namespace NavisworksTransport.Utils.CoordinateSystem
{
return new Vector3D(vector.X, vector.Y, vector.Z);
}
private static double GetAxisExtent(BoundingBox3D bounds, LocalAxisDirection axis)
{
switch (axis)
{
case LocalAxisDirection.PositiveX:
case LocalAxisDirection.NegativeX:
return bounds.Max.X - bounds.Min.X;
case LocalAxisDirection.PositiveY:
case LocalAxisDirection.NegativeY:
return bounds.Max.Y - bounds.Min.Y;
case LocalAxisDirection.PositiveZ:
case LocalAxisDirection.NegativeZ:
return bounds.Max.Z - bounds.Min.Z;
default:
throw new ArgumentOutOfRangeException(nameof(axis), axis, null);
}
}
}
}

View File

@ -111,6 +111,22 @@ namespace NavisworksTransport.Utils
return GetAxisDirectionFromTransform(transform, 2, new Vector3D(0, 0, 1));
}
/// <summary>
/// 根据模型局部轴约定,获取指定局部轴在世界坐标中的方向。
/// </summary>
public static Vector3D GetDirectionFromTransform(
Transform3D transform,
LocalAxisDirection axisDirection)
{
GetAxisIndexAndSign(axisDirection, out int axisIndex, out int sign);
Vector3D fallback = GetFallbackAxis(axisDirection);
Vector3D direction = GetAxisDirectionFromTransform(transform, axisIndex, fallback);
return sign >= 0
? direction
: new Vector3D(-direction.X, -direction.Y, -direction.Z);
}
/// <summary>
/// 获取绑定在物体局部坐标中的稳定底面锚点。
/// 适用于带俯仰/侧倾的三维姿态对象,避免使用世界 AABB 底面中心导致锚点漂移。
@ -305,6 +321,60 @@ namespace NavisworksTransport.Utils
}
}
private static void GetAxisIndexAndSign(LocalAxisDirection axisDirection, out int axisIndex, out int sign)
{
switch (axisDirection)
{
case LocalAxisDirection.PositiveX:
axisIndex = 0;
sign = 1;
break;
case LocalAxisDirection.NegativeX:
axisIndex = 0;
sign = -1;
break;
case LocalAxisDirection.PositiveY:
axisIndex = 1;
sign = 1;
break;
case LocalAxisDirection.NegativeY:
axisIndex = 1;
sign = -1;
break;
case LocalAxisDirection.PositiveZ:
axisIndex = 2;
sign = 1;
break;
case LocalAxisDirection.NegativeZ:
axisIndex = 2;
sign = -1;
break;
default:
throw new ArgumentOutOfRangeException(nameof(axisDirection), axisDirection, null);
}
}
private static Vector3D GetFallbackAxis(LocalAxisDirection axisDirection)
{
switch (axisDirection)
{
case LocalAxisDirection.PositiveX:
return new Vector3D(1, 0, 0);
case LocalAxisDirection.NegativeX:
return new Vector3D(-1, 0, 0);
case LocalAxisDirection.PositiveY:
return new Vector3D(0, 1, 0);
case LocalAxisDirection.NegativeY:
return new Vector3D(0, -1, 0);
case LocalAxisDirection.PositiveZ:
return new Vector3D(0, 0, 1);
case LocalAxisDirection.NegativeZ:
return new Vector3D(0, 0, -1);
default:
throw new ArgumentOutOfRangeException(nameof(axisDirection), axisDirection, null);
}
}
private static double[] SolveLinearSystem3x3(double[,] matrix, double[] values)
{
double determinant =
@ -538,13 +608,13 @@ namespace NavisworksTransport.Utils
actualBounds.Min.Z);
LogManager.Info(
$"[模型增量姿态] {item.DisplayName} 应用后位置: " +
$"[模型增量姿态] {item.DisplayName} 立即读回位置(可能滞后): " +
$"实际=({actualPosition.X:F3},{actualPosition.Y:F3},{actualPosition.Z:F3}), " +
$"期望=({targetPosition.X:F3},{targetPosition.Y:F3},{targetPosition.Z:F3}), " +
$"偏差=({actualPosition.X - targetPosition.X:F3},{actualPosition.Y - targetPosition.Y:F3},{actualPosition.Z - targetPosition.Z:F3})");
LogManager.Info(
$"[模型增量姿态] {item.DisplayName} 应用后旋转: " +
$"[模型增量姿态] {item.DisplayName} 立即读回旋转(可能滞后/不反映override): " +
$"实际X=({actualLinear.Get(0, 0):F4},{actualLinear.Get(1, 0):F4},{actualLinear.Get(2, 0):F4}), " +
$"实际Y=({actualLinear.Get(0, 1):F4},{actualLinear.Get(1, 1):F4},{actualLinear.Get(2, 1):F4}), " +
$"实际Z=({actualLinear.Get(0, 2):F4},{actualLinear.Get(1, 2):F4},{actualLinear.Get(2, 2):F4}), " +