优化碰撞检测算法,进行合理的去重后再检测;优化动画控制逻辑,避免重复订阅事件和资源清理;

This commit is contained in:
tian 2025-12-08 12:31:12 +08:00
parent a832e91b7b
commit f8320066c1
5 changed files with 549 additions and 457 deletions

View File

@ -2,6 +2,23 @@
## 功能点
### [2025/12/12]
1. [ ] (功能)碰撞检测时,增加手工指定被检测构件,用特殊颜色标识
2. [ ] (功能)动画时,物流模型朝向随路径变化
3. [ ] (功能)动画检测,增加使用模拟物流构件立方体选项
4. [ ] (功能)动画检测时,过滤门和其他可通行构件
5. [ ] BUG只有手动路径时导出路径按钮没激活
6. [ ] BUG重复打开模型有时程序崩溃需要先关闭物流插件窗口
7. [ ] (优化)生成动画时,显示要检测的构件数量,当数量大于阈值时,给于警示,并预估处理时间。
### [2025/12/05]
1. [x] BUG保存当前选择项时无响应
2. [ ] BUG相邻通道的相邻部分有边界障碍网格
3. [ ] BUG多层建筑的上层楼板通道中有超出范围或中间多出来的障碍网格可能受周围结构或下层柱子等结构的影响
4. [ ] 功能在【生成网格地图】步骤2.5: 高性能包围盒遍历处理障碍物中,在日志中记录处理进度。用于处理大型模型时评估时间。
### [2025/11/07]
1. [x] 功能制作MSI安装包实现插件的安装部署
@ -9,16 +26,16 @@
### [2025/10/21]
1. [x] 功能优化使用场景加载完成事件SceneLoaded实现文档更新后的物流列表刷新
1. [x] 优化使用场景加载完成事件SceneLoaded实现文档更新后的物流列表刷新
### [2025/10/18]
1. [x] (功能优化)为几何体提取增加文件缓存(暂时只用于体素网络)
1. [x] (功能)为几何体提取增加文件缓存(暂时只用于体素网络)
### [2025/10/14]
1. [x] 功能优化)用空间索引优化碰撞路径预处理
2. [x] 功能优化)规范文档打开前/后的初始化行为
1. [x] (优化)用空间索引优化碰撞路径预处理
2. [x] (优化)规范文档打开前/后的初始化行为
### [2025/10/12]
@ -68,8 +85,8 @@
### [2025/09/011]
1. [x] 性能优化)大模型文件分层和导出,不挂机不崩溃
2. [x] (性能优化) 提高显示/隐藏的性能
1. [x] (优化)大模型文件分层和导出,不挂机不崩溃
2. [x] (优化) 提高显示/隐藏的性能
### [2025/09/09]
@ -79,8 +96,8 @@
### [2025/09/08]
1. [x] (功能)增加局部直线优先路径策略
2. [x] (性能优化) 提高网格地图创建性能优化API的使用
3. [x] (提高稳定性) 通过idle改进UI事件管理把反射改成事件通知
2. [x] (优化) 提高网格地图创建性能优化API的使用
3. [x] (优化) 通过idle改进UI事件管理把反射改成事件通知
### [2025/09/07]
@ -91,12 +108,12 @@
### [2025/09/05]
1. [x] (功能)把三维视图选点光标改成十字形,当失去焦点时,按空格键切换回来。
2. [x] (优化和功能) 用精确几何方式进行通道网格构建,用网格可视化方式确认其正确性
2. [x] (优化) 用精确几何方式进行通道网格构建,用网格可视化方式确认其正确性
3. [x] (优化) 重写路径优化算法,确保直线和直角转弯,不引入错误路径
### [2025/09/04]
1. [x] 代码重构)将节点关系和几何体关系代码从动画管理器中抽取出来,形成工具类
1. [x] (重构)将节点关系和几何体关系代码从动画管理器中抽取出来,形成工具类
2. [x] (BUG) 特殊的运动物体动画碰撞有结果正确但ClashDetective检测不出来可能是因为树只是线不是solid类型
3. [x] (优化) 用ClashDetective API的标准用法重构碰撞检测部分增加了碰撞分组
@ -104,39 +121,39 @@
1. [x] 功能实现时间标签的UI原型
2. [x] 功能实现路径规划分析的UI原型
3. [x] 性能优化增加直接采用包围盒的2.5D自动寻路算法,代替空间索引+高度扫描算法。
3. [x] 优化增加直接采用包围盒的2.5D自动寻路算法,代替空间索引+高度扫描算法。
4. [x] BUG自动规划有时成功过滤3个通道找到26个障碍物有时失败过滤2个通道,只找到2个障碍物连续自动规划有时会崩溃。
5. [ ] BUG还有厚度为0的障碍物不一定是bug
6. [x] (性能优化) 用SearchAPI来搜索CategoryAttributeManager中的FilterByLogisticsType()、FilterTraversableItems()等方法
6. [x] (优化) 用SearchAPI来搜索CategoryAttributeManager中的FilterByLogisticsType()、FilterTraversableItems()等方法
### [2025/08/31]
1. [x]性能优化提高自动路径规划网格生成的速度原速度4楼模型0.2米4.6秒;0.1米18秒
1. [x]优化提高自动路径规划网格生成的速度原速度4楼模型0.2米4.6秒;0.1米18秒
优化空间索引后0.2米网格1.8秒0.1米网格5.1秒
### [2025/08/30]
1. [x]性能优化用几何方法识别通道的坡度变化侧面上表面轮廓线给通道网格准确的z坐标
1. [x]优化用几何方法识别通道的坡度变化侧面上表面轮廓线给通道网格准确的z坐标
2. [x]BUG动画中的包围盒检测和ClashDetective检测结果不一致是因为碰撞间隙不同造成的。Autodesk官方建议保守测试+0公差解决碰撞检测结果不准确的问题
3. [x] (功能)实现完整的路径点可视化编辑
### [2025/08/29]
1. [x]BUG路径导出只有一条路径时导出按钮不能点击导出的内容有时不完整只导出一个包含起点、一个路径点、终点的不存在的路径。
2. [x]性能优化垂直扫描处理器性能问题COM API几何提取效率极低23个候选项需要5715ms复杂几何体比简单几何体效率低4倍需要优化批量处理和并行机制。
3. [x]稳定性修复并行任务未观察异常导致程序崩溃AggregateException错误表明Task异常处理不当需要加强并行处理的异常处理和Task生命周期管理。
2. [x]优化垂直扫描处理器性能问题COM API几何提取效率极低23个候选项需要5715ms复杂几何体比简单几何体效率低4倍需要优化批量处理和并行机制。
3. [x]优化修复并行任务未观察异常导致程序崩溃AggregateException错误表明Task异常处理不当需要加强并行处理的异常处理和Task生命周期管理。
### [2025/08/28]
1. [x]将“自动规划路径”中的车辆长度、宽度的默认值改为1米安全间隙改为0.25米。高级设置中,网格的大小
1. [x](优化)将“自动规划路径”中的车辆长度、宽度的默认值改为1米安全间隙改为0.25米。高级设置中,网格的大小
默认值改为0.5米。
2. [x]将这些参数,作为插件配置文件的参数,在系统管理的插件管理中,统一管理
3. [x]修改分层预览的业务逻辑从一级节点开始遍历每个节点查找指定的分层属性如果找到记录下来作为一个分层不再遍历其下级节点如果没找到继续遍历其子节点直到找到为止遍历深度受到用户指定的遍历深度限制。特殊处理1、对于智能检测使用一组候选的分层属性对每个节点进行查找按属性的评分优先级确定选择何种属性。2、对于自定义查找用指定的分层属性在每个节点的"分层信息“属性类别中查找。
4. [x]对自动路径规划进行重构,按地面层+高度剖面投影层的方式构建可通行网格然后用A*算法获取最短路径。
2. [x](功能)将这些参数,作为插件配置文件的参数,在系统管理的插件管理中,统一管理
3. [x](优化)修改分层预览的业务逻辑从一级节点开始遍历每个节点查找指定的分层属性如果找到记录下来作为一个分层不再遍历其下级节点如果没找到继续遍历其子节点直到找到为止遍历深度受到用户指定的遍历深度限制。特殊处理1、对于智能检测使用一组候选的分层属性对每个节点进行查找按属性的评分优先级确定选择何种属性。2、对于自定义查找用指定的分层属性在每个节点的"分层信息“属性类别中查找。
4. [x](优化)对自动路径规划进行重构,按地面层+高度剖面投影层的方式构建可通行网格然后用A*算法获取最短路径。
### [2025/08/27]
1. [x]在分层预览列表中,去掉”文件大小“和”状态“列,增加:
1. [x](优化)在分层预览列表中,去掉”文件大小“和”状态“列,增加:
- “是否保存”列,内容是单选框,默认选中,选中的行,在点击“分层保存”时会保存,未选中的不保存
- 在列表下方,增加“单独显示”按钮,点击后会隐藏除了当前行之外的其他部分
- 保存文件时文件名的格式根节点名_自定义属性名_属性值_时间戳

View File

