基本实现物流对象沿着路径转向的功能

This commit is contained in:
tian 2025-12-19 10:13:41 +08:00
parent 2bd117ff8a
commit d63896bf63
8 changed files with 941 additions and 66 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ packages/
navisworks_api/
*.exe
*.db

View File

@ -1,35 +0,0 @@
@echo off
echo ========================================
echo NavisworksTransport 数据库清理工具
echo ========================================
echo.
echo 此脚本将删除所有现有的路径数据库文件(.db文件
echo 下次启动插件时将自动创建新的数据库结构
echo.
echo 警告:此操作将删除所有历史路径数据!
echo.
pause
REM 设置数据库文件所在目录
set DB_DIR=C:\Users\Tellme\Documents\NavisworksTransport\分层输出
echo.
echo 正在清理数据库文件...
echo 目录: %DB_DIR%
echo.
REM 删除所有.db文件
if exist "%DB_DIR%\*.db" (
del /f /q "%DB_DIR%\*.db"
echo 已删除所有数据库文件
) else (
echo 未找到数据库文件
)
echo.
echo ========================================
echo 数据库清理完成!
echo 下次启动Navisworks插件时将自动创建新的数据库
echo ========================================
echo.
pause

View File

@ -509,9 +509,16 @@ public void BatchNavisworksOperations(List<ModelItem> items)
**核心概念**
- `ModelItem.Transform` - 返回设计文件中的原始变换,只读属性
- `OverridePermanentTransform()` - 应用增量变换(与现有变换叠加
- `ModelItem.Transform` - 返回设计文件中的原始变换,**只读属性****不反映override后的状态**
- `OverridePermanentTransform()` - 应用增量变换(相对于原始Transform累积
- `ResetPermanentTransform()` - 清除所有增量变换,恢复到设计文件原始位置
- `ModelItem.BoundingBox()` - 返回**当前实际显示**的包围盒反映override效果
**⚠️ 关键理解**
1. **`ModelItem.Transform` 永远返回原始值**,即使通过 `OverridePermanentTransform` 改变了物体位置
2. **Override 信息存储在别处**,不会修改 `ModelItem.Transform` 属性
3. **要获取实际位置,使用 `BoundingBox().Center`**它反映override后的实际位置
### 11.2 Transform 操作的正确用法
@ -579,16 +586,15 @@ public void RestoreToOriginalPosition(ModelItem selectedObject)
### 11.5 常见Transform问题和解决方案
**问题1位置恢复有偏移**
**问题1获取不到实际位置**
```csharp
// ❌ 错误:使用增量变换恢复位置
doc.Models.OverridePermanentTransform(modelItems, originalTransform, false);
// 问题:如果物体已经被移动过,这会导致累积偏移
// ❌ 错误:以为 Transform 反映当前位置
var transform = item.Transform;
// 问题:这永远返回原始Transform即使物体已被移动
// ✅ 正确:重置到原始位置
doc.Models.ResetPermanentTransform(modelItems);
// 结果:直接恢复到设计文件中的原始位置,无偏移
// ✅ 正确:使用 BoundingBox 获取实际位置
var actualCenter = item.BoundingBox().Center; // 反映override后的实际位置
```
**问题2动画结束后位置不准确**
@ -634,6 +640,209 @@ doc.Models.ResetPermanentTransform(modelItems);
- 所有Transform操作都必须在主UI线程中执行
- 使用 `Dispatcher.Invoke` 确保线程安全
### 11.7 旋转操作的关键限制和解决方案 ⚠️ 重要
基于实际测试验证的关键发现2025-12-15
#### 11.7.1 旋转中心的API限制
**⚠️ 核心限制Navisworks API的旋转总是绕世界原点(0,0,0)进行**
```csharp
// ❌ 错误理解:以为旋转绕物体中心
var rotation = new Transform3D(new Rotation3D(new UnitVector3D(0, 0, 1), angle));
doc.Models.OverridePermanentTransform(modelItems, rotation, false);
// 实际效果:物体绕世界原点(0,0,0)"公转",不是绕自己"自转"
// 🔍 实际测试验证:
// 物体在 (-2.499, -1.640, 0.500) 位置
// 旋转45度后移动到 (-0.608, -2.927, 0.500)
// 验证公式x' = x*cos(45°) - y*sin(45°) = -0.607 ✓
// y' = x*sin(45°) + y*cos(45°) = -2.927 ✓
// 证明:旋转中心是世界原点(0,0,0),不是物体中心
```
**验证代码**
```csharp
// ✅ 测试代码:证明旋转绕世界原点
var initialCenter = item.BoundingBox().Center; // (-2.499, -1.640, 0.500)
// 应用45度旋转
var rotation = new Transform3D(new Rotation3D(new UnitVector3D(0, 0, 1), Math.PI/4));
doc.Models.OverridePermanentTransform(modelItems, rotation, false);
var afterCenter = item.BoundingBox().Center; // (-0.608, -2.927, 0.500)
// 计算期望位置(绕原点旋转)
double cos45 = Math.Cos(Math.PI/4);
double sin45 = Math.Sin(Math.PI/4);
double expectedX = initialCenter.X * cos45 - initialCenter.Y * sin45; // -0.607
double expectedY = initialCenter.X * sin45 + initialCenter.Y * cos45; // -2.927
// 验证:实际位置 = 期望位置(绕原点旋转)✓
```
#### 11.7.2 Transform3DComponents 的行为
**关键理解:`Transform3DComponents.Combine()` 的变换顺序**
```csharp
// Transform3DComponents.Combine() 应用顺序:
// 1. Scale缩放
// 2. Rotation旋转绕原点
// 3. Translation平移
// ❌ 错误直接设置rotation和translation
var components = identity.Factor();
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = deltaPos; // 平移在旋转之后应用
var transform = components.Combine();
// 问题:物体先绕原点旋转(产生位置偏移),然后平移
// 结果:物体"公转"到错误位置
```
#### 11.7.3 正确实现"绕物体中心旋转"
**解决方案:手动计算旋转导致的位置偏移并补偿**
```csharp
// ✅ 正确方法:计算补偿平移量
private void UpdateObjectPosition(Point3D newPosition, double newYaw)
{
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 计算旋转和平移增量
var deltaPos = new Vector3D(
newPosition.X - _currentPosition.X,
newPosition.Y - _currentPosition.Y,
newPosition.Z - _currentPosition.Z
);
Transform3D incrementalTransform;
if (!double.IsNaN(newYaw))
{
double deltaYaw = newYaw - _currentYaw;
// 🎯 关键:计算绕当前位置旋转的等效变换
// 1. 如果绕原点旋转deltaYaw当前位置会移到哪里
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;
// 2. 我们希望物体绕自己旋转位置移动到newPosition
// 所以需要的平移 = newPosition - (旋转后的位置)
var compensatedTranslation = new Vector3D(
newPosition.X - rotatedX, // 补偿X方向的偏移
newPosition.Y - rotatedY, // 补偿Y方向的偏移
newPosition.Z - _currentPosition.Z // Z保持增量
);
// 3. 组合:先旋转(绕原点),再平移(补偿+目标位置)
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = compensatedTranslation; // 关键:使用补偿后的平移
incrementalTransform = components.Combine();
_currentYaw = newYaw;
}
else
{
// 纯平移:直接使用增量
incrementalTransform = Transform3D.CreateTranslation(deltaPos);
}
// 应用增量变换
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
_currentPosition = newPosition;
}
```
**原理说明**
```
API限制
旋转 → 物体绕(0,0,0)旋转 → 位置从P1偏移到P2
我们需要的效果:
旋转 → 物体绕自己旋转 → 位置从P1移动到P_target
解决方案:
补偿平移 = P_target - P2
最终变换 = Rotation(deltaYaw) + Translation(P_target - P2)
结果:
物体先绕原点旋转到P2然后平移到P_target
看起来像是绕自己旋转并移动到目标位置
```
#### 11.7.4 初始化问题
**⚠️ 重要初始化yaw必须与第一帧匹配**
```csharp
// ❌ 错误初始化为0
_currentYaw = 0.0;
// 第一帧调用UpdateObjectPosition时
// deltaYaw = firstFrame.YawRadians - 0.0 // 产生大的旋转增量
// 导致物体从起点"公转"飞走
// ✅ 正确初始化为第一帧的yaw
if (_animationFrames != null && _animationFrames.Count > 0)
{
_currentYaw = _animationFrames[0].YawRadians; // 使deltaYaw=0
// 第一次调用UpdateObjectPosition
var firstFrame = _animationFrames[0];
UpdateObjectPosition(firstFrame.Position, firstFrame.YawRadians);
// 此时deltaYaw = firstFrame.YawRadians - firstFrame.YawRadians = 0
// 结果:只有平移,没有旋转偏移
}
```
#### 11.7.5 相关API限制说明
Autodesk官方论坛已确认的限制Issue NW-53280
- **无法设置旋转中心点**API不提供指定旋转中心的方法
- **UI的Override Transform功能**:也是通过计算补偿实现的
- **建议的解决方案**手动计算T(center) × R × T(-center)的等效变换
#### 11.7.6 旋转操作最佳实践
| 场景 | 方法 | 注意事项 |
|------|------|---------|
| 简单旋转(原地) | 使用位置补偿公式 | 必须计算旋转导致的偏移 |
| 旋转+移动 | 组合补偿平移和目标平移 | 理解Combine()的变换顺序 |
| 动画初始化 | `_currentYaw = firstFrame.YawRadians` | 避免第一帧产生旋转增量 |
| 调试验证 | 测试物体远离原点的情况 | 原点附近可能掩盖问题 |
**调试技巧**
```csharp
// ✅ 测试旋转中心的方法
// 1. 将物体移动到远离原点的位置(如(-5, -5, 0)
// 2. 应用旋转
// 3. 检查物体是否"公转"(位置大幅移动)还是"自转"(位置基本不变)
// 4. 如果发现"公转",说明没有正确补偿
// ✅ 验证补偿计算的公式
double expectedX_afterRotation = currentX * cos(angle) - currentY * sin(angle);
double expectedY_afterRotation = currentX * sin(angle) + currentY * cos(angle);
var compensationX = targetX - expectedX_afterRotation;
var compensationY = targetY - expectedY_afterRotation;
LogManager.Debug($"旋转前: ({currentX}, {currentY})");
LogManager.Debug($"绕原点旋转后: ({expectedX_afterRotation}, {expectedY_afterRotation})");
LogManager.Debug($"目标位置: ({targetX}, {targetY})");
LogManager.Debug($"需要补偿: ({compensationX}, {compensationY})");
```
## 12. Item属性和自定义属性访问
基于官方示例的正确属性访问方法总结。

View File

@ -0,0 +1,381 @@
# 动画朝向变换方案
**状态:✅ 已完成2025-12-19**
## 背景
- 需求来源:`doc/requirement/todo_features.md` 中"动画时,物流模型朝向随路径变化"。
- 现状:`PathAnimationManager` 仅对 `_animatedObject` 做平移增量,未处理旋转;预计算碰撞也以静态 AABB 为基准,无法反映转弯时的真实占位。
- 补充约束:移动对象为转运车,只需在 XY 平面上旋转(绕世界 Z 轴的 yaw
## 关键API限制实测验证
⚠️ **核心发现**Navisworks API的旋转操作存在关键限制必须理解才能正确实现
### 1. `ModelItem.Transform` 不反映override状态
```csharp
// ❌ 错误理解
var transform = item.Transform;
// 以为这会返回当前实际位置但实际上永远返回设计文件中的原始Transform
// ✅ 正确做法
var actualCenter = item.BoundingBox().Center; // 这才反映override后的实际位置
```
**关键理解**
- `item.Transform` 永远返回设计文件中的原始Transform
- 通过 `OverridePermanentTransform` 改变位置后,`item.Transform` 不变
- Override信息存储在别处无法通过API直接读取
### 2. 旋转总是绕世界原点(0,0,0)
```csharp
// ❌ 错误理解:以为旋转绕物体中心
var rotation = new Transform3D(new Rotation3D(new UnitVector3D(0, 0, 1), angle));
doc.Models.OverridePermanentTransform(modelItems, rotation, false);
// 实际效果:物体绕世界原点(0,0,0)"公转",不是绕自己"自转"
```
**实测验证**2025-12-19
```
物体初始位置:(-2.499, -1.640, 0.500)
应用45度Z轴旋转后(-0.608, -2.927, 0.500)
验证计算(绕原点旋转公式):
x' = -2.499 × cos(45°) - (-1.640) × sin(45°) = -0.607 ✓
y' = -2.499 × sin(45°) + (-1.640) × cos(45°) = -2.927 ✓
结论:旋转中心是世界原点(0,0,0),不是物体中心
```
**官方确认**
- Autodesk官方论坛Issue NW-53280已确认这是API限制
- 无法指定自定义旋转中心点
- 建议通过计算补偿实现绕物体中心旋转
### 3. Transform3DComponents的变换顺序
```csharp
// Transform3DComponents.Combine() 应用顺序:
// 1. Scale缩放
// 2. Rotation旋转绕原点
// 3. Translation平移
// ❌ 错误直接设置rotation和translation
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = deltaPos;
// 问题:物体先绕原点旋转(产生位置偏移),然后平移
// 结果:物体"公转"到错误位置
```
参考:`doc/design/2026/NavisworksAPI使用方法.md` 第11.7节
## 目标
1. **实现"绕物体中心旋转"的视觉效果**通过计算补偿平移量抵消API的"绕原点旋转"导致的位置偏移。
2. **路径朝向计算**预计算阶段为每帧生成朝向角度yaw供播放时使用。
3. **碰撞检测一致性**:预计算碰撞时创建旋转后的虚拟包围盒,避免仅平移带来的漏检/误检。
## 实施步骤
### 1. 帧数据扩展
`AnimationFrame` 中新增:
```csharp
public double YawRadians { get; set; } // 当前帧的朝向角度(弧度)
```
`PathAnimationManager` 中添加:
```csharp
private double _currentYaw = 0.0; // 跟踪当前偏航角
```
### 2. 路径朝向计算
`PrecomputeAnimationFrames()` 中为每帧计算yaw
```csharp
private double ComputeYawFromPath(int frameIndex)
{
// 取前后帧计算切线方向
int prevIndex = Math.Max(0, frameIndex - 1);
int nextIndex = Math.Min(_animationFrames.Count - 1, frameIndex + 1);
var prev = _animationFrames[prevIndex].Position;
var next = _animationFrames[nextIndex].Position;
// XY平面上的方向向量
double dx = next.X - prev.X;
double dy = next.Y - prev.Y;
// 计算yaw角atan2自动处理象限
double yaw = Math.Atan2(dy, dx);
// 零长度段使用上一帧方向
if (Math.Abs(dx) < 1e-6 && Math.Abs(dy) < 1e-6)
{
yaw = frameIndex > 0 ? _animationFrames[frameIndex - 1].YawRadians : 0.0;
}
return yaw;
}
```
### 3. 正确实现"绕物体中心旋转"
**核心解决方案**:手动计算旋转导致的位置偏移并补偿
```csharp
private void UpdateObjectPosition(Point3D newPosition, double newYaw = double.NaN)
{
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 计算平移增量
var deltaPos = new Vector3D(
newPosition.X - _currentPosition.X,
newPosition.Y - _currentPosition.Y,
newPosition.Z - _currentPosition.Z
);
Transform3D incrementalTransform;
if (!double.IsNaN(newYaw))
{
// 有旋转:需要计算补偿
double deltaYaw = newYaw - _currentYaw;
// 🎯 关键步骤:计算补偿平移量
// 1. 如果绕原点旋转deltaYaw当前位置会移到哪里
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;
// 2. 我们希望物体绕自己旋转位置移动到newPosition
// 所以需要的平移 = newPosition - (旋转后的位置)
var compensatedTranslation = new Vector3D(
newPosition.X - rotatedX, // 补偿X方向的偏移
newPosition.Y - rotatedY, // 补偿Y方向的偏移
newPosition.Z - _currentPosition.Z // Z保持增量
);
// 3. 组合:先旋转(绕原点),再平移(补偿+目标位置)
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = compensatedTranslation; // 关键:使用补偿后的平移
incrementalTransform = components.Combine();
_currentYaw = newYaw;
}
else
{
// 纯平移:直接使用增量
incrementalTransform = Transform3D.CreateTranslation(deltaPos);
}
// 应用增量变换false=增量模式)
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
_currentPosition = newPosition;
}
```
**原理说明**
```
API限制
旋转 → 物体绕(0,0,0)旋转 → 位置从P1偏移到P2
我们需要的效果:
旋转 → 物体绕自己旋转 → 位置从P1移动到P_target
解决方案:
补偿平移 = P_target - P2
最终变换 = Rotation(deltaYaw) + Translation(P_target - P2)
结果:
物体先绕原点旋转到P2然后平移到P_target
看起来像是绕自己旋转并移动到目标位置
```
### 4. 初始化问题修复
⚠️ **关键**`_currentYaw` 必须初始化为第一帧的yaw值避免第一帧产生旋转增量
```csharp
// ❌ 错误初始化为0
_currentYaw = 0.0;
// 第一帧调用UpdateObjectPosition时
// deltaYaw = firstFrame.YawRadians - 0.0 // 产生大的旋转增量
// 导致物体从起点"公转"飞走
// ✅ 正确初始化为第一帧的yaw
if (_animationFrames != null && _animationFrames.Count > 0)
{
_currentYaw = _animationFrames[0].YawRadians; // 使deltaYaw=0
// 第一次调用UpdateObjectPosition
var firstFrame = _animationFrames[0];
UpdateObjectPosition(firstFrame.Position, firstFrame.YawRadians);
// 此时deltaYaw = firstFrame.YawRadians - firstFrame.YawRadians = 0
// 结果:只有平移,没有旋转偏移
}
```
### 5. 旋转包围盒预计算
为了在碰撞检测中反映旋转后的实际占位,需要创建旋转后的虚拟包围盒:
```csharp
private BoundingBox3D CreateVirtualBoundingBox(Point3D centerPosition, Vector3D size, double yawRadians)
{
// 1. 计算包围盒的4个底面角点相对中心
double halfX = size.X / 2.0;
double halfY = size.Y / 2.0;
Point3D[] corners = new Point3D[4]
{
new Point3D(-halfX, -halfY, 0), // 左下
new Point3D( halfX, -halfY, 0), // 右下
new Point3D( halfX, halfY, 0), // 右上
new Point3D(-halfX, halfY, 0) // 左上
};
// 2. 旋转每个角点
double cos = Math.Cos(yawRadians);
double sin = Math.Sin(yawRadians);
for (int i = 0; i < 4; i++)
{
double x = corners[i].X;
double y = corners[i].Y;
corners[i] = new Point3D(
x * cos - y * sin,
x * sin + y * cos,
corners[i].Z
);
}
// 3. 找出旋转后的新AABB边界
double minX = corners.Min(c => c.X);
double maxX = corners.Max(c => c.X);
double minY = corners.Min(c => c.Y);
double maxY = corners.Max(c => c.Y);
// 4. 平移到实际位置
return new BoundingBox3D(
new Point3D(centerPosition.X + minX, centerPosition.Y + minY, centerPosition.Z),
new Point3D(centerPosition.X + maxX, centerPosition.Y + maxY, centerPosition.Z + size.Z)
);
}
```
在预计算碰撞时使用:
```csharp
// 使用旋转后的包围盒进行碰撞检测
var rotatedBBox = CreateVirtualBoundingBox(
frame.Position,
boundingBoxSize,
frame.YawRadians
);
// 使用rotatedBBox进行碰撞检测...
```
## 验证策略
### 1. 旋转中心验证
**测试方法**
```csharp
// 1. 将物体移动到远离原点的位置(如(-5, -5, 0)
// 2. 应用旋转
// 3. 检查物体是否"公转"(位置大幅移动)还是"自转"(位置基本不变)
```
**预期结果**
- 使用补偿公式后,物体应该绕自己旋转,位置基本不变(仅路径平移)
- 不应该出现"公转"现象
### 2. 路径覆盖测试
- 挑选含直角转弯、斜率变化的路径
- 播放/Seek/反向播放,观察模型朝向是否随路径切线方向
- 验证零长度段(重复点)是否正确使用上一帧方向
### 3. 碰撞一致性测试
- 对比旋转前/后的预计算碰撞结果
- 在狭窄转弯处确保碰撞被正确捕获
- 验证非正方形物体(如长方形车辆)的碰撞检测准确性
### 4. 初始化测试
- 验证动画开始时物体在正确的起点位置
- 确认第一帧没有产生意外的旋转偏移
## 调试技巧
### 验证补偿计算
```csharp
// 在UpdateObjectPosition中添加调试日志
double expectedX_afterRotation = _currentPosition.X * cos - _currentPosition.Y * sin;
double expectedY_afterRotation = _currentPosition.X * sin + _currentPosition.Y * cos;
var compensationX = newPosition.X - expectedX_afterRotation;
var compensationY = newPosition.Y - expectedY_afterRotation;
LogManager.Debug($"旋转前: ({_currentPosition.X}, {_currentPosition.Y})");
LogManager.Debug($"绕原点旋转后: ({expectedX_afterRotation}, {expectedY_afterRotation})");
LogManager.Debug($"目标位置: ({newPosition.X}, {newPosition.Y})");
LogManager.Debug($"需要补偿: ({compensationX}, {compensationY})");
```
### 检测"公转"问题
```csharp
// 如果看到以下现象,说明没有正确补偿:
// - 物体起点位置不对
// - 转弯时物体"飞"到错误位置
// - 包围盒中心变化量远大于路径移动距离
```
## 实际实现结果
**完成时间**2025-12-19
**关键成果**
1. ✅ 成功实现"绕物体中心旋转"的视觉效果
2. ✅ 物体沿路径平滑移动并旋转(自转而非公转)
3. ✅ 初始化问题已修复,起点位置正确
4. ✅ 碰撞检测支持旋转后的包围盒
**关键代码文件**
- `src/Core/Animation/PathAnimationManager.cs`
- `AnimationFrame` 添加了 `YawRadians` 字段
- `ComputeYawFromPath()` 计算每帧朝向
- `UpdateObjectPosition()` 实现补偿平移算法
- `CreateVirtualBoundingBox()` 创建旋转后的包围盒
**核心洞察**
- Navisworks API的旋转是固定绕世界原点的这是设计限制而非bug
- 通过数学补偿可以实现任意旋转中心的视觉效果
- `ModelItem.Transform` 不反映override状态需要用 `BoundingBox().Center` 获取实际位置
**参考文档**
- `doc/design/2026/NavisworksAPI使用方法.md` 第11.7节详细的API限制说明和解决方案

View File

@ -0,0 +1,66 @@
using System;
using System.Linq;
using System.Windows;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;
using NavisworksTransport.Core;
namespace NavisworksTransport.Commands
{
[PluginAttribute("ReadTransformTest", "YourDeveloperID", DisplayName = "读取Transform测试")]
[AddInPluginAttribute(AddInLocation.AddIn)]
public class ReadTransformTestCommand : AddInPlugin
{
public override int Execute(params string[] parameters)
{
try
{
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var selection = doc.CurrentSelection.SelectedItems;
if (selection.Count == 0)
{
MessageBox.Show("请先选择一个对象!", "提示");
return 0;
}
var item = selection.First();
// 读取Transform
var transform = item.Transform;
var components = transform.Factor();
var bbox = item.BoundingBox();
var center = bbox.Center;
var info = $"=== Transform信息 ===\n\n";
info += $"对象: {item.DisplayName}\n\n";
info += $"包围盒中心:\n";
info += $" X = {center.X:F3}\n";
info += $" Y = {center.Y:F3}\n";
info += $" Z = {center.Z:F3}\n\n";
info += $"Transform.Translation:\n";
info += $" X = {components.Translation.X:F3}\n";
info += $" Y = {components.Translation.Y:F3}\n";
info += $" Z = {components.Translation.Z:F3}\n\n";
info += $"Transform.Scale:\n";
info += $" X = {components.Scale.X:F3}\n";
info += $" Y = {components.Scale.Y:F3}\n";
info += $" Z = {components.Scale.Z:F3}\n\n";
info += $"Transform.Rotation:\n";
info += $" Axis = ({components.Rotation.Axis.X:F3}, {components.Rotation.Axis.Y:F3}, {components.Rotation.Axis.Z:F3})\n";
info += $" Angle = {components.Rotation.Angle:F6} rad = {components.Rotation.Angle * 180 / Math.PI:F2}°\n";
LogManager.Info(info);
MessageBox.Show(info, "Transform信息", MessageBoxButton.OK);
return 0;
}
catch (Exception ex)
{
MessageBox.Show($"错误: {ex.Message}", "错误");
LogManager.Error($"读取Transform失败: {ex.Message}\n{ex.StackTrace}");
return 1;
}
}
}
}

View File

@ -64,19 +64,21 @@ namespace NavisworksTransport.Core.Animation
/// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务
/// </summary>
/// <summary>
/// 动画帧数据(包含位置和碰撞信息)
/// 动画帧数据(包含位置、朝向和碰撞信息)
/// </summary>
public class AnimationFrame
{
public int Index { get; set; } // 帧索引
public double Progress { get; set; } // 进度(0-1)
public Point3D Position { get; set; } // 该帧的位置
public double YawRadians { get; set; } // 绕Z轴的偏航角弧度
public List<CollisionResult> Collisions { get; set; } // 该帧的碰撞结果
public bool HasCollision => Collisions?.Count > 0;
public AnimationFrame()
{
Collisions = new List<CollisionResult>();
YawRadians = 0.0;
}
}
@ -136,6 +138,7 @@ namespace NavisworksTransport.Core.Animation
private Point3D _currentPosition; // 存储部件的当前位置
private AnimationState _currentState = AnimationState.Idle;
private double _pausedProgress = 0.0; // 暂停时的进度0-1之间
private double _currentYaw = 0.0; // 当前偏航角(弧度)
// TimeLiner 集成
private TimeLinerIntegrationManager _timeLinerManager;
@ -259,11 +262,17 @@ namespace NavisworksTransport.Core.Animation
_animationDuration = durationSeconds;
// 保存原始变换以便重置
_originalTransform = GetCurrentTransform(_animatedObject);
_originalTransform = _animatedObject.Transform; // 使用真正的Transform不是自造的
// 保存车辆的原始中心位置
var originalBoundingBox = animatedObject.BoundingBox();
_originalCenter = originalBoundingBox.Center;
// 调试查看原始Transform的组成
var origComponents = _originalTransform.Factor();
LogManager.Info($"[原始Transform] Translation=({origComponents.Translation.X:F2},{origComponents.Translation.Y:F2},{origComponents.Translation.Z:F2})");
LogManager.Info($"[原始Transform] Scale=({origComponents.Scale.X:F2},{origComponents.Scale.Y:F2},{origComponents.Scale.Z:F2})");
LogManager.Info($"[原始Transform] 包围盒Center=({_originalCenter.X:F2},{_originalCenter.Y:F2},{_originalCenter.Z:F2})");
// 预计算动画帧和碰撞
PrecomputeAnimationFrames();
@ -442,11 +451,23 @@ namespace NavisworksTransport.Core.Animation
LogManager.Info($"空间查询半径: {searchRadiusInModelUnits:F2} 模型单位");
}
// 预计算每一帧
// 第一遍:收集所有帧位置
var framePositions = new List<Point3D>();
for (int i = 0; i < totalFrames; i++)
{
double progress = (double)i / totalFrames;
var framePosition = InterpolatePosition(progress);
framePositions.Add(framePosition);
}
// 第二遍:计算朝向并预计算每一帧
for (int i = 0; i < totalFrames; i++)
{
double progress = (double)i / totalFrames;
var framePosition = framePositions[i];
// 计算朝向(基于前后帧)
double yawRadians = ComputeYawFromPath(i, framePositions);
// 创建帧数据
var frame = new AnimationFrame
@ -454,11 +475,12 @@ namespace NavisworksTransport.Core.Animation
Index = i,
Progress = progress,
Position = framePosition,
YawRadians = yawRadians,
Collisions = new List<CollisionResult>()
};
// 虚拟碰撞检测(不移动实际物体)
var virtualBoundingBox = CreateVirtualBoundingBox(framePosition, boundingBoxSize);
// 虚拟碰撞检测(不移动实际物体),考虑旋转
var virtualBoundingBox = CreateVirtualBoundingBox(framePosition, boundingBoxSize, yawRadians);
IEnumerable<ModelItem> nearbyObjects;
if (manualOverrideActive)
@ -530,19 +552,116 @@ namespace NavisworksTransport.Core.Animation
}
/// <summary>
/// 创建虚拟包围盒(用于碰撞检测)
/// 根据路径计算指定帧的yaw角度
/// </summary>
private BoundingBox3D CreateVirtualBoundingBox(Point3D position, Vector3D size)
private double ComputeYawFromPath(int frameIndex, List<Point3D> framePositions)
{
if (framePositions == null || framePositions.Count < 2)
return 0.0;
int totalFrames = framePositions.Count;
Point3D currentPos = framePositions[frameIndex];
Point3D nextPos;
// 首帧或中间帧:看向下一帧
if (frameIndex < totalFrames - 1)
{
nextPos = framePositions[frameIndex + 1];
}
// 尾帧:保持上一帧的方向
else if (frameIndex > 0)
{
Point3D prevPos = framePositions[frameIndex - 1];
nextPos = new Point3D(
currentPos.X + (currentPos.X - prevPos.X),
currentPos.Y + (currentPos.Y - prevPos.Y),
currentPos.Z
);
}
else
{
return 0.0; // 只有一帧
}
// 计算XY平面的方向向量
double dx = nextPos.X - currentPos.X;
double dy = nextPos.Y - currentPos.Y;
double length = Math.Sqrt(dx * dx + dy * dy);
// 如果距离太小保持当前yaw或返回0
if (length < 1e-6)
return (frameIndex > 0 && frameIndex < totalFrames) ? ComputeYawFromPath(frameIndex - 1, framePositions) : 0.0;
// 计算yaw角度atan2返回弧度
return Math.Atan2(dy, dx);
}
/// <summary>
/// 创建虚拟包围盒(用于碰撞检测),考虑旋转
/// </summary>
private BoundingBox3D CreateVirtualBoundingBox(Point3D position, Vector3D size, double yawRadians = 0.0)
{
// 如果没有旋转,返回简单的轴对齐包围盒
if (Math.Abs(yawRadians) < 1e-6)
{
return new BoundingBox3D(
new Point3D(
position.X - size.X / 2,
position.Y - size.Y / 2,
position.Z
),
new Point3D(
position.X + size.X / 2,
position.Y + size.Y / 2,
position.Z + size.Z
)
);
}
// 有旋转:计算旋转后的轴对齐包围盒
// 1. 定义物体包围盒的4个底面角点相对于中心
double halfX = size.X / 2;
double halfY = size.Y / 2;
var corners = new[]
{
new { x = -halfX, y = -halfY },
new { x = halfX, y = -halfY },
new { x = halfX, y = halfY },
new { x = -halfX, y = halfY }
};
// 2. 旋转这些角点
double cos = Math.Cos(yawRadians);
double sin = Math.Sin(yawRadians);
double minX = double.MaxValue;
double maxX = double.MinValue;
double minY = double.MaxValue;
double maxY = double.MinValue;
foreach (var corner in corners)
{
// 旋转公式x' = x*cos - y*sin, y' = x*sin + y*cos
double rotatedX = corner.x * cos - corner.y * sin;
double rotatedY = corner.x * sin + corner.y * cos;
minX = Math.Min(minX, rotatedX);
maxX = Math.Max(maxX, rotatedX);
minY = Math.Min(minY, rotatedY);
maxY = Math.Max(maxY, rotatedY);
}
// 3. 创建包含所有旋转后角点的轴对齐包围盒
return new BoundingBox3D(
new Point3D(
position.X - size.X / 2,
position.Y - size.Y / 2,
position.X + minX,
position.Y + minY,
position.Z
),
new Point3D(
position.X + size.X / 2,
position.Y + size.Y / 2,
position.X + maxX,
position.Y + maxY,
position.Z + size.Z
)
);
@ -651,6 +770,21 @@ namespace NavisworksTransport.Core.Animation
_animationFrameCount = 0; // 重置帧计数
_currentFrameIndex = 0; // 重置当前帧索引,确保从第一帧开始
_pausedProgress = 0.0; // 重置暂停进度
// 初始化yaw为第一帧的yaw避免第一帧就产生旋转增量
if (_animationFrames != null && _animationFrames.Count > 0)
{
var firstFrame = _animationFrames[0];
_currentYaw = firstFrame.YawRadians; // 关键设置为第一帧的yaw使deltaYaw=0
// 立即设置到第一帧的位置和朝向此时deltaYaw=0只有平移
UpdateObjectPosition(firstFrame.Position, firstFrame.YawRadians);
LogManager.Debug($"[动画开始] 设置初始位置和朝向: pos=({firstFrame.Position.X:F2},{firstFrame.Position.Y:F2}), yaw={firstFrame.YawRadians:F3}rad");
}
else
{
_currentYaw = 0.0;
}
// 重置动画状态
_lastFrameTime = DateTime.MinValue;
@ -1224,26 +1358,64 @@ namespace NavisworksTransport.Core.Animation
}
/// <summary>
/// 更新对象位置
/// 更新对象位置和朝向(支持绕物体中心旋转)
/// </summary>
private void UpdateObjectPosition(Point3D newPosition)
private void UpdateObjectPosition(Point3D newPosition, double newYaw = double.NaN)
{
try
{
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 正确的增量变换:计算从当前位置到新位置的偏移
var incrementalOffset = new Vector3D(
// 计算平移和旋转的增量
var deltaPos = new Vector3D(
newPosition.X - _currentPosition.X,
newPosition.Y - _currentPosition.Y,
newPosition.Z - _currentPosition.Z
);
// 创建增量变换
var incrementalTransform = Transform3D.CreateTranslation(incrementalOffset);
Transform3D incrementalTransform;
// 应用增量变换(不重置之前的变换)
if (!double.IsNaN(newYaw))
{
// 有旋转:需要实现"绕物体当前位置自转"
// 由于Transform3DComponents.Rotation总是绕世界原点旋转
// 我们需要手动计算旋转导致的位置偏移,并补偿
double deltaYaw = newYaw - _currentYaw;
// 计算绕当前位置旋转的等效变换:
// 1. 如果绕原点旋转deltaYaw当前位置_currentPosition会移动到哪里
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;
// 2. 但我们希望物体绕自己旋转位置移动到newPosition
// 所以需要的平移 = newPosition - (旋转后的位置)
var compensatedTranslation = new Vector3D(
newPosition.X - rotatedX,
newPosition.Y - rotatedY,
newPosition.Z - _currentPosition.Z // Z保持deltaPos
);
// 3. 组合:先旋转(绕原点),再平移(补偿+目标位置)
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = compensatedTranslation;
incrementalTransform = components.Combine();
_currentYaw = newYaw;
}
else
{
// 纯平移
incrementalTransform = Transform3D.CreateTranslation(deltaPos);
}
// 应用增量变换false = 增量模式)
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
// 更新当前位置
@ -1440,9 +1612,9 @@ namespace NavisworksTransport.Core.Animation
_currentFrameIndex = frameIndex;
// 更新对象位置
// 更新对象位置和朝向
var frameData = _animationFrames[_currentFrameIndex];
UpdateObjectPosition(frameData.Position);
UpdateObjectPosition(frameData.Position, frameData.YawRadians);
// 更新碰撞高亮
UpdateCollisionHighlightFromFrame();
@ -2004,11 +2176,11 @@ namespace NavisworksTransport.Core.Animation
{
_currentFrameIndex = nextFrameIndex;
// 使用预计算的帧位置
// 使用预计算的帧位置和朝向
if (_currentFrameIndex < _animationFrames.Count)
{
var frameData = _animationFrames[_currentFrameIndex];
UpdateObjectPosition(frameData.Position);
UpdateObjectPosition(frameData.Position, frameData.YawRadians);
// 更新碰撞高亮(基于预计算结果)
UpdateCollisionHighlightFromFrame();

View File

@ -274,6 +274,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 功能测试命令
public ICommand TestVoxelGridSDFCommand { get; private set; }
public ICommand TestVoxelPathFindingCommand { get; private set; }
public ICommand ReadTransformTestCommand { get; private set; }
#endregion
@ -377,6 +378,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 功能测试命令
TestVoxelGridSDFCommand = new RelayCommand(() => ExecuteTestVoxelGridSDF());
TestVoxelPathFindingCommand = new RelayCommand(() => ExecuteTestVoxelPathFinding());
ReadTransformTestCommand = new RelayCommand(() => ExecuteReadTransformTest());
LogManager.Info("系统管理命令初始化完成");
}
@ -1025,6 +1027,79 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "体素路径规划测试");
}
/// <summary>
/// 读取选中对象的Transform信息并测试旋转
/// </summary>
private void ExecuteReadTransformTest()
{
try
{
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var selection = doc.CurrentSelection.SelectedItems;
if (selection.Count == 0)
{
System.Windows.MessageBox.Show("请先选择一个对象!", "提示",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
return;
}
var item = selection[0];
var modelItems = new Autodesk.Navisworks.Api.ModelItemCollection { item };
// === 读取初始状态 ===
var transform1 = item.Transform;
var components1 = transform1.Factor();
var bbox1 = item.BoundingBox();
var center1 = bbox1.Center;
var info = $"=== 初始状态 ===\n";
info += $"对象: {item.DisplayName}\n";
info += $"包围盒中心: ({center1.X:F3}, {center1.Y:F3}, {center1.Z:F3})\n";
info += $"Transform.Translation: ({components1.Translation.X:F3}, {components1.Translation.Y:F3}, {components1.Translation.Z:F3})\n";
info += $"Transform.Rotation: {components1.Rotation}\n\n";
// === 应用旋转override ===
// 绕Z轴旋转45度π/4弧度
double angle = Math.PI / 4; // 45度
var rotationTransform = new Autodesk.Navisworks.Api.Transform3D(
new Autodesk.Navisworks.Api.Rotation3D(
new Autodesk.Navisworks.Api.UnitVector3D(0, 0, 1),
angle
)
);
// 应用overridefalse=增量模式)
doc.Models.OverridePermanentTransform(modelItems, rotationTransform, false);
info += $"=== 应用旋转后绕Z轴45度===\n";
// === 重新读取状态 ===
var transform2 = item.Transform;
var components2 = transform2.Factor();
var bbox2 = item.BoundingBox();
var center2 = bbox2.Center;
info += $"包围盒中心: ({center2.X:F3}, {center2.Y:F3}, {center2.Z:F3})\n";
info += $"Transform.Translation: ({components2.Translation.X:F3}, {components2.Translation.Y:F3}, {components2.Translation.Z:F3})\n";
info += $"Transform.Rotation: {components2.Rotation}\n\n";
info += $"=== 对比 ===\n";
info += $"包围盒中心变化: ({center2.X - center1.X:F3}, {center2.Y - center1.Y:F3}, {center2.Z - center1.Z:F3})\n";
info += $"item.Transform是否变化: {!transform1.Equals(transform2)}\n";
LogManager.Info(info);
System.Windows.MessageBox.Show(info, "Transform测试",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"错误: {ex.Message}", "错误",
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
LogManager.Error($"读取Transform失败: {ex.Message}\n{ex.StackTrace}");
}
}
#endregion
#region

View File

@ -270,6 +270,12 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav
Command="{Binding TestVoxelPathFindingCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="使用3D A*算法在体素网格中规划路径,支持楼梯、坡道等垂直移动"/>
<!-- 读取Transform测试按钮 -->
<Button Content="读取Transform"
Command="{Binding ReadTransformTestCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="读取选中对象的Transform信息包括旋转角度"/>
</StackPanel>
</StackPanel>
</Border>