优化碰撞检测算法,进行合理的去重后再检测;优化动画控制逻辑,避免重复订阅事件和资源清理;
This commit is contained in:
parent
a832e91b7b
commit
f8320066c1
@ -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](优化)在分层预览列表中,去掉”文件大小“和”状态“列,增加:
|
||||
- “是否保存”列,内容是单选框,默认选中,选中的行,在点击“分层保存”时会保存,未选中的不保存
|
||||
- 在列表下方,增加“单独显示”按钮,点击后会隐藏除了当前行之外的其他部分
|
||||
- 保存文件时,文件名的格式:根节点名_自定义属性名_属性值_时间戳
|
||||
|
||||
41
doc/working/碰撞检测性能优化_20251208.md
Normal file
41
doc/working/碰撞检测性能优化_20251208.md
Normal 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 个冗余检测点",方便评估优化效果。
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user