增加了虚拟物流车辆动画生成和碰撞检测功能

This commit is contained in:
tian 2025-12-10 16:59:35 +08:00
parent e5b8501a63
commit 7446431f9c
11 changed files with 927 additions and 32 deletions

View File

@ -121,6 +121,8 @@
<Compile Include="src\Core\IdleEventManager.cs" />
<!-- Core - Document State Management -->
<Compile Include="src\Core\DocumentStateManager.cs" />
<!-- Core - Virtual Vehicle Management -->
<Compile Include="src\Core\VirtualVehicleManager.cs" />
<!-- Core - Configuration Management -->
<Compile Include="src\Core\Config\SystemConfig.cs" />
<Compile Include="src\Core\Config\ConfigManager.cs" />

View File

@ -2503,3 +2503,25 @@ await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
- ✅ 相同门窗的多实例 → 正确去重
- ✅ 地板的多片段 → 正确保留
### Setting “Units and Transform” values via Navisworks API
https://blog.autodesk.io/setting-units-and-transform-values-via-navisworks-api/
DocumentModels models = doc.Models;
//Get the required model from DocumentModels
Model model;
Transform3D oldTransform3d=model.Transform;
Transform3DComponents transform3dComponents = oldTransform3d.Factor();
//Get Values
Vector3D originVector3D = transform3dComponents.Translation;
Vector3D scaleVector3D = transform3dComponents.Scale;
Rotation3D rotationVector3D = transform3dComponents.Rotation;
//Set Values
transform3dComponents.Translation = new Vector3D(origin_X, origin_Y, origin_Z); //Eg: new Vector3D(10,10,10);
transform3dComponents.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), 0.872665); //Here 50 degree=0.872665 radian
transform3dComponents.Scale = new Vector3D(scale_X, scale_Y, scale_Z); //Eg: new Vector3D(2,2,2);
Transform3D newTransform3D = transform3dComponents.Combine();
//Change model units value
Units units = Units.Meters;
models.SetModelUnitsAndTransform(model, units , newTransform3D, true);

View File

@ -11,7 +11,7 @@
1. [ ] (功能)碰撞检测时,增加手工指定被检测构件,用特殊颜色标识
2. [ ] (功能)动画时,物流模型朝向随路径变化
3. [ ] (功能)动画检测,增加使用模拟物流构件立方体选项
3. [x] (功能)动画生成,增加使用模拟物流车辆立方体选项
4. [ ] (功能)动画检测时,过滤门和其他可通行构件
5. [ ] BUG只有手动路径时导出路径按钮没激活
6. [ ] BUG重复打开模型有时程序崩溃需要先关闭物流插件窗口

View File