@ -0,0 +1,41 @@
# 对碰撞检测的性能进行优化
## 问题描述
1. 当前性能瓶颈:
- 动画生成建立时间长,对于某大型模型的日志:
[2025-12-08 10:02:31.838] [INFO] [动画生成] 开始构建碰撞检测缓存
[2025-12-08 10:05:28.571] [INFO] 几何对象列表缓存构建完成,耗时: 176730ms
[2025-12-08 10:05:28.571] [INFO] - 缓存对象总数: 374425 个
[2025-12-08 10:05:28.750] [INFO] 通道对象缓存构建完成,耗时: 174ms
[2025-12-08 10:05:28.751] [INFO] - 可通行物流根对象: 2 个
[2025-12-08 10:05:28.751] [INFO] - 缓存总对象数: 5 个
[2025-12-08 10:05:28.751] [INFO] [动画生成] 碰撞检测缓存构建完成,耗时: 176911.7ms
[2025-12-08 10:05:28.774] [INFO] === 构建全局空间索引 ===
[2025-12-08 10:05:28.776] [INFO] [空间索引] 车辆宽度: 1.00米 → 格子大小: 3.28模型单位
[2025-12-08 10:05:28.781] [INFO] [空间索引] 开始构建全局空间索引
[2025-12-08 10:05:28.782] [INFO] [空间索引] 格子大小: 3.28 模型单位
[2025-12-08 10:05:28.807] [INFO] [空间索引] 从缓存获取 374422 个非通道几何对象(已过滤通道)
[2025-12-08 10:05:31.538] [INFO] [空间索引] 构建完成
[2025-12-08 10:05:31.538] [INFO] - 成功索引: 374422 个对象(通道已在缓存阶段过滤)
[2025-12-08 10:05:31.538] [INFO] - 失败: 0 个对象
[2025-12-08 10:05:31.538] [INFO] - 格子数量: 58986 个
[2025-12-08 10:05:31.540] [INFO] - 耗时: 2756 ms
[2025-12-08 10:05:31.782] [INFO] [动画生成] 动画生成完成,总耗时: 179942.9ms
- 如果碰撞检测点过多检测时间长。用户实际模型中有2000多个检测点耗时近300秒。
## 解决方案
- 优化碰撞检测算法
优化详情:
分组去重:代码现在会先将所有预计算的碰撞事件按 (移动物体, 被撞物体) 进行分组。
验证即止:
对于每一对物体,按干涉深度排序。
逐帧验证:将物体移动到该帧的位置,进行精确 Clash Detective 检测。
智能跳过:一旦确认了该对物体发生了真实碰撞,就记录结果并立即停止对该对物体的后续检测。
统计信息:日志中现在会显示"确认碰撞 X 组,跳过 Y 个冗余检测点",方便评估优化效果。

View File

