基本实现物流对象沿着路径转向的功能
This commit is contained in:
parent
2bd117ff8a
commit
d63896bf63
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ packages/
|
||||
navisworks_api/
|
||||
|
||||
*.exe
|
||||
*.db
|
||||
|
||||
@ -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
|
||||
@ -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属性和自定义属性访问
|
||||
|
||||
基于官方示例的正确属性访问方法总结。
|
||||
|
||||
381
doc/working/animation_orientation_plan_20251215.md
Normal file
381
doc/working/animation_orientation_plan_20251215.md
Normal 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限制说明和解决方案)
|
||||
66
src/Commands/ReadTransformTestCommand.cs
Normal file
66
src/Commands/ReadTransformTestCommand.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
)
|
||||
);
|
||||
|
||||
// 应用override(false=增量模式)
|
||||
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 辅助方法
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user