@ -0,0 +1,288 @@
# 虚拟车辆立方体碰撞检测功能设计方案
## 需求背景
当前项目中,检测动画用的移动物体是手工选择的。需要增加一种方式,按照路径编辑中车辆的长宽高,动态生成一个立方体,用它来进行碰撞检测。
**需求来源**`doc/requirement/todo_features.md` - 2025/12/08 功能点3
> (功能)动画检测,增加使用模拟物流构件立方体选项
## 方案选择
### 方案A已弃用虚拟包围盒
- 仅使用虚拟包围盒进行碰撞检测
- **问题**Clash Detective需要两个真实的ModelItem进行精确几何体验证虚拟包围盒无法满足
### 方案B采用创建真实几何体
- 在Navisworks中动态创建一个真实的立方体几何体
- 将其作为动画对象,复用现有的全部碰撞检测逻辑
- **优势**完全复用现有代码支持Clash Detective精确验证
## 核心思路
**关键洞察**虚拟车辆本质上就是一个动态创建的立方体ModelItem一旦创建完成后续流程与手动选择物体**完全一致**。
```
用户选择"虚拟车辆模式"
根据车辆尺寸创建立方体几何体NWD/NWC文件或内存几何体
将创建的立方体作为 _animatedObject
【完全复用现有流程】
- SetupAnimation()
- PrecomputeAnimationFrames()
- StartAnimation()
- CreateClashTestAfterAnimation()
```
## 技术实现
### 1. 创建立方体几何体的方法
Navisworks API有以下几种方式创建几何体
#### 方法1使用临时NWD文件推荐
```csharp
// 1. 创建临时NWD文件包含立方体
// 2. 追加到当前文档
// 3. 获取追加后的ModelItem引用
// 4. 动画结束后移除临时模型
```
#### 方法2使用Presenter API如果可用
```csharp
// 使用 Autodesk.Navisworks.Api.Presenter 命名空间
// 创建临时几何体用于可视化
```
#### 方法3使用外部工具生成
```csharp
// 预先创建一个单位立方体NWC文件
// 运行时追加并缩放到目标尺寸
```
### 2. 实现方案详细设计
#### 2.1 创建 VirtualVehicleManager 类
```csharp
/// <summary>
/// 虚拟车辆管理器 - 负责创建和管理虚拟车辆几何体
/// </summary>
public class VirtualVehicleManager
{
private static VirtualVehicleManager _instance;
public static VirtualVehicleManager Instance => _instance ?? (_instance = new VirtualVehicleManager());
private ModelItem _virtualVehicleModelItem;
private string _tempFilePath;
/// <summary>
/// 创建虚拟车辆立方体
/// </summary>
/// <param name="lengthMeters">长度(米)</param>
/// <param name="widthMeters">宽度(米)</param>
/// <param name="heightMeters">高度(米)</param>
/// <param name="position">初始位置</param>
/// <returns>创建的ModelItem</returns>
public ModelItem CreateVirtualVehicle(double lengthMeters, double widthMeters, double heightMeters, Point3D position)
{
try
{
// 1. 清理之前的虚拟车辆
RemoveVirtualVehicle();
// 2. 创建临时NWD文件包含立方体
_tempFilePath = CreateCubeNwdFile(lengthMeters, widthMeters, heightMeters);
// 3. 追加到当前文档
var doc = Application.ActiveDocument;
doc.AppendFile(_tempFilePath);
// 4. 获取追加后的ModelItem最后一个模型的根节点
_virtualVehicleModelItem = GetLastAppendedModel();
// 5. 移动到指定位置
MoveToPosition(_virtualVehicleModelItem, position);
LogManager.Info($"虚拟车辆创建成功: {lengthMeters:F1}m × {widthMeters:F1}m × {heightMeters:F1}m");
return _virtualVehicleModelItem;
}
catch (Exception ex)
{
LogManager.Error($"创建虚拟车辆失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 移除虚拟车辆
/// </summary>
public void RemoveVirtualVehicle()
{
if (_virtualVehicleModelItem != null)
{
// 从文档中移除模型
// ...
_virtualVehicleModelItem = null;
}
// 清理临时文件
if (!string.IsNullOrEmpty(_tempFilePath) && File.Exists(_tempFilePath))
{
File.Delete(_tempFilePath);
_tempFilePath = null;
}
}
/// <summary>
/// 获取当前虚拟车辆
/// </summary>
public ModelItem CurrentVirtualVehicle => _virtualVehicleModelItem;
}
```
#### 2.2 修改 AnimationControlViewModel
```csharp
private void ExecuteGenerateAnimation()
{
try
{
ModelItem animatedObject;
if (UseVirtualVehicle)
{
// 创建虚拟车辆几何体
var startPosition = CurrentPathRoute.Points.First();
animatedObject = VirtualVehicleManager.Instance.CreateVirtualVehicle(
VirtualVehicleLength,
VirtualVehicleWidth,
VirtualVehicleHeight,
new Point3D(startPosition.X, startPosition.Y, startPosition.Z)
);
LogManager.Info($"使用虚拟车辆: {VirtualVehicleLength:F1}m × {VirtualVehicleWidth:F1}m × {VirtualVehicleHeight:F1}m");
}
else
{
animatedObject = SelectedAnimatedObject;
}
// 【以下完全复用现有逻辑】
var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
_pathAnimationManager.CreateAnimation(
animatedObject, // 无论是手动选择的还是虚拟车辆都是真实的ModelItem
pathPoints,
AnimationDuration,
CurrentPathRoute.Name,
CurrentPathRoute.Id
);
// ... 其余现有逻辑不变 ...
}
catch (Exception ex)
{
// ...
}
}
```
#### 2.3 动画结束后清理
```csharp
// 在动画完成后的处理中
private void OnAnimationCompleted()
{
// ... 现有的报告生成和Clash测试创建 ...
// 如果使用了虚拟车辆,询问用户是否保留
if (UseVirtualVehicle && VirtualVehicleManager.Instance.CurrentVirtualVehicle != null)
{
// 可选:提示用户是否保留虚拟车辆
// 或者自动移除
// VirtualVehicleManager.Instance.RemoveVirtualVehicle();
}
}
```
### 3. 创建立方体NWD文件的技术细节
由于Navisworks API不直接支持创建几何体可以采用以下方案
#### 方案A预制单位立方体 + 缩放
1. 预先准备一个1m×1m×1m的立方体NWC文件放在插件资源目录
2. 运行时追加这个文件
3. 使用`Transform3D.CreateScale`缩放到目标尺寸
```csharp
private ModelItem CreateScaledCube(double length, double width, double height)
{
// 追加预制的单位立方体
var unitCubePath = Path.Combine(PluginResourcesPath, "unit_cube.nwc");
doc.AppendFile(unitCubePath);
var cubeItem = GetLastAppendedModel();
// 缩放到目标尺寸
var scaleTransform = Transform3D.CreateScale(length, width, height);
doc.Models.OverridePermanentTransform(new ModelItemCollection { cubeItem }, scaleTransform, true);
return cubeItem;
}
```
#### 方案B使用FBX/OBJ中间格式
1. 运行时生成OBJ格式的立方体文件
2. 使用Navisworks的文件转换功能导入
### 4. 关于预制立方体文件
需要准备一个`unit_cube.nwc`文件:
- 尺寸1m × 1m × 1m
- 原点:底面中心在原点
- 材质:半透明(便于观察碰撞)
可以使用以下工具创建:
1. AutoCAD/Revit 导出
2. 3ds Max 导出
3. 简单的OBJ文件手动编写后转换
## 修改文件清单
| 文件 | 修改类型 | 主要改动 |
|------|----------|----------|
| `VirtualVehicleManager.cs` | **新增** | 虚拟车辆几何体创建和管理 |
| `AnimationControlView.xaml` | 修改 | 添加RadioButton选择组和虚拟车辆尺寸显示 |
| `AnimationControlViewModel.cs` | 修改 | 调用VirtualVehicleManager创建虚拟车辆 |
| `LogisticsControlPanel.xaml.cs` | 修改 | 添加车辆参数同步逻辑 |
| `resources/unit_cube.nwc` | **新增** | 预制的单位立方体模型文件 |
## 关键优势
1. **完全复用现有代码**创建真实ModelItem后所有现有逻辑动画、碰撞检测、Clash Detective测试、报告生成无需任何修改
2. **真实的碰撞检测结果**Clash Detective可以进行精确几何体验证
3. **最小改动原则**:只需添加创建几何体的逻辑,其他代码不变
## 测试要点
1. **几何体创建**
- 虚拟车辆立方体正确创建
- 尺寸和位置正确
2. **碰撞检测**
- 与手动选择物体的检测结果一致
- Clash Detective测试正常生成
3. **清理**
- 动画结束后正确清理临时文件
- 用户可选择是否保留虚拟车辆
## 实现步骤
1. 准备预制的单位立方体NWC文件
2. 创建VirtualVehicleManager类
3. 修改AnimationControlViewModel调用VirtualVehicleManager
4. 测试完整流程