@ -73,7 +73,7 @@ namespace NavisworksTransport.Core.Animation
public Point3D Position { get; set; } // 该帧的位置
public List<CollisionResult> Collisions { get; set; } // 该帧的碰撞结果
public bool HasCollision => Collisions?.Count > 0;
public AnimationFrame()
{
Collisions = new List<CollisionResult>();
@ -92,7 +92,7 @@ namespace NavisworksTransport.Core.Animation
private List<ModelItem> _cachedExclusionList;
private DateTime _cacheCreatedTime;
private readonly TimeSpan _cacheValidDuration = TimeSpan.FromMinutes(30); // 缓存有效期30分钟
// === 预计算动画系统 ===
private List<AnimationFrame> _animationFrames; // 所有帧数据
private int _currentFrameIndex = 0; // 当前帧索引
@ -102,12 +102,12 @@ namespace NavisworksTransport.Core.Animation
// === 碰撞测试优化 ===
private string _currentAnimationHash = ""; // 当前动画配置的哈希值
// === 动画播放机制 ===
private double _frameInterval; // 帧间隔(毫秒)
private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间
private DispatcherTimer _animationTimer; // 备用DispatcherTimer定时器
// === 动画参数 ===
private double _animationDuration = 10.0; // 动画总时长(秒)
private DateTime _animationStartTime;
@ -119,26 +119,26 @@ namespace NavisworksTransport.Core.Animation
private string _pathName = "未知路径"; // 路径名称
private string _currentRouteId = null; // 当前路由ID
private string _animatedObjectName = null; // 动画对象名称
// === 双向播放和步进控制 ===
private int _playbackDirection = 1; // 播放方向1=正向,-1=反向
private double _playbackSpeed = 1.0; // 播放速度倍率
// === 性能监控 ===
private int _fpsFrameCount = 0;
private DateTime _fpsCounterStart = DateTime.Now;
private double _actualFPS = 0;
private Transform3D _originalTransform;
private Point3D _originalCenter; // 存储部件的原始中心位置
private Point3D _currentPosition; // 存储部件的当前位置
private AnimationState _currentState = AnimationState.Idle;
private double _pausedProgress = 0.0; // 暂停时的进度0-1之间
// TimeLiner 集成
private TimeLinerIntegrationManager _timeLinerManager;
private string _currentTaskId;
// --- 新增事件 ---
/// <summary>
/// 当动画状态发生改变时触发
@ -160,7 +160,7 @@ namespace NavisworksTransport.Core.Animation
_pathPoints = new List<Point3D>();
_animationFrames = new List<AnimationFrame>();
_allCollisionResults = new List<CollisionResult>();
// 初始化动画模式
_frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔
_lastFrameTime = DateTime.MinValue;
@ -172,7 +172,7 @@ namespace NavisworksTransport.Core.Animation
Interval = TimeSpan.FromMilliseconds(_frameInterval)
};
_animationTimer.Tick += OnTimerTick;
// 初始化 TimeLiner 集成
try
{
@ -184,11 +184,11 @@ namespace NavisworksTransport.Core.Animation
LogManager.Warning($"TimeLiner 集成初始化失败,将使用基础动画功能: {ex.Message}");
_timeLinerManager = null;
}
// 订阅文档状态事件
DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated;
}
/// <summary>
/// 获取单例实例
/// </summary>
@ -213,7 +213,7 @@ namespace NavisworksTransport.Core.Animation
{
if (animatedObject == null)
throw new ArgumentNullException(nameof(animatedObject));
if (pathPoints == null || pathPoints.Count < 2)
throw new ArgumentException("路径点数量必须至少为2个", nameof(pathPoints));
@ -225,27 +225,27 @@ namespace NavisworksTransport.Core.Animation
var point = pathPoints[i];
bool isValid = !double.IsNaN(point.X) && !double.IsNaN(point.Y) && !double.IsNaN(point.Z) &&
!double.IsInfinity(point.X) && !double.IsInfinity(point.Y) && !double.IsInfinity(point.Z);
LogManager.Info($"路径点[{i}]坐标验证: ({point.X:F6},{point.Y:F6},{point.Z:F6}) - {(isValid ? "" : "")}");
if (!isValid)
{
hasInvalidCoordinates = true;
LogManager.Error($"检测到无效坐标: 路径点[{i}] = ({point.X},{point.Y},{point.Z})");
}
// 检查是否为零坐标(可能表示数据丢失)
if (isValid && point.X == 0.0 && point.Y == 0.0 && point.Z == 0.0)
{
LogManager.Warning($"路径点[{i}]坐标为零,可能存在数据丢失问题");
}
}
if (hasInvalidCoordinates)
{
throw new ArgumentException("路径点包含无效坐标NaN或无穷大无法创建动画");
}
LogManager.Info("=== 坐标验证完成 ===");
// 计算并保存当前动画配置的哈希值
@ -255,30 +255,30 @@ namespace NavisworksTransport.Core.Animation
_animatedObject = animatedObject;
_pathPoints = new List<Point3D>(pathPoints);
_animationDuration = durationSeconds;
// 保存原始变换以便重置
_originalTransform = GetCurrentTransform(_animatedObject);
// 保存车辆的原始中心位置
var originalBoundingBox = animatedObject.BoundingBox();
_originalCenter = originalBoundingBox.Center;
// 预计算动画帧和碰撞
PrecomputeAnimationFrames();
// 关键修复:将车辆立即移动到路径起点
// 这样动画就从路径起点开始,而不是从车辆当前位置开始
MoveVehicleToPathStart();
// 记录文档单位信息
var documentUnits = GetDocumentUnitsInfo();
// 添加详细的调试信息
LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 路径点数={_pathPoints.Count}, 时长={_animationDuration}秒");
LogManager.Info($"文档单位: {documentUnits}");
LogManager.Info($"路径起点: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})");
LogManager.Info($"路径终点: ({_pathPoints[_pathPoints.Count-1].X:F2},{_pathPoints[_pathPoints.Count-1].Y:F2},{_pathPoints[_pathPoints.Count-1].Z:F2})");
LogManager.Info($"路径终点: ({_pathPoints[_pathPoints.Count - 1].X:F2},{_pathPoints[_pathPoints.Count - 1].Y:F2},{_pathPoints[_pathPoints.Count - 1].Z:F2})");
var totalDistInModelUnits = CalculateTotalPathDistance();
var totalDistInMeters = UnitsConverter.ConvertToMeters(totalDistInModelUnits);
LogManager.Info($"路径总长度: {totalDistInMeters:F2}米");
@ -300,34 +300,34 @@ namespace NavisworksTransport.Core.Animation
try
{
if (_pathPoints.Count == 0) return;
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 获取物体的包围盒
var boundingBox = _animatedObject.BoundingBox();
// 计算物体底面中心点(而不是整体中心点)
var objectBottomCenter = new Point3D(
_originalCenter.X,
_originalCenter.Y,
_originalCenter.Y,
boundingBox.Min.Z // 使用包围盒的最小Z值作为底面
);
// 计算从物体底面中心到路径起点的偏移
var startOffset = new Vector3D(
_pathPoints[0].X - objectBottomCenter.X,
_pathPoints[0].Y - objectBottomCenter.Y,
_pathPoints[0].Z - objectBottomCenter.Z
);
// 创建变换并应用
var startTransform = Transform3D.CreateTranslation(startOffset);
doc.Models.OverridePermanentTransform(modelItems, startTransform, false);
// 更新当前位置为路径起点
_currentPosition = _pathPoints[0];
LogManager.Info($"物体已移动到路径起点,底面对齐地面");
LogManager.Info($"物体包围盒: Min=({boundingBox.Min.X:F2},{boundingBox.Min.Y:F2},{boundingBox.Min.Z:F2}), Max=({boundingBox.Max.X:F2},{boundingBox.Max.Y:F2},{boundingBox.Max.Z:F2})");
LogManager.Info($"物体原始中心: ({_originalCenter.X:F2},{_originalCenter.Y:F2},{_originalCenter.Z:F2})");
@ -350,7 +350,7 @@ namespace NavisworksTransport.Core.Animation
{
// 基于动画时长和帧率计算总帧数
int totalFrames = (int)(_animationDuration * _animationFrameRate);
// 反向计算检测精度(确保每帧都检测)
var totalDistanceInModelUnits = CalculateTotalPathDistance();
_collisionDetectionAccuracy = totalDistanceInModelUnits / totalFrames;
@ -364,12 +364,12 @@ namespace NavisworksTransport.Core.Animation
LogManager.Info($"总帧数: {totalFrames}");
LogManager.Info($"路径总长: {totalDistanceInMeters:F2}米");
LogManager.Info($"检测精度: {detectionAccuracyInMeters:F3}米/帧");
// 初始化帧列表
_animationFrames = new List<AnimationFrame>();
_allCollisionResults = new List<CollisionResult>();
_currentFrameIndex = 0;
// 获取动画对象的包围盒信息
var originalBoundingBox = _animatedObject.BoundingBox();
var boundingBoxSize = new Vector3D(
@ -430,7 +430,7 @@ namespace NavisworksTransport.Core.Animation
foreach (var collider in nearbyObjects)
{
var colliderBox = collider.BoundingBox();
// 使用包围盒检测
if (BoundingBoxesIntersectWithGap(virtualBoundingBox, colliderBox, _detectionGap))
{
@ -459,11 +459,11 @@ namespace NavisworksTransport.Core.Animation
_allCollisionResults.Add(collisionResult);
}
}
_animationFrames.Add(frame);
}
// 统计碰撞信息
var framesWithCollision = _animationFrames.Count(f => f.HasCollision);
var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count);
@ -527,7 +527,7 @@ namespace NavisworksTransport.Core.Animation
(box2.Min.Y + box2.Max.Y) / 2,
(box2.Min.Z + box2.Max.Z) / 2
);
return CalculateDistance(center1, center2);
}
@ -701,14 +701,14 @@ namespace NavisworksTransport.Core.Animation
LogManager.Debug($"动画已处于{_currentState}状态,跳过重复停止操作");
return;
}
// 停止播放机制
StopAnimationPlayback();
// 收集整个路径上的所有碰撞对象
var allCollisionResults = new List<CollisionResult>();
var uniqueCollidedItems = new HashSet<ModelItem>();
foreach (var frame in _animationFrames)
{
if (frame.HasCollision)
@ -724,7 +724,7 @@ namespace NavisworksTransport.Core.Animation
}
}
}
// 高亮显示所有碰撞过的对象
if (allCollisionResults.Count > 0)
{
@ -736,23 +736,23 @@ namespace NavisworksTransport.Core.Animation
ClashDetectiveIntegration.Instance.ClearHighlights();
LogManager.Info("动画停止,路径上无碰撞对象");
}
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
// 将物体移回起点位置
if (_pathPoints != null && _pathPoints.Count > 0 && _animatedObject != null)
{
UpdateObjectPosition(_pathPoints[0]);
LogManager.Info($"物体已移回起点位置: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})");
}
LogManager.Info("动画已停止");
// 动画停止时不创建碰撞测试汇总,由动画完成事件统一处理
LogManager.Info("动画停止,等待动画完成事件统一处理碰撞测试...");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
@ -773,14 +773,14 @@ namespace NavisworksTransport.Core.Animation
try
{
LogManager.Info("[PathAnimationManager] 程序关闭,执行动画清理");
// 检查动画状态,避免重复停止
if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle)
{
LogManager.Debug($"动画已处于{_currentState}状态,跳过关闭清理");
return;
}
// 停止播放机制(关闭时不记录详细日志)
try
{
@ -790,12 +790,12 @@ namespace NavisworksTransport.Core.Animation
{
LogManager.Debug($"关闭清理时停止播放机制异常: {stopEx.Message}");
}
// 直接设置状态为停止不执行任何UI操作
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
LogManager.Info("[PathAnimationManager] 动画关闭清理完成");
}
catch (Exception ex)
@ -898,13 +898,13 @@ namespace NavisworksTransport.Core.Animation
// 计算并保存当前进度
double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds;
_pausedProgress = Math.Min(elapsedSeconds / _animationDuration, 1.0);
// 停止播放机制
StopAnimationPlayback();
SetState(AnimationState.Paused);
LogManager.Info($"动画已暂停,当前进度: {_pausedProgress:F3}");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
@ -930,14 +930,14 @@ namespace NavisworksTransport.Core.Animation
// 使用保存的暂停进度重新设置动画开始时间
// 设置开始时间,使得当前时间对应保存的暂停进度
_animationStartTime = DateTime.Now.AddSeconds(-(_pausedProgress * _animationDuration));
// 重新启动播放机制
_lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行
StartAnimationPlayback();
SetState(AnimationState.Playing);
LogManager.Info($"动画已恢复,从进度 {_pausedProgress:F3} 继续播放");
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
@ -975,7 +975,7 @@ namespace NavisworksTransport.Core.Animation
{
// 先安全获取对象名称,避免访问已释放对象的属性
string objectName = GetSafeObjectName(_animatedObject);
var modelItems = new ModelItemCollection { _animatedObject };
activeDoc.Models.OverridePermanentTransform(modelItems, _originalTransform, false);
LogManager.Info($"部件 {objectName} 已重置到原始位置");
@ -1010,7 +1010,7 @@ namespace NavisworksTransport.Core.Animation
}
}
/// <summary>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// </summary>
@ -1024,7 +1024,7 @@ namespace NavisworksTransport.Core.Animation
{
return "NULL";
}
// 尝试访问DisplayName如果对象已被释放会抛出异常
return item.DisplayName ?? "未命名对象";
}
@ -1053,7 +1053,7 @@ namespace NavisworksTransport.Core.Animation
// 确保进度在0-1范围内
progress = Math.Max(0.0, Math.Min(1.0, progress));
// 如果进度达到100%,直接返回终点
if (progress >= 1.0)
{
@ -1062,14 +1062,14 @@ namespace NavisworksTransport.Core.Animation
// 计算总路径长度
var totalDistance = CalculateTotalPathDistance();
// 检查总距离是否有效
if (totalDistance <= 0.0 || double.IsNaN(totalDistance) || double.IsInfinity(totalDistance))
{
LogManager.Error($"路径总长度无效: {totalDistance},返回起点坐标");
return _pathPoints[0];
}
var targetDistance = totalDistance * progress;
// 找到当前应该在哪两个点之间
@ -1077,34 +1077,34 @@ namespace NavisworksTransport.Core.Animation
for (int i = 0; i < _pathPoints.Count - 1; i++)
{
var segmentDistance = CalculateDistance(_pathPoints[i], _pathPoints[i + 1]);
// 检查段距离是否有效
if (double.IsNaN(segmentDistance) || double.IsInfinity(segmentDistance))
{
LogManager.Error($"路径段[{i}-{i+1}]距离无效: {segmentDistance},跳过此段");
LogManager.Error($"路径段[{i}-{i + 1}]距离无效: {segmentDistance},跳过此段");
continue;
}
if (accumulatedDistance + segmentDistance > targetDistance || i == _pathPoints.Count - 2)
{
// 在这个线段内,或者是最后一个线段
var segmentProgress = segmentDistance > 0 ? (targetDistance - accumulatedDistance) / segmentDistance : 0.0;
// 确保段内进度在0-1范围内
segmentProgress = Math.Max(0.0, Math.Min(1.0, segmentProgress));
var interpolatedPoint = InterpolatePoints(_pathPoints[i], _pathPoints[i + 1], segmentProgress);
// 验证插值结果
if (double.IsNaN(interpolatedPoint.X) || double.IsNaN(interpolatedPoint.Y) || double.IsNaN(interpolatedPoint.Z))
{
LogManager.Error($"插值计算结果无效: ({interpolatedPoint.X},{interpolatedPoint.Y},{interpolatedPoint.Z}),返回起点");
return _pathPoints[0];
}
return interpolatedPoint;
}
accumulatedDistance += segmentDistance;
}
@ -1145,7 +1145,7 @@ namespace NavisworksTransport.Core.Animation
try
{
if (item == null) return new Point3D(0, 0, 0);
var bounds = item.BoundingBox();
if (bounds != null)
{
@ -1184,20 +1184,20 @@ namespace NavisworksTransport.Core.Animation
{
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 正确的增量变换:计算从当前位置到新位置的偏移
var incrementalOffset = new Vector3D(
newPosition.X - _currentPosition.X,
newPosition.Y - _currentPosition.Y,
newPosition.Z - _currentPosition.Z
);
// 创建增量变换
var incrementalTransform = Transform3D.CreateTranslation(incrementalOffset);
// 应用增量变换(不重置之前的变换)
doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false);
// 更新当前位置
_currentPosition = newPosition;
}
@ -1234,7 +1234,7 @@ namespace NavisworksTransport.Core.Animation
// 获取包围盒中心作为参考点
var boundingBox = item.BoundingBox();
var center = boundingBox.Center;
// 创建基于中心点的单位变换
return Transform3D.CreateTranslation(new Vector3D(center.X, center.Y, center.Z));
}
@ -1248,7 +1248,7 @@ namespace NavisworksTransport.Core.Animation
{
// 订阅碰撞检测事件
ClashDetectiveIntegration.Instance.CollisionDetected += OnClashDetectiveCollisionDetected;
LogManager.Info("动态碰撞检测设置完成(实时模式)");
}
catch (Exception ex)
@ -1266,7 +1266,7 @@ namespace NavisworksTransport.Core.Animation
{
// 将碰撞结果转发到动画管理器的事件
OnCollisionDetected(e);
LogManager.Debug($"Clash Detective 检测到 {e.CollisionCount} 个碰撞");
}
catch (Exception ex)
@ -1373,7 +1373,7 @@ namespace NavisworksTransport.Core.Animation
{
_playbackSpeed = Math.Abs(speed);
_playbackDirection = speed >= 0 ? 1 : -1;
// 更新帧间隔
_frameInterval = 1000.0 / (_animationFrameRate * _playbackSpeed);
LogManager.Info($"播放速度设置为: {speed}x方向: {(speed >= 0 ? "" : "")}");
@ -1395,7 +1395,7 @@ namespace NavisworksTransport.Core.Animation
// 限制帧索引范围
frameIndex = Math.Max(0, Math.Min(frameIndex, _animationFrames.Count - 1));
if (frameIndex == _currentFrameIndex)
{
LogManager.Debug($"已处于目标帧 {frameIndex},无需跳转");
@ -1403,17 +1403,17 @@ namespace NavisworksTransport.Core.Animation
}
_currentFrameIndex = frameIndex;
// 更新对象位置
var frameData = _animationFrames[_currentFrameIndex];
UpdateObjectPosition(frameData.Position);
// 更新碰撞高亮
UpdateCollisionHighlightFromFrame();
// 计算并触发进度事件
double progress = TotalFrames > 1 ? (double)_currentFrameIndex / (TotalFrames - 1) * 100 : 0;
ProgressChanged?.Invoke(this, progress);
ProgressChanged?.Invoke(this, progress);
}
catch (Exception ex)
{
@ -1456,10 +1456,10 @@ namespace NavisworksTransport.Core.Animation
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex + frames;
targetFrame = Math.Min(targetFrame, TotalFrames - 1);
SeekToFrame(targetFrame);
}
catch (Exception ex)
@ -1485,10 +1485,10 @@ namespace NavisworksTransport.Core.Animation
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex - frames;
targetFrame = Math.Max(targetFrame, 0);
SeekToFrame(targetFrame);
}
catch (Exception ex)
@ -1571,19 +1571,19 @@ namespace NavisworksTransport.Core.Animation
try
{
LogManager.Info("[PathAnimationManager] 清理对象引用");
// 停止动画
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
ShutdownAnimation();
ShutdownAnimation();
}
// 清空对象引用
_animatedObject = null;
_pathPoints?.Clear();
_currentPosition = new Point3D(0, 0, 0);
_originalCenter = new Point3D(0, 0, 0);
LogManager.Info("[PathAnimationManager] 对象引用已清理");
}
catch (Exception ex)
@ -1591,7 +1591,7 @@ namespace NavisworksTransport.Core.Animation
LogManager.Error($"[PathAnimationManager] 清理对象引用失败: {ex.Message}");
}
}
/// <summary>
/// 验证动画对象是否有效
/// </summary>
@ -1599,11 +1599,11 @@ namespace NavisworksTransport.Core.Animation
{
if (_animatedObject == null)
return false;
// 使用DocumentStateManager验证
return DocumentStateManager.Instance.IsModelItemValid(_animatedObject);
}
/// <summary>
/// 文档失效事件处理
/// </summary>
@ -1619,7 +1619,7 @@ namespace NavisworksTransport.Core.Animation
LogManager.Error($"[PathAnimationManager] 处理文档失效事件失败: {ex.Message}");
}
}
/// <summary>
/// 资源清理
/// </summary>
@ -1628,7 +1628,7 @@ namespace NavisworksTransport.Core.Animation
try
{
LogManager.Info("开始清理PathAnimationManager资源");
// 1. 优先停止动画(最小化的操作,不触发复杂的重置逻辑)
// 只有在动画运行时才停止,避免重复调用
try
@ -1642,11 +1642,11 @@ namespace NavisworksTransport.Core.Animation
{
LogManager.Warning($"停止动画时出现警告: {stopEx.Message},继续清理其他资源");
}
// 2. 在Dispose中不进行重置操作避免访问已释放的对象
// 程序关闭时,对象位置重置是不必要的
LogManager.Info("跳过动画重置操作(程序关闭时不需要恢复对象位置)");
// 3. 清理 TimeLiner 资源
if (_timeLinerManager != null)
{
@ -1661,7 +1661,7 @@ namespace NavisworksTransport.Core.Animation
LogManager.Warning($"清理 TimeLiner 资源时出现警告: {ex.Message}");
}
}
// 4. 清理DispatcherTimer资源
if (_animationTimer != null)
{
@ -1680,7 +1680,7 @@ namespace NavisworksTransport.Core.Animation
// 5. 取消订阅文档事件
DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated;
LogManager.Info("PathAnimationManager资源清理完成");
}
catch (Exception ex)
@ -1698,7 +1698,7 @@ namespace NavisworksTransport.Core.Animation
try
{
var doc = NavisApplication.ActiveDocument;
// 尝试访问TimeLiner
var timeliner = doc.Timeliner;
if (timeliner != null)
@ -1743,7 +1743,7 @@ namespace NavisworksTransport.Core.Animation
// 使用现有的SetupAnimation方法
SetupAnimation(component, pathPoints, durationSeconds);
LogManager.Info($"简化动画设置成功:部件={component.DisplayName}, 路径点数={pathPoints.Count}, 时长={durationSeconds}秒");
return true;
}
@ -1764,7 +1764,7 @@ namespace NavisworksTransport.Core.Animation
{
var doc = NavisApplication.ActiveDocument;
var selectedItems = doc.CurrentSelection.SelectedItems;
if (selectedItems.Count == 1)
{
var item = selectedItems.First;
@ -2329,4 +2329,4 @@ namespace NavisworksTransport.Core.Animation
#endregion
}
}
}