BIN
resources/unit_cube.nwc Normal file

Binary file not shown.

48
resources/unit_cube.obj Normal file
View File

@ -0,0 +1,48 @@
# Unit Cube 1m x 1m x 1m
# Bottom center at origin (0, 0, 0)
# For Navisworks Virtual Vehicle
# Vertices (8 corners)
# Bottom face (Z = 0)
v -0.5 -0.5 0.0
v 0.5 -0.5 0.0
v 0.5 0.5 0.0
v -0.5 0.5 0.0
# Top face (Z = 1)
v -0.5 -0.5 1.0
v 0.5 -0.5 1.0
v 0.5 0.5 1.0
v -0.5 0.5 1.0
# Normals
vn 0 0 -1
vn 0 0 1
vn 0 -1 0
vn 0 1 0
vn -1 0 0
vn 1 0 0
# Faces (6 faces, 2 triangles each)
# Bottom face (Z = 0, normal -Z)
f 1//1 3//1 2//1
f 1//1 4//1 3//1
# Top face (Z = 1, normal +Z)
f 5//2 6//2 7//2
f 5//2 7//2 8//2
# Front face (Y = -0.5, normal -Y)
f 1//3 2//3 6//3
f 1//3 6//3 5//3
# Back face (Y = 0.5, normal +Y)
f 3//4 4//4 8//4
f 3//4 8//4 7//4
# Left face (X = -0.5, normal -X)
f 1//5 5//5 8//5
f 1//5 8//5 4//5
# Right face (X = 0.5, normal +X)
f 2//6 3//6 7//6
f 2//6 7//6 6//6

View File

@ -1820,7 +1820,6 @@ namespace NavisworksTransport.Core.Animation
SetState(AnimationState.Ready);
}
#region

View File