View File

@ -20,10 +20,10 @@ namespace NavisworksTransport
// 通道对象缓存,用于优化碰撞检测性能
private static HashSet<ModelItem> _channelObjectsCache = null;
private static readonly object _cacheLock = new object();
// 几何对象列表缓存,用于避免重复获取对象列表
private static List<ModelItem> _allGeometryItemsCache = null;
// 碰撞检测计数器
private int _animationCollisionCount = 0; // 动画过程中简单包围盒检测的碰撞数量
private int _clashDetectiveCollisionCount = 0; // Clash Detective最终检测的碰撞数量
@ -31,17 +31,17 @@ namespace NavisworksTransport
/// <summary>
/// 动画过程中检测到的碰撞数量(仅供参考统计)
/// </summary>
public int AnimationCollisionCount
{
get { return _animationCollisionCount; }
public int AnimationCollisionCount
{
get { return _animationCollisionCount; }
}
/// <summary>
/// Clash Detective检测到的权威碰撞数量
/// </summary>
public int ClashDetectiveCollisionCount
{
get { return _clashDetectiveCollisionCount; }
public int ClashDetectiveCollisionCount
{
get { return _clashDetectiveCollisionCount; }
}
/// <summary>
@ -154,24 +154,24 @@ namespace NavisworksTransport
// 🔧 智能容器映射:将几何体子对象映射到有名称的容器对象
var mappedAnimatedObject = GetCollisionObjectWithValidIdentity(animatedObject);
var mappedCollisionObject = GetCollisionObjectWithValidIdentity(collisionObject);
// 检查是否进行了容器映射
bool hasMapping1 = !mappedAnimatedObject.Equals(animatedObject);
bool hasMapping2 = !mappedCollisionObject.Equals(collisionObject);
// 使用包围盒碰撞检测算法
var animatedBoundingBox = animatedObject.BoundingBox();
var collisionBoundingBox = collisionObject.BoundingBox();
if (BoundingBoxGeometryUtils.BoundingBoxesIntersect(animatedBoundingBox, collisionBoundingBox))
{
// 🔍 计算并验证位置信息
var finalCollisionPosition = collisionObjectPosition ?? GetObjectPosition(collisionObject);
// 创建碰撞结果
var collisionDistance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, collisionBoundingBox);
var collisionCenter = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, collisionBoundingBox);
var collision = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
@ -191,9 +191,9 @@ namespace NavisworksTransport
};
// 去重处理:避免重复缓存相同的碰撞对(基于容器对象)
var existing = _cachedResults.FirstOrDefault(r =>
var existing = _cachedResults.FirstOrDefault(r =>
r.Item1.Equals(mappedAnimatedObject) && r.Item2.Equals(mappedCollisionObject));
if (existing == null)
{
_cachedResults.Add(collision);
@ -215,7 +215,7 @@ namespace NavisworksTransport
try
{
if (item == null) return new Point3D(0, 0, 0);
var bounds = item.BoundingBox();
if (bounds != null)
{
@ -297,29 +297,29 @@ namespace NavisworksTransport
}
var doc = Application.ActiveDocument;
// 使用分组方案:创建一个主测试和一个包含所有碰撞结果的组
LogManager.Info("=== 开始分组方案:创建主测试和碰撞分组 ===");
// 过滤有效的碰撞
var validCollisions = collisionResults.Where(collision =>
collision.HasPositionInfo &&
IsModelItemValid(collision.Item1) &&
IsModelItemValid(collision.Item2)
).ToList();
if (validCollisions.Count == 0)
{
LogManager.Warning("没有有效的碰撞可以创建测试");
return;
}
LogManager.Info($"[分组测试] 有效碰撞数量: {validCollisions.Count}");
// 创建主测试名称
var mainTestName = $"物流碰撞检测_{pathName}_{DateTime.Now:yyyyMMdd_HHmmss}";
LogManager.Info($"[分组测试] 创建主测试: {mainTestName}");
try
{
// 使用事务确保操作的原子性
@ -334,11 +334,11 @@ namespace NavisworksTransport
Guid = Guid.Empty,
MergeComposites = true
};
// 添加主测试到文档
_documentClash.TestsData.TestsAddCopy(mainTest);
LogManager.Info($"[分组测试] 主测试已添加到文档");
// 获取添加后的测试对象引用
var addedMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == mainTestName) as ClashTest;
if (addedMainTest == null)
@ -346,127 +346,161 @@ namespace NavisworksTransport
LogManager.Error("无法获取添加后的主测试对象");
return;
}
// 第二步:创建分组并添加碰撞结果
// 第二步:创建分组并添加碰撞结果(应用智能去重)
// 1. 分组:按碰撞对象对分组
var groupedCollisions = validCollisions
.GroupBy(c => new { Item1 = c.Item1, Item2 = c.Item2 })
.ToList();
LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对");
var collisionGroup = new ClashResultGroup
{
DisplayName = $"物流碰撞检测组 ({validCollisions.Count} 个检测点)"
DisplayName = $"物流碰撞检测组 ({groupedCollisions.Count} 个唯一碰撞对)"
};
LogManager.Info($"[分组测试] 创建碰撞分组: {collisionGroup.DisplayName}");
int resultCount = 0;
foreach (var collision in validCollisions)
{
resultCount++;
int confirmedCount = 0;
int skippedCount = 0;
try
{
// 临时移动动画对象到碰撞位置以执行测试
var testAnimatedObject = collision.Item1;
var modelItems = new ModelItemCollection { testAnimatedObject };
var targetPosition = collision.Item1Position;
// 计算移动偏移
var currentBounds = testAnimatedObject.BoundingBox();
var currentPos = new Point3D(
(currentBounds.Min.X + currentBounds.Max.X) / 2,
(currentBounds.Min.Y + currentBounds.Max.Y) / 2,
(currentBounds.Min.Z + currentBounds.Max.Z) / 2
);
var offset = new Vector3D(
targetPosition.X - currentPos.X,
targetPosition.Y - currentPos.Y,
targetPosition.Z - currentPos.Z
);
var transform = Transform3D.CreateTranslation(offset);
doc.Models.OverridePermanentTransform(modelItems, transform, false);
// 创建临时测试以获取真实碰撞结果
var tempTestName = $"临时测试_{resultCount}_{DateTime.Now:HHmmss_fff}";
var tempTest = new ClashTest
// 2. 遍历每一组
foreach (var group in groupedCollisions)
{
// 3. 排序:按距离/重叠深度排序,优先检测最严重的碰撞
// 注意Distance通常越小或负值越大表示碰撞越深具体取决于计算方式
// 这里假设Distance越小越严重
var sortedCandidates = group.OrderBy(c => c.Distance).ToList();
bool pairConfirmed = false;
// 4. 验证即止:逐个检测候选帧
for (int i = 0; i < sortedCandidates.Count; i++)
{
var candidate = sortedCandidates[i];
try
{
DisplayName = tempTestName,
TestType = ClashTestType.HardConservative,
Tolerance = detectionGap,
Guid = Guid.Empty,
MergeComposites = true
};
// 设置选择集
var selectionA = new ModelItemCollection { collision.Item1 };
var selectionB = new ModelItemCollection { collision.Item2 };
tempTest.SelectionA.Selection.CopyFrom(selectionA);
tempTest.SelectionB.Selection.CopyFrom(selectionB);
// 添加并运行临时测试
_documentClash.TestsData.TestsAddCopy(tempTest);
var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
if (addedTempTest != null)
{
// 使用正确的PrimitiveTypes设置模式
try
// 临时移动动画对象到碰撞位置以执行测试
var testAnimatedObject = candidate.Item1;
var modelItems = new ModelItemCollection { testAnimatedObject };
var targetPosition = candidate.Item1Position;
// 计算移动偏移
var currentBounds = testAnimatedObject.BoundingBox();
var currentPos = new Point3D(
(currentBounds.Min.X + currentBounds.Max.X) / 2,
(currentBounds.Min.Y + currentBounds.Max.Y) / 2,
(currentBounds.Min.Z + currentBounds.Max.Z) / 2
);
var offset = new Vector3D(
targetPosition.X - currentPos.X,
targetPosition.Y - currentPos.Y,
targetPosition.Z - currentPos.Z
);
var transform = Transform3D.CreateTranslation(offset);
doc.Models.OverridePermanentTransform(modelItems, transform, false);
// 创建临时测试以获取真实碰撞结果
var tempTestName = $"临时验证_{confirmedCount + 1}_{i}_{DateTime.Now:HHmmss_fff}";
var tempTest = new ClashTest
{
var copyTest = addedTempTest.CreateCopy() as ClashTest;
copyTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
copyTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
_documentClash.TestsData.TestsEditTestFromCopy(addedTempTest, copyTest);
}
catch (Exception geomEx)
DisplayName = tempTestName,
TestType = ClashTestType.HardConservative,
Tolerance = detectionGap,
Guid = Guid.Empty,
MergeComposites = true
};
// 设置选择集
var selectionA = new ModelItemCollection { candidate.Item1 };
var selectionB = new ModelItemCollection { candidate.Item2 };
tempTest.SelectionA.Selection.CopyFrom(selectionA);
tempTest.SelectionB.Selection.CopyFrom(selectionB);
// 添加并运行临时测试
_documentClash.TestsData.TestsAddCopy(tempTest);
var addedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
if (addedTempTest != null)
{
LogManager.Warning($"设置几何类型失败: {geomEx.Message}");
}
// 运行测试
_documentClash.TestsData.TestsRunTest(addedTempTest);
// 获取刷新后的测试结果
var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0)
{
// 将碰撞结果复制到分组中,并设置唯一名称
int subResultIndex = 1;
foreach (var child in refreshedTempTest.Children)
// 使用正确的PrimitiveTypes设置模式
try
{
if (child is ClashResult result)
var copyTest = addedTempTest.CreateCopy() as ClashTest;
copyTest.SelectionA.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
copyTest.SelectionB.PrimitiveTypes = PrimitiveTypes.Triangles | PrimitiveTypes.Lines | PrimitiveTypes.Points;
_documentClash.TestsData.TestsEditTestFromCopy(addedTempTest, copyTest);
}
catch (Exception geomEx)
{
LogManager.Warning($"设置几何类型失败: {geomEx.Message}");
}
// 运行测试
_documentClash.TestsData.TestsRunTest(addedTempTest);
// 获取刷新后的测试结果
var refreshedTempTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName == tempTestName) as ClashTest;
if (refreshedTempTest != null && refreshedTempTest.Children.Count > 0)
{
// !!!发现真实碰撞!!!
pairConfirmed = true;
confirmedCount++;
skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量
// 将碰撞结果复制到分组中
int subResultIndex = 1;
foreach (var child in refreshedTempTest.Children)
{
var copiedResult = result.CreateCopy() as ClashResult;
copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID
// 🔧 设置唯一且有意义的碰撞名称
var object1Name = collision.Item1?.DisplayName ?? "未知对象";
var object2Name = collision.Item2?.DisplayName ?? "未知对象";
var timeStamp = DateTime.Now.ToString("HHmmss");
copiedResult.DisplayName = $"物流碰撞#{resultCount:00}-{subResultIndex:00}_{timeStamp}: {object1Name} ↔ {object2Name}";
collisionGroup.Children.Add(copiedResult);
subResultIndex++;
if (child is ClashResult result)
{
var copiedResult = result.CreateCopy() as ClashResult;
copiedResult.Guid = Guid.NewGuid(); // 生成新的GUID
// 设置唯一且有意义的碰撞名称
var object1Name = candidate.Item1?.DisplayName ?? "未知对象";
var object2Name = candidate.Item2?.DisplayName ?? "未知对象";
var timeStamp = DateTime.Now.ToString("HHmmss");
copiedResult.DisplayName = $"物流碰撞#{confirmedCount:00}-{subResultIndex:00}_{timeStamp}: {object1Name} ↔ {object2Name}";
collisionGroup.Children.Add(copiedResult);
subResultIndex++;
}
}
}
}
// 删除临时测试
try
{
_documentClash.TestsData.TestsRemove(refreshedTempTest ?? addedTempTest);
}
catch (Exception cleanEx)
{
LogManager.Warning($"清理临时测试失败: {cleanEx.Message}");
// 删除临时测试
try
{
_documentClash.TestsData.TestsRemove(refreshedTempTest ?? addedTempTest);
}
catch (Exception cleanEx)
{
LogManager.Warning($"清理临时测试失败: {cleanEx.Message}");
}
// 如果已确认碰撞,跳出当前候选循环,不再检测该组剩余帧
if (pairConfirmed)
{
break;
}
}
}
}
catch (Exception resultEx)
{
LogManager.Error($"[分组测试-{resultCount:00}] 处理碰撞结果失败: {resultEx.Message}");
catch (Exception itemEx)
{
LogManager.Error($"候选检测失败: {itemEx.Message}");
}
}
}
LogManager.Info($"[分组测试] 智能去重完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点");
// 第三步:将分组添加到主测试
if (collisionGroup.Children.Count > 0)
{
@ -474,9 +508,9 @@ namespace NavisworksTransport
}
else
{
LogManager.Warning("[分组测试] 分组为空,未添加到主测试");
LogManager.Warning("[分组测试] 分组为空(未检测到真实几何碰撞),未添加到主测试");
}
// 提交事务
transaction.Commit();
}
@ -492,9 +526,9 @@ namespace NavisworksTransport
try
{
LogManager.Info("=== 开始恢复物体到动画终点位置 ===");
var modelItems = new ModelItemCollection { animatedObject };
// 计算当前位置到动画终点的偏移
var currentBounds = animatedObject.BoundingBox();
var currentPos = new Point3D(
@ -502,16 +536,16 @@ namespace NavisworksTransport
(currentBounds.Min.Y + currentBounds.Max.Y) / 2,
(currentBounds.Min.Z + currentBounds.Max.Z) / 2
);
var restoreOffset = new Vector3D(
animationEndPosition.X - currentPos.X,
animationEndPosition.Y - currentPos.Y,
animationEndPosition.Z - currentPos.Z
);
var restoreTransform = Transform3D.CreateTranslation(restoreOffset);
doc.Models.OverridePermanentTransform(modelItems, restoreTransform, false);
LogManager.Info($"已将 {animatedObject.DisplayName} 恢复到动画终点位置: ({animationEndPosition.X:F2},{animationEndPosition.Y:F2},{animationEndPosition.Z:F2})");
LogManager.Info("=== 物体位置恢复完成 ===");
}
@ -520,23 +554,23 @@ namespace NavisworksTransport
LogManager.Error($"恢复物体到动画终点失败: {restoreEx.Message}");
}
}
LogManager.Info($"=== 分组方案完成:成功创建分组碰撞测试(容差: {detectionGap}米) ===");
// 检查是否成功创建了主测试
var finalMainTest = _documentClash.TestsData.Tests.FirstOrDefault(t => t.DisplayName.Contains("物流碰撞检测")) as ClashTest;
if (finalMainTest != null)
{
// 刷新Clash Detective窗口
RefreshClashDetectiveUI();
// 自动高亮所有碰撞结果(使用类别管理)
try
{
// 使用过滤后的预计算结果进行高亮
var validResults = collisionResults.Where(r =>
IsModelItemValid(r.Item1) && IsModelItemValid(r.Item2)).ToList();
if (validResults.Count > 0)
{
// 使用类别管理的高亮方式
@ -549,7 +583,7 @@ namespace NavisworksTransport
{
LogManager.Error($"动画结束后自动高亮失败: {highlightEx.Message}");
}
// 更新Clash Detective碰撞计数器 - 统计分组测试中的碰撞数量
var finalClashDetectiveCount = 0;
if (_documentClash != null)
@ -559,16 +593,16 @@ namespace NavisworksTransport
.Where(t => t.DisplayName.Contains("物流碰撞检测") ||
t.DisplayName.Contains("物流碰撞"))
.ToList();
finalClashDetectiveCount = logisticsTests.Sum(t => GetTotalClashResultCount(t));
LogManager.Info($"统计Clash Detective最终碰撞数量: {logisticsTests.Count}个测试,总共{finalClashDetectiveCount}个碰撞");
// 详细分析主测试结构
if (finalMainTest != null)
{
LogManager.Info($"[分组统计] 主测试: {finalMainTest.DisplayName}");
LogManager.Info($"[分组统计] 主测试直接子项数量: {finalMainTest.Children.Count}");
foreach (var child in finalMainTest.Children)
{
if (child is ClashResultGroup group)
@ -582,7 +616,7 @@ namespace NavisworksTransport
}
}
}
_clashDetectiveCollisionCount = finalClashDetectiveCount;
LogManager.Info($"=== 碰撞统计最终结果 ===");
LogManager.Info($"动画过程包围盒检测: {_animationCollisionCount}个碰撞 (参考值)");
@ -667,11 +701,11 @@ namespace NavisworksTransport
{
if (item == null)
return false;
// 尝试访问对象的属性来检查是否有效
var displayName = item.DisplayName;
var hasGeometry = item.HasGeometry;
// 额外检查:确保对象没有被释放
var boundingBox = item.BoundingBox();
return true;
@ -704,17 +738,17 @@ namespace NavisworksTransport
try
{
var doc = Application.ActiveDocument;
// 根据参数决定是否清除之前的高亮
if (clearPrevious)
{
doc.Models.ResetAllTemporaryMaterials();
}
if (results != null && results.Count > 0)
{
var collidingItems = new ModelItemCollection();
foreach (var result in results)
{
if (result.Item1 != null && !collidingItems.Contains(result.Item1))
@ -722,7 +756,7 @@ namespace NavisworksTransport
if (result.Item2 != null && !collidingItems.Contains(result.Item2))
collidingItems.Add(result.Item2);
}
// 高亮碰撞对象(使用指定颜色)
doc.Models.OverrideTemporaryColor(collidingItems, highlightColor);
}
@ -761,7 +795,7 @@ namespace NavisworksTransport
/// <param name="results">碰撞结果</param>
/// <param name="highlightColor">高亮颜色</param>
/// <param name="clearOtherCategories">是否清除其他类别的高亮</param>
public void ManageHighlightsByCategory(string category, List<CollisionResult> results,
public void ManageHighlightsByCategory(string category, List<CollisionResult> results,
Color highlightColor, bool clearOtherCategories = false)
{
try
@ -863,7 +897,7 @@ namespace NavisworksTransport
var category = kvp.Key;
var items = kvp.Value;
var color = GetCategoryColor(category);
info.AppendLine($"类别: {category}");
info.AppendLine($" 对象数量: {items.Count}");
info.AppendLine($" 颜色: {color}");
@ -891,38 +925,38 @@ namespace NavisworksTransport
lock (_cacheLock)
{
if (_channelObjectsCache != null) return; // 双重检查锁定
var cacheStopwatch = new System.Diagnostics.Stopwatch();
cacheStopwatch.Start();
_channelObjectsCache = new HashSet<ModelItem>();
try
{
var document = Application.ActiveDocument;
// 获取所有可通行的物流模型项
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems();
if (allChannelItems.Count == 0)
{
LogManager.Warning("[通道缓存] ⚠️ 未找到任何可通行的物流元素,请检查模型中的物流属性设置");
cacheStopwatch.Stop();
return;
}
// 智能收集通道相关节点(包括父节点、同级节点、子节点等)
foreach (var channelItem in allChannelItems)
{
try
{
{
var itemRelatedNodes = ModelItemAnalysisHelper.CollectRelatedNodes(channelItem);
// 将结果添加到缓存中
foreach (var node in itemRelatedNodes)
{
_channelObjectsCache.Add(node);
}
}
}
catch (Exception ex)
{
@ -931,7 +965,7 @@ namespace NavisworksTransport
_channelObjectsCache.Add(channelItem);
}
}
cacheStopwatch.Stop();
LogManager.Info($"通道对象缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 可通行物流根对象: {allChannelItems.Count} 个");
@ -957,7 +991,7 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 构建几何对象列表缓存,一次性获取所有几何对象
/// </summary>
@ -1020,7 +1054,7 @@ namespace NavisworksTransport
.ToList();
}
}
/// <summary>
/// 清除所有缓存,在模型变化时调用
/// </summary>
@ -1033,7 +1067,7 @@ namespace NavisworksTransport
LogManager.Debug("所有对象缓存已清除");
}
}
/// <summary>
/// 清理碰撞缓存
/// </summary>
@ -1042,23 +1076,23 @@ namespace NavisworksTransport
try
{
LogManager.Info("[ClashDetectiveIntegration] 清理碰撞缓存");
// 清空当前碰撞列表
_currentCollisions?.Clear();
// 清空缓存的结果
lock (_highlightLock)
{
_cachedResults?.Clear();
}
// 清除对象缓存
ClearAllCaches();
// 重置计数器
_animationCollisionCount = 0;
_clashDetectiveCollisionCount = 0;
LogManager.Info("[ClashDetectiveIntegration] 碰撞缓存已清理");
}
catch (Exception ex)
@ -1066,7 +1100,7 @@ namespace NavisworksTransport
LogManager.Error($"[ClashDetectiveIntegration] 清理碰撞缓存失败: {ex.Message}");
}
}
/// <summary>
/// 增强的碰撞对象信息,包含容器映射
/// </summary>
@ -1075,16 +1109,16 @@ namespace NavisworksTransport
private static ModelItem GetCollisionObjectWithValidIdentity(ModelItem originalItem)
{
if (originalItem == null) return null;
// 如果原对象已经有有效名称,直接返回
if (!string.IsNullOrEmpty(originalItem.DisplayName) && originalItem.DisplayName.Trim().Length > 0)
{
return originalItem;
}
// 否则查找有名称的父级容器
var containerObject = ModelItemAnalysisHelper.FindNamedParentContainer(originalItem);
return containerObject ?? originalItem;
}
@ -1096,15 +1130,15 @@ namespace NavisworksTransport
try
{
LogManager.Info("=== [容器映射测试] 开始测试容器映射功能 ===");
var testResults = new List<string>();
// 获取一些测试对象
var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item => item.HasGeometry).Take(10).ToList();
LogManager.Info($"[容器映射测试] 找到 {allItems.Count} 个几何体对象进行测试");
foreach (var item in allItems)
{
try
@ -1112,13 +1146,13 @@ namespace NavisworksTransport
var originalName = item.DisplayName ?? "无名称";
var mappedObject = GetCollisionObjectWithValidIdentity(item);
var mappedName = mappedObject?.DisplayName ?? "无名称";
bool hasMapping = !mappedObject.Equals(item);
testResults.Add($"原始: '{originalName}' -> 映射: '{mappedName}' (映射: {hasMapping})");
LogManager.Info($"[容器映射测试] {testResults.Last()}");
// 验证映射对象的有效性
if (hasMapping)
{
@ -1135,13 +1169,13 @@ namespace NavisworksTransport
LogManager.Warning($"[容器映射测试] 测试单个对象失败: {itemEx.Message}");
}
}
LogManager.Info("=== [容器映射测试] 测试结果汇总 ===");
int mappingCount = testResults.Count(r => r.Contains("映射: True"));
LogManager.Info($"测试对象总数: {testResults.Count}");
LogManager.Info($"发生映射的对象: {mappingCount}");
LogManager.Info($"映射成功率: {(mappingCount * 100.0 / Math.Max(testResults.Count, 1)):F1}%");
// 测试碰撞结果数据结构
var testCollision = new CollisionResult
{
@ -1154,14 +1188,14 @@ namespace NavisworksTransport
HasContainerMapping = true,
HasPositionInfo = true
};
LogManager.Info($"[容器映射测试] CollisionResult数据结构测试:");
LogManager.Info($" - GetValidItem1: {testCollision.GetValidItem1()?.DisplayName ?? "NULL"}");
LogManager.Info($" - GetValidItem2: {testCollision.GetValidItem2()?.DisplayName ?? "NULL"}");
LogManager.Info($" - GetOriginalItem1: {testCollision.GetOriginalItem1()?.DisplayName ?? "NULL"}");
LogManager.Info($" - GetOriginalItem2: {testCollision.GetOriginalItem2()?.DisplayName ?? "NULL"}");
LogManager.Info($" - HasContainerMapping: {testCollision.HasContainerMapping}");
LogManager.Info("=== [容器映射测试] 测试完成 ===");
}
catch (Exception ex)
@ -1181,7 +1215,7 @@ namespace NavisworksTransport
private int GetTotalClashResultCount(ClashTest test)
{
if (test == null) return 0;
int totalCount = 0;
foreach (var child in test.Children)
{
@ -1195,10 +1229,10 @@ namespace NavisworksTransport
totalCount += CountClashResultsInGroup(group);
}
}
return totalCount;
}
/// <summary>
/// 递归统计分组内的碰撞结果数量
/// </summary>
@ -1207,7 +1241,7 @@ namespace NavisworksTransport
private int CountClashResultsInGroup(ClashResultGroup group)
{
if (group == null) return 0;
int count = 0;
foreach (var child in group.Children)
{
@ -1221,7 +1255,7 @@ namespace NavisworksTransport
count += CountClashResultsInGroup(nestedGroup);
}
}
return count;
}
@ -1233,7 +1267,7 @@ namespace NavisworksTransport
try
{
LogManager.Info("开始Clash Detective集成资源清理...");
// 检查文档是否仍然有效,如果无效则跳过操作
var document = Application.ActiveDocument;
if (document == null || document.IsClear)
@ -1241,7 +1275,7 @@ namespace NavisworksTransport
LogManager.Info("文档已无效,跳过资源清理操作");
return;
}
// 清除临时高亮
try
{
@ -1256,20 +1290,20 @@ namespace NavisworksTransport
{
LogManager.Warning($"清除临时材质失败: {ex.Message}");
}
// 清空内存中的结果
_currentCollisions?.Clear();
_cachedResults?.Clear();
// 清理高亮管理器
lock (_highlightLock)
{
_activeHighlights?.Clear();
}
// 清理.NET API引用
_documentClash = null;
LogManager.Info("Clash Detective集成资源清理完成");
}
catch (Exception ex)
@ -1301,21 +1335,21 @@ namespace NavisworksTransport
public Point3D Center { get; set; }
public double Distance { get; set; }
public DateTime CreatedTime { get; set; }
// 位置信息用于恢复测试
public Point3D Item1Position { get; set; }
public Point3D Item2Position { get; set; }
public bool HasPositionInfo { get; set; }
// 📦 容器映射信息:记录原始几何体对象引用
public ModelItem OriginalItem1 { get; set; } // 原始几何体对象
public ModelItem OriginalItem2 { get; set; } // 原始几何体对象
public bool HasContainerMapping { get; set; } // 标识是否进行了容器映射
// 📍 获取最终用于ClashDetective的有效对象
public ModelItem GetValidItem1() => Item1; // 容器对象用于ClashDetective选择集
public ModelItem GetValidItem2() => Item2; // 容器对象用于ClashDetective选择集
// 📍 获取原始几何体对象(用于精确位置信息)
public ModelItem GetOriginalItem1() => OriginalItem1 ?? Item1;
public ModelItem GetOriginalItem2() => OriginalItem2 ?? Item2;