@ -0,0 +1,306 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Core
{
/// <summary>
/// 虚拟车辆管理器 - 负责创建和管理虚拟车辆几何体
/// 通过追加预制的单位立方体NWC文件并缩放来创建指定尺寸的虚拟车辆
/// </summary>
public class VirtualVehicleManager
{
private static VirtualVehicleManager _instance;
private static readonly object _lock = new object();
/// <summary>
/// 单例实例
/// </summary>
public static VirtualVehicleManager Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new VirtualVehicleManager();
}
}
}
return _instance;
}
}
private ModelItem _virtualVehicleModelItem;
private Model _virtualVehicleModel;
private bool _isVirtualVehicleActive;
/// <summary>
/// 当前虚拟车辆ModelItem
/// </summary>
public ModelItem CurrentVirtualVehicle => _virtualVehicleModelItem;
/// <summary>
/// 虚拟车辆是否激活
/// </summary>
public bool IsVirtualVehicleActive => _isVirtualVehicleActive;
private VirtualVehicleManager()
{
_isVirtualVehicleActive = false;
}
/// <summary>
/// 创建虚拟车辆立方体
/// </summary>
/// <param name="lengthMeters">长度(米)- X方向</param>
/// <param name="widthMeters">宽度(米)- Y方向</param>
/// <param name="heightMeters">高度(米)- Z方向</param>
/// <returns>创建的ModelItem</returns>
public ModelItem CreateVirtualVehicle(double lengthMeters, double widthMeters, double heightMeters)
{
try
{
LogManager.Info($"=== 创建虚拟车辆 ===");
LogManager.Info($"目标尺寸: {lengthMeters:F2}m × {widthMeters:F2}m × {heightMeters:F2}m");
// 1. 清理之前的虚拟车辆
RemoveVirtualVehicle();
// 2. 获取单位立方体文件路径
var unitCubePath = GetUnitCubeFilePath();
if (string.IsNullOrEmpty(unitCubePath) || !File.Exists(unitCubePath))
{
throw new FileNotFoundException($"找不到单位立方体文件: {unitCubePath}");
}
LogManager.Info($"单位立方体文件: {unitCubePath}");
// 3. 记录追加前的模型数量
var doc = Application.ActiveDocument;
int modelCountBefore = doc.Models.Count;
LogManager.Info($"追加前模型数量: {modelCountBefore}");
// 4. 追加单位立方体文件
if (!doc.TryAppendFile(unitCubePath))
{
throw new InvalidOperationException($"无法追加文件: {unitCubePath}");
}
// 5. 获取新追加的模型
int modelCountAfter = doc.Models.Count;
LogManager.Info($"追加后模型数量: {modelCountAfter}");
if (modelCountAfter <= modelCountBefore)
{
throw new InvalidOperationException("追加文件后模型数量未增加");
}
_virtualVehicleModel = doc.Models.Last();
_virtualVehicleModelItem = _virtualVehicleModel.RootItem;
LogManager.Info($"虚拟车辆模型: {_virtualVehicleModel.FileName}");
LogManager.Info($"虚拟车辆根项: {_virtualVehicleModelItem.DisplayName}");
// 6. 获取实际的几何体项(可能在子节点中)
var geometryItem = FindFirstGeometryItem(_virtualVehicleModelItem);
if (geometryItem != null && !geometryItem.Equals(_virtualVehicleModelItem))
{
LogManager.Info($"找到几何体项: {geometryItem.DisplayName}");
_virtualVehicleModelItem = geometryItem;
}
// 7. 缩放到目标尺寸
ScaleVirtualVehicle(lengthMeters, widthMeters, heightMeters);
_isVirtualVehicleActive = true;
LogManager.Info($"虚拟车辆创建成功");
return _virtualVehicleModelItem;
}
catch (Exception ex)
{
LogManager.Error($"创建虚拟车辆失败: {ex.Message}", ex);
RemoveVirtualVehicle();
throw;
}
}
/// <summary>
/// 缩放虚拟车辆到目标尺寸
/// 使用DocumentModels.SetModelUnitsAndTransform方法进行模型级缩放
/// </summary>
private void ScaleVirtualVehicle(double lengthMeters, double widthMeters, double heightMeters)
{
if (_virtualVehicleModel == null || _virtualVehicleModelItem == null) return;
var doc = Application.ActiveDocument;
// 获取当前包围盒单位立方体应该是1m x 1m x 1m
var boundingBox = _virtualVehicleModelItem.BoundingBox();
double currentLength = boundingBox.Max.X - boundingBox.Min.X;
double currentWidth = boundingBox.Max.Y - boundingBox.Min.Y;
double currentHeight = boundingBox.Max.Z - boundingBox.Min.Z;
// 获取单位转换因子(米到模型单位)
double metersToUnits = Utils.UnitsConverter.GetMetersToUnitsConversionFactor(doc.Units);
// 当前尺寸(米)
double currentLengthMeters = currentLength / metersToUnits;
double currentWidthMeters = currentWidth / metersToUnits;
double currentHeightMeters = currentHeight / metersToUnits;
LogManager.Info($"当前尺寸: {currentLengthMeters:F2}m × {currentWidthMeters:F2}m × {currentHeightMeters:F2}m");
LogManager.Info($"目标尺寸: {lengthMeters:F2}m × {widthMeters:F2}m × {heightMeters:F2}m");
// 计算缩放比例
double scaleX = currentLengthMeters > 0 ? lengthMeters / currentLengthMeters : lengthMeters;
double scaleY = currentWidthMeters > 0 ? widthMeters / currentWidthMeters : widthMeters;
double scaleZ = currentHeightMeters > 0 ? heightMeters / currentHeightMeters : heightMeters;
LogManager.Info($"缩放比例: X={scaleX:F2}, Y={scaleY:F2}, Z={scaleZ:F2}");
// 使用Transform3DComponents进行缩放
// 获取当前模型的变换并分解
Transform3D currentTransform = _virtualVehicleModel.Transform;
Transform3DComponents transformComponents = currentTransform.Factor();
// 获取当前值
Vector3D currentTranslation = transformComponents.Translation;
Rotation3D currentRotation = transformComponents.Rotation;
Vector3D currentScale = transformComponents.Scale;
LogManager.Info($"当前变换 - 平移: ({currentTranslation.X:F2}, {currentTranslation.Y:F2}, {currentTranslation.Z:F2})");
LogManager.Info($"当前变换 - 缩放: ({currentScale.X:F2}, {currentScale.Y:F2}, {currentScale.Z:F2})");
// 设置新的缩放值(在原有缩放基础上乘以新的缩放比例)
transformComponents.Scale = new Vector3D(
currentScale.X * scaleX,
currentScale.Y * scaleY,
currentScale.Z * scaleZ
);
// 组合成新的变换
Transform3D newTransform = transformComponents.Combine();
// 获取当前模型单位
Units currentUnits = _virtualVehicleModel.Units;
// 应用新的变换
doc.Models.SetModelUnitsAndTransform(_virtualVehicleModel, currentUnits, newTransform, false);
// 验证缩放结果
var newBoundingBox = _virtualVehicleModelItem.BoundingBox();
double newLength = newBoundingBox.Max.X - newBoundingBox.Min.X;
double newWidth = newBoundingBox.Max.Y - newBoundingBox.Min.Y;
double newHeight = newBoundingBox.Max.Z - newBoundingBox.Min.Z;
double newLengthMeters = newLength / metersToUnits;
double newWidthMeters = newWidth / metersToUnits;
double newHeightMeters = newHeight / metersToUnits;
LogManager.Info($"缩放后尺寸: {newLengthMeters:F2}m × {newWidthMeters:F2}m × {newHeightMeters:F2}m");
}
/// <summary>
/// 移除虚拟车辆隐藏模型因为Navisworks API不支持直接删除追加的模型
/// </summary>
public void RemoveVirtualVehicle()
{
try
{
if (_virtualVehicleModelItem != null)
{
// Navisworks API不支持直接删除追加的模型
// 使用隐藏方式处理
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { _virtualVehicleModelItem };
doc.Models.SetHidden(modelItems, true);
LogManager.Info($"已隐藏虚拟车辆模型");
}
// 清理引用但不尝试删除模型
_virtualVehicleModel = null;
_virtualVehicleModelItem = null;
_isVirtualVehicleActive = false;
}
catch (Exception ex)
{
LogManager.Error($"隐藏虚拟车辆失败: {ex.Message}");
_virtualVehicleModel = null;
_virtualVehicleModelItem = null;
_isVirtualVehicleActive = false;
}
}
/// <summary>
/// 获取单位立方体文件路径
/// </summary>
private string GetUnitCubeFilePath()
{
// 方法1从插件目录获取
var assemblyLocation = Assembly.GetExecutingAssembly().Location;
var pluginDir = Path.GetDirectoryName(assemblyLocation);
// 尝试多个可能的位置
var possiblePaths = new[]
{
Path.Combine(pluginDir, "resources", "unit_cube.nwc"),
Path.Combine(pluginDir, "unit_cube.nwc"),
Path.Combine(pluginDir, "..", "resources", "unit_cube.nwc"),
// 开发时的位置
@"c:\Users\Tellme\apps\NavisworksTransport\resources\unit_cube.nwc"
};
foreach (var path in possiblePaths)
{
var fullPath = Path.GetFullPath(path);
if (File.Exists(fullPath))
{
LogManager.Info($"找到单位立方体文件: {fullPath}");
return fullPath;
}
}
LogManager.Warning($"在以下位置未找到单位立方体文件:");
foreach (var path in possiblePaths)
{
LogManager.Warning($" - {Path.GetFullPath(path)}");
}
return null;
}
/// <summary>
/// 查找第一个包含几何体的ModelItem
/// </summary>
private ModelItem FindFirstGeometryItem(ModelItem root)
{
if (root.HasGeometry)
return root;
foreach (var child in root.Children)
{
var result = FindFirstGeometryItem(child);
if (result != null)
return result;
}
return null;
}
/// <summary>
/// 清理资源
/// </summary>
public void Cleanup()
{
RemoveVirtualVehicle();
}
}
}