View File

@ -82,17 +82,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 碰撞检测参数字段(从配置初始化)
private double _detectionGap; // 检测间隙(米)
private int _animationFrameRate; // 动画帧率(FPS)
// 当前选中路径
private PathRouteViewModel _currentPathRoute;
// 移动物体相关字段
private ModelItem _selectedAnimatedObject;
private string _selectedAnimatedObjectName = "未选择移动物体";
private bool _hasSelectedAnimatedObject = false;
private bool _canGenerateAnimation = false;
#endregion
@ -114,13 +114,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public int SelectedFrameRate
{
get => _selectedFrameRate;
set
{
set
{
if (SetProperty(ref _selectedFrameRate, value))
{
// 同步更新AnimationFrameRate字段
_animationFrameRate = value;
// 帧率变化时,步长需要重新计算(步长=路径长度/(帧率*时长)
OnPropertyChanged(nameof(CollisionDetectionAccuracy));
}
@ -133,8 +133,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public double AnimationDuration
{
get => _animationDuration;
set
{
set
{
if (SetProperty(ref _animationDuration, value))
{
// 动画时长变化时,步长和速度需要重新计算
@ -283,8 +283,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public PathRouteViewModel CurrentPathRoute
{
get => _currentPathRoute;
set
{
set
{
if (SetProperty(ref _currentPathRoute, value))
{
// 路径改变时,步长、速度和路径长度需要重新计算
@ -302,14 +302,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ModelItem SelectedAnimatedObject
{
get => _selectedAnimatedObject;
set
{
set
{
SetProperty(ref _selectedAnimatedObject, value);
UpdateAnimatedObjectInfo();
UpdateCanGenerateAnimation();
// 移动物体改变时清除已生成的动画(如果有的话)
if (_pathAnimationManager != null &&
if (_pathAnimationManager != null &&
(_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Ready ||
_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Finished))
{
@ -323,7 +323,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"清除之前动画时出现警告: {ex.Message}");
}
}
// ✨ 新功能:动画对象选择时预计算排除列表(线程安全版本)
_ = PrecomputeCollisionExclusionsAsync(value);
}
@ -387,7 +387,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
get
{
if (_pathAnimationManager == null) return "00:00";
var currentSeconds = (_pathAnimationManager.CurrentFrame / (double)(_pathAnimationManager.TotalFrames - 1)) * _animationDuration;
return FormatTime(currentSeconds);
}
@ -413,13 +413,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// <summary>
/// 是否正在正向播放
/// </summary>
public bool IsPlayingForward => _pathAnimationManager?.PlaybackDirection == 1 &&
public bool IsPlayingForward => _pathAnimationManager?.PlaybackDirection == 1 &&
_pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;
/// <summary>
/// 是否正在反向播放
/// </summary>
public bool IsPlayingReverse => _pathAnimationManager?.PlaybackDirection == -1 &&
public bool IsPlayingReverse => _pathAnimationManager?.PlaybackDirection == -1 &&
_pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;
#endregion
@ -621,9 +621,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 清理之前的碰撞缓存
_pathAnimationManager?.ClearExclusionCache();
LogManager.Info("已清理UI层碰撞检测缓存");
// 首先重置进度(开始全新动画时)
_pathAnimationManager.StartAnimation();
LogManager.Info($"开始物体移动动画播放: {CurrentPathRoute.Name}, 帧率: {SelectedFrameRate}fps");
@ -659,7 +659,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
catch (Exception ex)
{
LogManager.Error($"暂停动画异常: {ex.Message}", ex);
// 错误时手动恢复UI状态
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
@ -760,7 +760,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// </summary>
private bool CanExecuteMediaCommands()
{
return _pathAnimationManager != null &&
return _pathAnimationManager != null &&
_pathAnimationManager.CurrentState != NavisworksTransport.Core.Animation.AnimationState.Idle &&
_pathAnimationManager.TotalFrames > 0;
}
@ -950,7 +950,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
// 路径改变时清除已生成的动画(如果有的话)
if (_pathAnimationManager != null &&
if (_pathAnimationManager != null &&
(_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Ready ||
_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Finished))
{
@ -964,15 +964,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"清除之前动画时出现警告: {ex.Message}");
}
}
// 路径改变时,步长、速度和路径长度需要重新计算
OnPropertyChanged(nameof(CollisionDetectionAccuracy));
OnPropertyChanged(nameof(MovementSpeed));
OnPropertyChanged(nameof(PathLength));
// 使用新的统一按钮状态更新方法
UpdateAnimationButtonStates();
// 更新碰撞状态
if (pathRoute == null)
{
@ -986,7 +986,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
UpdateMainStatus("碰撞检测就绪");
}
LogManager.Info($"AnimationControlViewModel当前路径更新: {pathRoute?.Name ?? ""}, 点数: {pathRoute?.Points.Count ?? 0}");
}
@ -1157,43 +1157,43 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
LogManager.Info("开始选择移动物体");
// 获取当前选中的模型项
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var selectedItems = doc.CurrentSelection.SelectedItems;
if (selectedItems.Count == 0)
{
UpdateMainStatus("请先在Navisworks中选择一个物体");
LogManager.Warning("用户未选择任何物体");
return;
}
if (selectedItems.Count > 1)
{
UpdateMainStatus("请只选择一个物体作为移动对象");
LogManager.Warning($"用户选择了{selectedItems.Count}个物体,需要只选择一个");
return;
}
var selectedItem = selectedItems.First;
// 检查选中项是否包含几何体(直接包含或子项包含)
bool hasAnyGeometry = HasGeometryRecursive(selectedItem);
if (!hasAnyGeometry)
{
UpdateMainStatus("选中的项目及其子项都不包含几何体,请选择其他物体");
LogManager.Warning("选中的项目及其子项都不包含几何体");
return;
}
LogManager.Info($"选中物体信息: DisplayName={selectedItem.DisplayName}, HasGeometry={selectedItem.HasGeometry}, ChildCount={selectedItem.Children.Count()}");
// 设置选中的移动物体
SelectedAnimatedObject = selectedItem;
UpdateMainStatus("移动物体选择成功");
LogManager.Info($"移动物体选择成功: {SelectedAnimatedObject.DisplayName}");
}
catch (Exception ex)
@ -1212,7 +1212,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
LogManager.Info("开始清除移动物体选择、当前动画并恢复原始位置");
// 首先停止当前动画(如果正在播放)
if (_pathAnimationManager != null && _pathAnimationManager.IsAnimating)
{
@ -1226,7 +1226,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"停止当前动画时出现警告: {stopEx.Message}");
}
}
// 清除PathAnimationManager中的动画数据将状态重置为Idle
if (_pathAnimationManager != null)
{
@ -1241,19 +1241,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"重置PathAnimationManager时出现警告: {resetEx.Message}");
}
}
// 如果有选中的物体,则恢复到原始位置
if (SelectedAnimatedObject != null)
{
try
{
LogManager.Info($"开始恢复物体 '{SelectedAnimatedObject.DisplayName}' 到原始位置");
// 使用ResetPermanentTransform清除所有增量变换恢复到设计文件中的原始位置
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var modelItems = new ModelItemCollection { SelectedAnimatedObject };
doc.Models.ResetPermanentTransform(modelItems);
LogManager.Info($"物体 '{SelectedAnimatedObject.DisplayName}' 已成功恢复到原始位置");
UpdateMainStatus("已清除移动物体选择并恢复到原始位置");
}
@ -1268,7 +1268,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info("没有选中的物体需要清除");
UpdateMainStatus("已清除移动物体选择");
}
// 清理选择状态
SelectedAnimatedObject = null;
@ -1289,10 +1289,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 更新按钮状态 - 清除动画后应该重新评估按钮状态
UpdateAnimationButtonStates();
// 更新生成动画的能力状态
UpdateCanGenerateAnimation();
LogManager.Info("移动物体选择和当前动画已完全清除,按钮状态已更新");
}
catch (Exception ex)
@ -1314,15 +1314,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
return;
}
LogManager.Info($"[缓存预计算] 开始为新选择的动画对象预计算: {animationObject.DisplayName}");
// 在UI线程中更新开始状态
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
UpdateMainStatus("正在分析动画对象...", -1, true);
}));
// 在UI线程中执行Navisworks API操作线程安全
bool success;
try
@ -1334,9 +1334,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Error($"[缓存预计算] 预计算失败: {ex.Message}", ex);
success = false;
}
// 异步更新UI状态
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (success)
{
@ -1354,7 +1354,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
catch (Exception ex)
{
LogManager.Error($"[缓存预计算] 异步预计算过程异常: {ex.Message}", ex);
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
UpdateMainStatus("动画对象分析异常");
}));
@ -1381,27 +1381,27 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 🔥 新增:在生成动画阶段构建碰撞检测缓存
UpdateMainStatus("正在构建碰撞检测缓存...", -1, true);
LogManager.Info("[动画生成] 开始构建碰撞检测缓存");
var cacheStartTime = DateTime.Now;
try
{
// 构建全局缓存(与具体移动物体无关)
ClashDetectiveIntegration.ClearAllCaches();
UpdateMainStatus("正在缓存几何对象列表...", -1, true);
ClashDetectiveIntegration.BuildAllGeometryItemsCache();
UpdateMainStatus("正在缓存通道对象...", -1, true);
var clashIntegration = ClashDetectiveIntegration.Instance;
clashIntegration.BuildChannelObjectsCache();
var cacheElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
LogManager.Info($"[动画生成] 碰撞检测缓存构建完成,耗时: {cacheElapsed:F1}ms");
// 构建特定于动画对象的缓存
UpdateMainStatus("正在分析动画对象...", -1, true);
var success = _pathAnimationManager.PrecomputeCollisionExclusions(SelectedAnimatedObject);
if (!success)
{
LogManager.Warning("[动画生成] 动画对象分析失败,将使用实时计算模式");
@ -1429,11 +1429,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 使用PathAnimationManager创建物体动画真正的物体移动动画会设置状态为Ready
_pathAnimationManager.CreateAnimation(SelectedAnimatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id);
// 更新状态 - 使用新的按钮状态更新方法
UpdateAnimationButtonStates();
UpdateMainStatus("动画生成成功");
var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
LogManager.Info($"[动画生成] 动画生成完成,总耗时: {totalElapsed:F1}ms");
LogManager.Info($"动画生成成功: 物体={SelectedAnimatedObject.DisplayName}, 路径={CurrentPathRoute.Name}");
@ -1442,7 +1442,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
UpdateMainStatus("动画生成失败");
LogManager.Error($"生成动画时发生错误: {ex.Message}");
LogManager.Error($"动画生成失败,详细错误信息:{ex}");
}
}
@ -1470,17 +1470,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private bool HasGeometryRecursive(ModelItem item)
{
if (item == null) return false;
// 如果当前项有几何体直接返回true
if (item.HasGeometry) return true;
// 递归检查子项
foreach (ModelItem child in item.Children)
{
if (HasGeometryRecursive(child))
return true;
}
return false;
}
@ -1491,9 +1491,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
var hasObject = SelectedAnimatedObject != null;
var hasPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
CanGenerateAnimation = hasObject && hasPath;
if (!hasObject && !hasPath)
{
UpdateMainStatus("请选择移动物体和动画路径");
@ -1510,7 +1510,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
UpdateMainStatus("可以生成动画");
}
// 同时更新动画按钮状态,因为对象或路径的变化会影响"开始动画"按钮的可用性
UpdateAnimationButtonStates();
}
@ -1528,24 +1528,24 @@ namespace NavisworksTransport.UI.WPF.ViewModels
double totalLength = 0.0;
var points = CurrentPathRoute.Points;
for (int i = 0; i < points.Count - 1; i++)
{
var p1 = points[i];
var p2 = points[i + 1];
// 计算两点之间的欧几里得距离(模型单位)
double dx = p2.X - p1.X;
double dy = p2.Y - p1.Y;
double dz = p2.Z - p1.Z;
double segmentLengthInModelUnits = Math.Sqrt(dx * dx + dy * dy + dz * dz);
// 转换为米单位
double segmentLengthInMeters = NavisworksTransport.Utils.UnitsConverter.ConvertToMeters(segmentLengthInModelUnits);
totalLength += segmentLengthInMeters;
}
return totalLength;
}
@ -1561,11 +1561,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
var hasValidPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
var hasValidAnimatedObject = SelectedAnimatedObject != null;
var hasValidAnimation = hasValidPath && hasValidAnimatedObject;
// 检查动画管理器当前状态
var animationState = _pathAnimationManager?.CurrentState ?? NavisworksTransport.Core.Animation.AnimationState.Stopped;
var isAnimating = _pathAnimationManager?.IsAnimating ?? false;
// 检查动画是否已经生成Ready状态表示动画已生成但未播放
var isAnimationReady = animationState == NavisworksTransport.Core.Animation.AnimationState.Ready ||
animationState == NavisworksTransport.Core.Animation.AnimationState.Finished;
@ -1580,7 +1580,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CanStopAnimation = true;
UpdateMainStatus("动画播放中");
break;
case NavisworksTransport.Core.Animation.AnimationState.Paused:
// 暂停中:开始按钮可用(显示为继续),暂停按钮禁用,停止按钮可用
CanStartAnimation = hasValidAnimation && isAnimationReady;
@ -1594,7 +1594,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CanStartAnimation = hasValidAnimation && isAnimationReady;
CanPauseAnimation = false;
CanStopAnimation = false;
// 更新状态文本
if (!hasValidPath && !hasValidAnimatedObject)
{
@ -1636,10 +1636,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
LogManager.Info("开始从Clash Detective获取碰撞检测结果");
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var documentClash = doc.GetClash();
if (documentClash == null)
{
LogManager.Warning("无法获取Clash Detective文档");
@ -1666,7 +1666,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
// 递归统计测试中的碰撞总数(包括分组内的)
var totalCollisionCount = GetTotalClashResultCountForReport(test);
var testInfo = new CollisionTestInfo
{
TestName = test.DisplayName,
@ -1761,9 +1761,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 没有碰撞时显示简单提示
LogManager.Info("未发现碰撞问题,路径规划良好");
System.Windows.MessageBox.Show(
"未发现碰撞问题,路径规划良好!",
"碰撞检测结果",
System.Windows.MessageBoxButton.OK,
"未发现碰撞问题,路径规划良好!",
"碰撞检测结果",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Information);
}
else
@ -1771,9 +1771,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 报告无效时的错误处理
LogManager.Error($"碰撞报告数据无效: {reportData.ErrorMessage}");
System.Windows.MessageBox.Show(
$"碰撞报告生成失败:{reportData.ErrorMessage}",
"错误",
System.Windows.MessageBoxButton.OK,
$"碰撞报告生成失败:{reportData.ErrorMessage}",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
}
}
@ -1781,28 +1781,28 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
LogManager.Error($"显示碰撞报告异常: {ex.Message}");
System.Windows.MessageBox.Show(
$"显示碰撞报告时发生异常:{ex.Message}",
"错误",
System.Windows.MessageBoxButton.OK,
$"显示碰撞报告时发生异常:{ex.Message}",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error);
}
}
#endregion
#region
/// <summary>
/// 获取单例实例
/// </summary>
private static AnimationControlViewModel _instance;
public static AnimationControlViewModel Instance
{
public static AnimationControlViewModel Instance
{
get { return _instance; }
set { _instance = value; }
}
/// <summary>
/// 文档失效时的处理
/// </summary>
@ -1818,7 +1818,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Error($"[AnimationControlViewModel] 处理文档失效失败: {ex.Message}");
}
}
/// <summary>
/// 文档就绪时的处理
/// </summary>
@ -1827,11 +1827,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
LogManager.Info("[AnimationControlViewModel] 文档就绪,启用功能");
// 重新启用UI
CanStartAnimation = true;
CanGenerateAnimation = true;
UpdateMainStatus("文档已加载,就绪");
}
catch (Exception ex)
@ -1839,7 +1839,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Error($"[AnimationControlViewModel] 处理文档就绪失败: {ex.Message}");
}
}
/// <summary>
/// 重置视图模型状态
/// </summary>
@ -1848,28 +1848,28 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
LogManager.Info("[AnimationControlViewModel] 重置状态");
// 停止动画
if (_pathAnimationManager != null && _pathAnimationManager.IsAnimating)
{
_pathAnimationManager.CancelAnimation();
}
// 清空选中对象
SelectedAnimatedObject = null;
SelectedAnimatedObjectName = string.Empty;
CurrentPathRoute = null;
// 禁用按钮
CanStartAnimation = false;
CanPauseAnimation = false;
CanStopAnimation = false;
CanGenerateAnimation = false;
HasSelectedAnimatedObject = false;
// 清空碰撞结果
HasCollisionResults = false;
UpdateMainStatus("已重置");
}
catch (Exception ex)
@ -1877,7 +1877,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Error($"[AnimationControlViewModel] 重置失败: {ex.Message}");
}
}
#endregion
#region
@ -1907,7 +1907,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info("开始清理AnimationControlViewModel资源");
// 1. 取消事件订阅(在停止动画之前,避免事件处理中访问已释放的对象)
// 取消DocumentStateManager事件订阅
try
{
@ -1919,7 +1919,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
LogManager.Warning($"取消DocumentStateManager事件订阅时出现警告: {docEx.Message}");
}
if (_pathAnimationManager != null)
{
try
@ -1933,7 +1933,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"取消PathAnimationManager事件订阅时出现警告: {eventEx.Message}");
}
}
if (_clashIntegration != null)
{
try
@ -1946,7 +1946,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"取消碰撞检测事件订阅时出现警告: {eventEx.Message}");
}
}
// 3. 不再清理PathAnimationManager - 现在使用单例模式,由应用程序生命周期管理
// 注意PathAnimationManager.GetInstance()返回的是单例实例,
// 不应该在这里Dispose否则会影响其他使用该单例的地方
@ -1954,19 +1954,19 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
try
{
// 只清理事件订阅和重置状态不调用Dispose
_pathAnimationManager.ResetAnimation();
LogManager.Debug("PathAnimationManager状态重置完成");
// 只清理事件订阅和停止动画不调用Dispose也不重置位置(避免访问已释放对象)
_pathAnimationManager.ShutdownAnimation();
LogManager.Debug("PathAnimationManager已执行关闭清理");
}
catch (Exception resetEx)
{
LogManager.Warning($"重置PathAnimationManager状态时出现警告: {resetEx.Message}");
LogManager.Warning($"执行PathAnimationManager关闭清理时出现警告: {resetEx.Message}");
}
}
// 4. 清理PathAnimationManager缓存缓存功能已迁移到PathAnimationManager
// PathAnimationManager的Dispose已经包含了ClearExclusionCache的调用
LogManager.Info("AnimationControlViewModel资源清理完成");
}
catch (Exception ex)
@ -1975,7 +1975,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 即使清理过程中发生异常,也不应该抛出,因为这会干扰应用程序的正常关闭
}
}
_disposed = true;
}
}
@ -1986,7 +1986,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private int GetTotalClashResultCountForReport(ClashTest test)
{
if (test == null) return 0;
int totalCount = 0;
foreach (var child in test.Children)
{
@ -2000,17 +2000,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
totalCount += CountClashResultsInGroupForReport(group);
}
}
return totalCount;
}
/// <summary>
/// 递归统计分组内的碰撞结果数量 - 用于报告
/// </summary>
private int CountClashResultsInGroupForReport(ClashResultGroup group)
{
if (group == null) return 0;
int count = 0;
foreach (var child in group.Children)
{
@ -2024,10 +2024,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
count += CountClashResultsInGroupForReport(nestedGroup);
}
}
return count;
}
/// <summary>
/// 递归获取测试中的所有碰撞结果(包括分组内的)- 用于报告
/// </summary>
@ -2035,7 +2035,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
var results = new List<ClashResult>();
if (test == null) return results;
foreach (var child in test.Children)
{
if (child is ClashResult result)
@ -2048,10 +2048,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
results.AddRange(GetAllClashResultsFromGroup(group));
}
}
return results;
}
/// <summary>
/// 递归获取分组内的所有碰撞结果 - 用于报告
/// </summary>
@ -2059,7 +2059,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
var results = new List<ClashResult>();
if (group == null) return results;
foreach (var child in group.Children)
{
if (child is ClashResult result)
@ -2072,7 +2072,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
results.AddRange(GetAllClashResultsFromGroup(nestedGroup));
}
}
return results;
}