View File

@ -93,6 +93,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private bool _hasSelectedAnimatedObject = false;
private bool _canGenerateAnimation = false;
// 虚拟车辆相关字段
private bool _useSelectedObject = true; // 默认使用选择物体方式
private bool _useVirtualVehicle = false; // 使用虚拟车辆
private double _virtualVehicleLength = 1.0; // 虚拟车辆长度(米)
private double _virtualVehicleWidth = 1.0; // 虚拟车辆宽度(米)
private double _virtualVehicleHeight = 2.0; // 虚拟车辆高度(米)
#endregion
@ -356,6 +362,95 @@ namespace NavisworksTransport.UI.WPF.ViewModels
set => SetProperty(ref _canGenerateAnimation, value);
}
#region
/// <summary>
/// 是否使用选择的模型物体
/// </summary>
public bool UseSelectedObject
{
get => _useSelectedObject;
set
{
if (SetProperty(ref _useSelectedObject, value))
{
if (value)
{
_useVirtualVehicle = false;
OnPropertyChanged(nameof(UseVirtualVehicle));
}
UpdateCanGenerateAnimation();
}
}
}
/// <summary>
/// 是否使用虚拟车辆
/// </summary>
public bool UseVirtualVehicle
{
get => _useVirtualVehicle;
set
{
if (SetProperty(ref _useVirtualVehicle, value))
{
if (value)
{
_useSelectedObject = false;
OnPropertyChanged(nameof(UseSelectedObject));
}
UpdateCanGenerateAnimation();
}
}
}
/// <summary>
/// 虚拟车辆长度(米)- 只读,从路径编辑同步
/// </summary>
public double VirtualVehicleLength
{
get => _virtualVehicleLength;
private set => SetProperty(ref _virtualVehicleLength, value);
}
/// <summary>
/// 虚拟车辆宽度(米)- 只读,从路径编辑同步
/// </summary>
public double VirtualVehicleWidth
{
get => _virtualVehicleWidth;
private set => SetProperty(ref _virtualVehicleWidth, value);
}
/// <summary>
/// 虚拟车辆高度(米)- 只读,从路径编辑同步
/// </summary>
public double VirtualVehicleHeight
{
get => _virtualVehicleHeight;
private set => SetProperty(ref _virtualVehicleHeight, value);
}
/// <summary>
/// 设置虚拟车辆参数由主ViewModel调用
/// </summary>
public void SetVirtualVehicleParameters(double length, double width, double height)
{
VirtualVehicleLength = length;
VirtualVehicleWidth = width;
VirtualVehicleHeight = height;
LogManager.Info($"[AnimationControlViewModel] 虚拟车辆参数已更新: {length:F1}m × {width:F1}m × {height:F1}m");
// 如果当前使用虚拟车辆模式,更新生成动画的可用状态
if (UseVirtualVehicle)
{
UpdateCanGenerateAnimation();
}
}
#endregion
#region
/// <summary>
@ -1369,13 +1464,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
UpdateMainStatus("正在生成动画...", -1, true);
LogManager.Info("开始生成动画");
LogManager.Info($"开始生成动画 - 模式: {(UseVirtualVehicle ? "" : "")}");
// 🔥 新增:在生成动画阶段构建碰撞检测缓存
var cacheStartTime = DateTime.Now;
// 构建碰撞检测缓存(两种模式都需要几何对象缓存)
UpdateMainStatus("正在构建碰撞检测缓存...", -1, true);
LogManager.Info("[动画生成] 开始构建碰撞检测缓存");
var cacheStartTime = DateTime.Now;
try
{
// 构建全局缓存(与具体移动物体无关)
@ -1391,23 +1487,26 @@ namespace NavisworksTransport.UI.WPF.ViewModels
var cacheElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
LogManager.Info($"[动画生成] 碰撞检测缓存构建完成,耗时: {cacheElapsed:F1}ms");
// 构建特定于动画对象的缓存
UpdateMainStatus("正在分析动画对象...", -1, true);
var success = _pathAnimationManager.PrecomputeCollisionExclusions(SelectedAnimatedObject);
if (!success)
// 选择物体模式:还需要构建特定于动画对象的缓存
if (!UseVirtualVehicle)
{
var msg = "[动画生成] 动画对象分析失败,无法继续生成动画";
LogManager.Error(msg);
UpdateMainStatus("生成失败:对象分析错误");
return; // 关键修改:直接返回,不再回退到实时模式
UpdateMainStatus("正在分析动画对象...", -1, true);
var success = _pathAnimationManager.PrecomputeCollisionExclusions(SelectedAnimatedObject);
if (!success)
{
var msg = "[动画生成] 动画对象分析失败,无法继续生成动画";
LogManager.Error(msg);
UpdateMainStatus("生成失败:对象分析错误");
return;
}
}
}
catch (Exception cacheEx)
{
LogManager.Error($"[动画生成] 缓存构建失败: {cacheEx.Message}");
UpdateMainStatus($"生成失败:{cacheEx.Message}");
return; // 关键修改:处理异常并返回
return;
}
UpdateMainStatus("正在生成路径动画...", -1, true);
@ -1421,19 +1520,55 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 将PathRouteViewModel的点转换为Point3D列表
var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
ModelItem animatedObject;
LogManager.Info($"[ExecuteGenerateAnimation] 准备创建动画: 路径名称='{CurrentPathRoute.Name}', ID='{CurrentPathRoute.Id}', 动画对象='{SelectedAnimatedObject.DisplayName}'");
if (UseVirtualVehicle)
{
// 使用虚拟车辆:创建真实的立方体几何体
LogManager.Info($"[ExecuteGenerateAnimation] 准备创建虚拟车辆: " +
$"尺寸={VirtualVehicleLength:F1}m × {VirtualVehicleWidth:F1}m × {VirtualVehicleHeight:F1}m");
// 使用PathAnimationManager创建物体动画
_pathAnimationManager.CreateAnimation(SelectedAnimatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id);
UpdateMainStatus("正在创建虚拟车辆...", -1, true);
// 创建虚拟车辆几何体
animatedObject = Core.VirtualVehicleManager.Instance.CreateVirtualVehicle(
VirtualVehicleLength,
VirtualVehicleWidth,
VirtualVehicleHeight
);
if (animatedObject == null)
{
LogManager.Error("[动画生成] 虚拟车辆创建失败");
UpdateMainStatus("生成失败:无法创建虚拟车辆");
return;
}
LogManager.Info($"虚拟车辆创建成功: {animatedObject.DisplayName}");
}
else
{
animatedObject = SelectedAnimatedObject;
}
// 【统一使用CreateAnimation完全复用现有逻辑】
LogManager.Info($"[ExecuteGenerateAnimation] 准备创建动画: 路径名称='{CurrentPathRoute.Name}', ID='{CurrentPathRoute.Id}', " +
$"动画对象='{animatedObject.DisplayName}'");
_pathAnimationManager.CreateAnimation(animatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id);
var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
LogManager.Info($"[动画生成] 动画生成完成,总耗时: {totalElapsed:F1}ms");
LogManager.Info($"动画生成成功: 物体={animatedObject.DisplayName}, 路径={CurrentPathRoute.Name}");
if (UseVirtualVehicle)
{
LogManager.Info($"虚拟车辆尺寸: {VirtualVehicleLength:F1}×{VirtualVehicleWidth:F1}×{VirtualVehicleHeight:F1}m");
}
// 更新状态
UpdateAnimationButtonStates();
UpdateMainStatus("动画生成成功");
var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
LogManager.Info($"[动画生成] 动画生成完成,总耗时: {totalElapsed:F1}ms");
LogManager.Info($"动画生成成功: 物体={SelectedAnimatedObject.DisplayName}, 路径={CurrentPathRoute.Name}");
}
catch (Exception ex)
{
@ -1487,18 +1622,32 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// </summary>
private void UpdateCanGenerateAnimation()
{
var hasObject = SelectedAnimatedObject != null;
var hasPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
CanGenerateAnimation = hasObject && hasPath;
if (!hasObject && !hasPath)
bool hasValidObject;
if (UseVirtualVehicle)
{
UpdateMainStatus("请选择移动物体和动画路径");
// 使用虚拟车辆时,只需要有效的车辆尺寸
hasValidObject = VirtualVehicleLength > 0 &&
VirtualVehicleWidth > 0 &&
VirtualVehicleHeight > 0;
}
else if (!hasObject)
else
{
UpdateMainStatus("请选择移动物体");
// 使用选择物体时,需要已选择物体
hasValidObject = SelectedAnimatedObject != null;
}
CanGenerateAnimation = hasValidObject && hasPath;
// 更新状态提示
if (!hasPath && !hasValidObject)
{
UpdateMainStatus(UseVirtualVehicle ? "请选择动画路径" : "请选择移动物体和动画路径");
}
else if (!hasValidObject)
{
UpdateMainStatus(UseVirtualVehicle ? "虚拟车辆参数无效" : "请选择移动物体");
}
else if (!hasPath)
{

View File

@ -149,8 +149,22 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
<StackPanel>
<Label Content="生成动画" Style="{StaticResource SectionHeaderStyle}"/>
<!-- 选择移动物体 -->
<Grid Margin="0,10,0,10">
<!-- 移动物体来源选择 -->
<StackPanel Orientation="Horizontal" Margin="0,10,0,10">
<RadioButton Content="选择模型物体"
IsChecked="{Binding UseSelectedObject}"
GroupName="ObjectSource"
VerticalAlignment="Center"/>
<RadioButton Content="使用虚拟车辆"
IsChecked="{Binding UseVirtualVehicle}"
GroupName="ObjectSource"
VerticalAlignment="Center"
Margin="20,0,0,0"/>
</StackPanel>
<!-- 方式1选择模型物体 -->
<Grid Margin="0,5,0,10"
Visibility="{Binding UseSelectedObject, Converter={StaticResource BoolToVisConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
@ -181,6 +195,39 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
</StackPanel>
</Grid>
<!-- 方式2虚拟车辆 -->
<Grid Margin="0,5,0,10"
Visibility="{Binding UseVirtualVehicle, Converter={StaticResource BoolToVisConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="虚拟车辆:" Style="{StaticResource ParameterLabelStyle}" Width="80"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Margin="5,0,10,0"
FontWeight="SemiBold"
Foreground="#FF2B579A"
Background="#FFF5F5F5"
Padding="5,3">
<Run Text="长 "/>
<Run Text="{Binding VirtualVehicleLength, StringFormat=F1, Mode=OneWay}"/>
<Run Text="m × 宽 "/>
<Run Text="{Binding VirtualVehicleWidth, StringFormat=F1, Mode=OneWay}"/>
<Run Text="m × 高 "/>
<Run Text="{Binding VirtualVehicleHeight, StringFormat=F1, Mode=OneWay}"/>
<Run Text="m"/>
</TextBlock>
</Grid>
<!-- 虚拟车辆提示 -->
<TextBlock Text="提示:虚拟车辆尺寸来自路径编辑中的车辆参数设置"
Style="{StaticResource StatusTextStyle}"
Foreground="#FF666666"
Margin="85,0,0,5"
Visibility="{Binding UseVirtualVehicle, Converter={StaticResource BoolToVisConverter}}"/>
<!-- 选择路径 -->
<Grid Margin="0,5,0,10">
<Grid.ColumnDefinitions>

View File

@ -156,7 +156,10 @@ namespace NavisworksTransport.UI.WPF
// 更新引用
AnimationControlView = newAnimationControlView;
LogManager.Info("AnimationControlView初始化完成 - 支持统一状态栏");
// 初始化时同步车辆参数
SyncVehicleParametersToAnimationView();
LogManager.Info("AnimationControlView初始化完成 - 支持统一状态栏和虚拟车辆");
}
else
{
@ -411,6 +414,13 @@ namespace NavisworksTransport.UI.WPF
SyncCurrentPathToAnimationView();
LogManager.Info("PathEditingView路径选择已变化同步到动画控制视图");
}
// 当车辆参数变化时同步到AnimationControlView
else if (e.PropertyName == nameof(PathEditingViewModel.VehicleLength) ||
e.PropertyName == nameof(PathEditingViewModel.VehicleWidth) ||
e.PropertyName == nameof(PathEditingViewModel.VehicleHeight))
{
SyncVehicleParametersToAnimationView();
}
}
catch (Exception ex)
{
@ -418,6 +428,30 @@ namespace NavisworksTransport.UI.WPF
}
}
/// <summary>
/// 同步车辆参数到动画控制视图
/// </summary>
private void SyncVehicleParametersToAnimationView()
{
try
{
if (AnimationControlView?.ViewModel != null && PathEditingView?.ViewModel != null)
{
var pathEditingVM = PathEditingView.ViewModel;
AnimationControlView.ViewModel.SetVirtualVehicleParameters(
pathEditingVM.VehicleLength,
pathEditingVM.VehicleWidth,
pathEditingVM.VehicleHeight
);
LogManager.Debug($"车辆参数已同步到动画控制视图: {pathEditingVM.VehicleLength:F1}×{pathEditingVM.VehicleWidth:F1}×{pathEditingVM.VehicleHeight:F1}m");
}
}
catch (Exception ex)
{
LogManager.Error($"同步车辆参数到动画控制视图失败: {ex.Message}");
}
}
/// <summary>
/// 同步当前路径到动画控制视图
/// </summary>