使用复合节点改善性能,并且适配Clashdetective输出的结果(只包含实体节点)
This commit is contained in:
parent
cf2e5a4d2f
commit
c4cf718502
@ -2607,6 +2607,7 @@ await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
**官方示例参考**:
|
||||
|
||||
官方示例文件中的 Progress API 使用案例:
|
||||
|
||||
- `ClashDetective/ClashGrouper/ClashGrouperUtils.cs` - 碰撞分组中的进度条使用
|
||||
- 展示了在复杂算法中如何正确使用 Progress API 和取消支持
|
||||
|
||||
@ -2631,13 +2632,13 @@ await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
关键发现
|
||||
|
||||
1. 每次文档操作都有两个事件:
|
||||
- 先清空Models(Count=0)
|
||||
- 再加载新Models(Count>0)
|
||||
2. 状态转换清晰:
|
||||
- 无模型→无模型:初始清理
|
||||
- 无模型→有模型:文档加载完成 ✅ 这是我们需要初始化的时机
|
||||
- 有模型→无模型:文档关闭/切换 ✅ 这是我们需要清理的时机
|
||||
3. 时间间隔很短:事件成对出现,间隔仅0.1秒左右
|
||||
- 先清空Models(Count=0)
|
||||
- 再加载新Models(Count>0)
|
||||
1. 状态转换清晰:
|
||||
- 无模型→无模型:初始清理
|
||||
- 无模型→有模型:文档加载完成 ✅ 这是我们需要初始化的时机
|
||||
- 有模型→无模型:文档关闭/切换 ✅ 这是我们需要清理的时机
|
||||
1. 时间间隔很短:事件成对出现,间隔仅0.1秒左右
|
||||
|
||||
### Navisworks Fragment和Path的API分析
|
||||
|
||||
@ -2659,7 +2660,7 @@ await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
- 用于显示的几何体
|
||||
- 用于显示的外观
|
||||
|
||||
2. Path概念(第8页)
|
||||
1. Path概念(第8页)
|
||||
|
||||
A Path is the sequence of nodes from the Partition at the root of the
|
||||
scene graph to a particular node within it that uniquely specifies
|
||||
@ -2671,7 +2672,7 @@ await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
- Path用来唯一标识特定实例
|
||||
- 但这里的"实例"指的是场景图中的节点实例,不是几何体的不同部分
|
||||
|
||||
3. 你的地板问题分析
|
||||
1. 你的地板问题分析
|
||||
|
||||
真正的问题:
|
||||
|
||||
@ -2693,7 +2694,7 @@ await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
- 多实例去重:应该针对不同ModelItem的相同几何体
|
||||
- 多片段保留:应该保留同一ModelItem的所有Fragment
|
||||
|
||||
4. 解决方案建议
|
||||
1. 解决方案建议
|
||||
|
||||
需要修改去重逻辑,区分:
|
||||
|
||||
@ -2715,7 +2716,7 @@ await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
|
||||
### Setting “Units and Transform” values via Navisworks API
|
||||
|
||||
https://blog.autodesk.io/setting-units-and-transform-values-via-navisworks-api/
|
||||
<https://blog.autodesk.io/setting-units-and-transform-values-via-navisworks-api/>
|
||||
|
||||
DocumentModels models = doc.Models;
|
||||
//Get the required model from DocumentModels
|
||||
@ -2961,6 +2962,7 @@ CREATE TABLE ClashDetectiveCollisionObjects (
|
||||
```
|
||||
|
||||
**关键要点**:
|
||||
|
||||
- **最小化存储**:只需要 `ModelIndex` 和 `PathId` 两个字段即可恢复对象
|
||||
- **避免冗余**:不要存储与 PathId 相同的 ModelItemPath 字段
|
||||
- **保留显示信息**:DisplayName 和 ObjectName 用于 UI 显示和日志,不影响对象恢复
|
||||
@ -3097,6 +3099,7 @@ public ModelItem LoadObject(int? modelIndex, string pathId)
|
||||
- 如果 NWD 文件被重新导出或结构改变,PathId 可能失效
|
||||
|
||||
2. **批量恢复优化**:
|
||||
|
||||
```csharp
|
||||
// ✅ 批量恢复时,先收集所有 PathId
|
||||
var pathIds = collisionObjects.Select(obj => new ModelItemPathId
|
||||
@ -3123,6 +3126,7 @@ public ModelItem LoadObject(int? modelIndex, string pathId)
|
||||
```
|
||||
|
||||
3. **缓存机制**:
|
||||
|
||||
```csharp
|
||||
// ✅ 使用缓存减少重复查询
|
||||
private Dictionary<string, ModelItem> _modelItemCache = new Dictionary<string, ModelItem>();
|
||||
@ -3148,8 +3152,229 @@ public ModelItem LoadObject(int? modelIndex, string pathId)
|
||||
|------|------|------|---------|
|
||||
| **CreatePathId/ResolvePathId** | 官方API,稳定可靠,跨会话有效 | 需要保存两个字段 | 推荐用于所有持久化场景 |
|
||||
| **ModelItem.GetHashCode()** | 简单,单个值 | 跨会话无效,哈希冲突 | 仅用于运行时去重 |
|
||||
|
||||
## 14. Clash Detective API 使用方法
|
||||
|
||||
### 14.1 Clash Detective 检测行为的关键洞察 ⚠️ 重要
|
||||
|
||||
基于实际测试验证的核心发现(2026-01-13)。
|
||||
|
||||
#### 14.1.1 输入 vs 输出的对象类型差异
|
||||
|
||||
**⚠️ 核心发现:Clash Detective 返回的对象与输入的对象类型不同**
|
||||
|
||||
```csharp
|
||||
// 🔍 实际测试验证:
|
||||
// 输入给 Clash Detective 的对象:
|
||||
var selectionA = new ModelItemCollection { compositeItem }; // IsComposite=True
|
||||
var selectionB = new ModelItemCollection { compositeItem2 }; // IsComposite=True
|
||||
|
||||
test.SelectionA.Selection.CopyFrom(selectionA);
|
||||
test.SelectionB.Selection.CopyFrom(selectionB);
|
||||
|
||||
// Clash Detective 返回的对象:
|
||||
ClashResult result = test.Children[0] as ClashResult;
|
||||
// result.Item1.IsComposite = False ← 注意:变成了 False
|
||||
// result.Item2.IsComposite = False ← 注意:变成了 False
|
||||
// result.Item1.DisplayName = "" ← 注意:名字是空的
|
||||
// result.Item2.DisplayName = "" ← 注意:名字是空的
|
||||
```
|
||||
|
||||
**日志验证**:
|
||||
|
||||
```
|
||||
[ClashDetective输入] 候选1: Item1=Chair Sitting Square (IsComposite=True), Item2=Chair-Stacking (IsComposite=True)
|
||||
[ClashDetective结果] 原始碰撞: Item1= (IsComposite=False), Item2= (IsComposite=False)
|
||||
[ClashDetective结果] 容器映射: Item1=Chair Sitting Square (IsComposite=True), Item2=Chair-Stacking (IsComposite=True)
|
||||
```
|
||||
|
||||
#### 14.1.2 Clash Detective 的内部行为
|
||||
|
||||
**关键理解**:
|
||||
|
||||
1. **输入对象**:我们给 Clash Detective 的是复合对象(`IsComposite=True`)
|
||||
2. **检测过程**:Clash Detective 深入到几何体级别进行碰撞检测
|
||||
3. **返回对象**:Clash Detective 返回的是实际发生碰撞的几何体子节点(`IsComposite=False`)
|
||||
4. **命名问题**:几何体子节点通常没有 DisplayName,所以返回的名字是空的
|
||||
|
||||
**为什么这样设计**:
|
||||
|
||||
- Clash Detective 需要精确到几何体级别进行碰撞检测
|
||||
- 复合对象可能包含多个几何体子节点
|
||||
- 返回具体的几何体节点可以提供更精确的碰撞位置信息
|
||||
|
||||
#### 14.1.3 正确的处理方法
|
||||
|
||||
**解决方案:使用 FindNamedParentContainer 映射回复合对象**
|
||||
|
||||
```csharp
|
||||
// ✅ 正确方法:容器映射
|
||||
foreach (var child in collisionGroup.Children)
|
||||
{
|
||||
if (child is ClashResult clashResult)
|
||||
{
|
||||
// 1. Clash Detective 返回的是几何体级别的子节点
|
||||
var originalItem1 = clashResult.Item1; // IsComposite=False, DisplayName=""
|
||||
var originalItem2 = clashResult.Item2; // IsComposite=False, DisplayName=""
|
||||
|
||||
// 2. 向上查找有意义的父级容器
|
||||
var compositeItem1 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item1);
|
||||
var compositeItem2 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item2);
|
||||
|
||||
// 3. 使用复合对象创建碰撞结果
|
||||
var collisionResult = new CollisionResult
|
||||
{
|
||||
ClashGuid = clashResult.Guid,
|
||||
DisplayName = clashResult.DisplayName,
|
||||
Status = clashResult.Status,
|
||||
Item1 = compositeItem1, // IsComposite=True, DisplayName="Chair Sitting Square"
|
||||
Item2 = compositeItem2, // IsComposite=True, DisplayName="Chair-Stacking"
|
||||
Center = clashResult.Center,
|
||||
Distance = clashResult.Distance,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 14.1.4 FindNamedParentContainer 的实现
|
||||
|
||||
```csharp
|
||||
// ✅ 向上查找有意义的父级容器
|
||||
public static ModelItem FindNamedParentContainer(ModelItem geometryItem)
|
||||
{
|
||||
if (geometryItem == null)
|
||||
return null;
|
||||
|
||||
ModelItem current = geometryItem;
|
||||
|
||||
// 向上遍历父节点链
|
||||
while (current != null)
|
||||
{
|
||||
// 如果当前节点有名字且是复合对象,返回它
|
||||
if (!string.IsNullOrEmpty(current.DisplayName) && current.IsComposite)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
// 继续向上查找
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
// 如果没找到,返回原始对象
|
||||
return geometryItem;
|
||||
}
|
||||
```
|
||||
|
||||
#### 14.1.5 容器映射的必要性
|
||||
|
||||
**为什么必须进行容器映射**:
|
||||
|
||||
1. **用户可读性**:复合对象有有意义的名字(如"Chair-Stacking"),几何体子节点没有名字
|
||||
2. **高亮显示**:高亮复合对象比高亮几何体子节点更直观
|
||||
3. **碰撞报告**:报告需要显示有意义的对象名称
|
||||
4. **数据一致性**:预计算使用复合对象,Clash Detective 结果也应该使用复合对象
|
||||
|
||||
**不进行容器映射的问题**:
|
||||
|
||||
```csharp
|
||||
// ❌ 错误:直接使用 Clash Detective 返回的对象
|
||||
var collisionResult = new CollisionResult
|
||||
{
|
||||
Item1 = clashResult.Item1, // DisplayName="", IsComposite=False
|
||||
Item2 = clashResult.Item2 // DisplayName="", IsComposite=False
|
||||
};
|
||||
|
||||
// 问题:
|
||||
// 1. 碰撞报告中显示"未命名对象"
|
||||
// 2. 高亮显示的是几何体子节点(用户看不到)
|
||||
// 3. 无法与预计算结果对应(预计算使用复合对象)
|
||||
```
|
||||
|
||||
#### 14.1.6 性能考虑
|
||||
|
||||
**容器映射的性能影响**:
|
||||
|
||||
- 每次碰撞结果都需要向上遍历父节点链
|
||||
- 对于大型模型,可能影响性能
|
||||
- 建议缓存容器映射结果
|
||||
|
||||
**优化方案**:
|
||||
|
||||
```csharp
|
||||
// ✅ 使用缓存优化容器映射
|
||||
private Dictionary<ModelItem, ModelItem> _containerCache = new Dictionary<ModelItem, ModelItem>();
|
||||
|
||||
public ModelItem FindNamedParentContainerCached(ModelItem geometryItem)
|
||||
{
|
||||
if (geometryItem == null)
|
||||
return null;
|
||||
|
||||
// 检查缓存
|
||||
if (_containerCache.TryGetValue(geometryItem, out ModelItem cachedContainer))
|
||||
{
|
||||
return cachedContainer;
|
||||
}
|
||||
|
||||
// 执行容器映射
|
||||
var container = FindNamedParentContainer(geometryItem);
|
||||
|
||||
// 缓存结果
|
||||
_containerCache[geometryItem] = container;
|
||||
|
||||
return container;
|
||||
}
|
||||
```
|
||||
|
||||
#### 14.1.7 与 MergeComposites 选项的关系
|
||||
|
||||
**MergeComposites 选项的作用**:
|
||||
|
||||
```csharp
|
||||
var test = new ClashTest
|
||||
{
|
||||
DisplayName = testName,
|
||||
TestType = ClashTestType.HardConservative,
|
||||
Tolerance = detectionGap,
|
||||
MergeComposites = true // 这个选项
|
||||
};
|
||||
```
|
||||
|
||||
**MergeComposites = true 的效果**:
|
||||
|
||||
- 将同一个复合对象的多个子节点碰撞合并为一个碰撞
|
||||
- 减少重复的碰撞结果
|
||||
- 但仍然返回几何体级别的对象(不是复合对象)
|
||||
|
||||
**验证**:
|
||||
|
||||
```
|
||||
// 即使设置了 MergeComposites=true
|
||||
// Clash Detective 返回的仍然是:
|
||||
// result.Item1.IsComposite = False
|
||||
// result.Item2.IsComposite = False
|
||||
// result.Item1.DisplayName = ""
|
||||
```
|
||||
|
||||
**结论**:`MergeComposites` 选项只是减少碰撞数量,不会改变返回对象的类型。容器映射仍然是必需的。
|
||||
|
||||
#### 14.1.8 最佳实践总结
|
||||
|
||||
| 场景 | 方法 | 注意事项 |
|
||||
|------|------|---------|
|
||||
| 碰撞检测输入 | 使用复合对象 | 提高检测效率 |
|
||||
| 碰撞结果提取 | 使用容器映射 | 获取有意义的对象名称 |
|
||||
| 碰撞报告显示 | 使用复合对象 | 显示可读的名称 |
|
||||
| 高亮显示 | 使用复合对象 | 高亮整个对象 |
|
||||
| 性能优化 | 缓存容器映射 | 减少重复计算 |
|
||||
|
||||
**关键要点**:
|
||||
|
||||
1. **Clash Detective 输入和输出的对象类型不同**
|
||||
2. **必须使用 FindNamedParentContainer 进行容器映射**
|
||||
3. **容器映射确保碰撞结果的名称和可读性**
|
||||
4. **使用缓存优化容器映射的性能**
|
||||
| **索引路径(旧方法)** | 简单易懂 | 不稳定,易失效 | 不推荐使用 |
|
||||
| **DisplayName** | 易于理解 | 不唯一,可能重复 | 仅用于UI显示 |
|
||||
|
||||
**结论**:`CreatePathId/ResolvePathId` 是 Navisworks API 提供的官方持久化方法,应该作为首选方案。
|
||||
|
||||
|
||||
@ -539,14 +539,19 @@ namespace NavisworksTransport.Core.Animation
|
||||
LogManager.Info("=== 构建全局空间索引 ===");
|
||||
spatialIndexManager = SpatialIndexManager.Instance;
|
||||
|
||||
double vehicleWidthMeters = ConfigManager.Instance.Current.PathEditing.VehicleWidthMeters;
|
||||
// 使用传入的车辆宽度参数,而不是从ConfigManager读取
|
||||
double vehicleWidthMeters = _virtualVehicleWidth;
|
||||
double cellSizeInModelUnits = UnitsConverter.ConvertFromMeters(vehicleWidthMeters);
|
||||
|
||||
LogManager.Info($"[空间索引] 车辆宽度: {vehicleWidthMeters:F2}米 → 格子大小: {cellSizeInModelUnits:F2}模型单位");
|
||||
|
||||
if (!spatialIndexManager.IsInitialized)
|
||||
// 检查空间索引是否需要重新构建(格子大小变化或未初始化)
|
||||
bool needRebuild = !spatialIndexManager.IsInitialized ||
|
||||
Math.Abs(spatialIndexManager.CellSize - cellSizeInModelUnits) > 0.0001;
|
||||
|
||||
if (needRebuild)
|
||||
{
|
||||
LogManager.Info("[空间索引] 空间索引未初始化,开始构建...");
|
||||
LogManager.Info("[空间索引] 空间索引未初始化或格子大小已改变,开始重新构建...");
|
||||
spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits);
|
||||
}
|
||||
else
|
||||
@ -2412,13 +2417,10 @@ namespace NavisworksTransport.Core.Animation
|
||||
{
|
||||
foreach (var collision in currentFrame.Collisions)
|
||||
{
|
||||
var item1 = collision.GetValidItem1();
|
||||
var item2 = collision.GetValidItem2();
|
||||
|
||||
if (item1 != null)
|
||||
currentCollisionObjects.Add(item1);
|
||||
if (item2 != null)
|
||||
currentCollisionObjects.Add(item2);
|
||||
if (collision.Item1 != null)
|
||||
currentCollisionObjects.Add(collision.Item1);
|
||||
if (collision.Item2 != null)
|
||||
currentCollisionObjects.Add(collision.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2631,8 +2633,9 @@ namespace NavisworksTransport.Core.Animation
|
||||
sb.Append("|");
|
||||
|
||||
// 包含虚拟车辆尺寸(确保车辆尺寸改变时重新检测)
|
||||
var vehicleConfig = ConfigManager.Instance.Current.PathEditing;
|
||||
sb.Append($"Vehicle:{vehicleConfig.VehicleLengthMeters:F2}x{vehicleConfig.VehicleWidthMeters:F2}x{vehicleConfig.VehicleHeightMeters:F2}");
|
||||
// 使用传入的车辆尺寸参数,而不是从ConfigManager读取
|
||||
var vehicleSizeString = $"Vehicle:{_virtualVehicleLength:F2}x{_virtualVehicleWidth:F2}x{_virtualVehicleHeight:F2}";
|
||||
sb.Append(vehicleSizeString);
|
||||
sb.Append("|");
|
||||
|
||||
// 包含手工检测对象列表(确保手工指定模式的目标变化时重新检测)
|
||||
|
||||
@ -415,20 +415,20 @@ namespace NavisworksTransport
|
||||
{
|
||||
throw new InvalidOperationException($"[LoadClashDetectiveResultsFromDatabase] 找到的 ModelItem 无效: {obj.ModelIndex},{obj.PathId}");
|
||||
}
|
||||
|
||||
|
||||
// 使用重建对象的 DisplayName,而不是数据库中保存的 DisplayName
|
||||
var collidedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collidedObject);
|
||||
|
||||
var collisionResult = new CollisionResult
|
||||
{
|
||||
ClashGuid = Guid.NewGuid(),
|
||||
DisplayName = $"历史碰撞: {obj.DisplayName}",
|
||||
DisplayName = $"历史碰撞: {collidedObjectName}",
|
||||
Status = ClashResultStatus.Active,
|
||||
Item1 = vehicleObject,
|
||||
Item2 = collidedObject,
|
||||
Center = collidedObject.BoundingBox().Center,
|
||||
Distance = 0.0,
|
||||
CreatedTime = DateTime.Now,
|
||||
OriginalItem1 = vehicleObject,
|
||||
OriginalItem2 = collidedObject,
|
||||
HasContainerMapping = true
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
|
||||
results.Add(collisionResult);
|
||||
@ -444,57 +444,6 @@ namespace NavisworksTransport
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并复合对象碰撞结果
|
||||
/// 将几何体级别的碰撞结果合并成复合对象级别的碰撞结果(去重)
|
||||
/// </summary>
|
||||
private List<CollisionResult> MergeCompositeCollisions(ClashResultGroup collisionGroup)
|
||||
{
|
||||
var clashResults = new List<CollisionResult>();
|
||||
var mergedKeys = new HashSet<string>();
|
||||
|
||||
foreach (var child in collisionGroup.Children)
|
||||
{
|
||||
if (child is ClashResult clashResult)
|
||||
{
|
||||
// 向上查找有意义的父级容器
|
||||
var compositeItem1 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item1);
|
||||
var compositeItem2 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item2);
|
||||
|
||||
// 使用复合对象的HashCode作为key去重
|
||||
var key = $"{compositeItem1.GetHashCode()}_{compositeItem2.GetHashCode()}";
|
||||
|
||||
if (!mergedKeys.Contains(key))
|
||||
{
|
||||
// 创建自定义的CollisionResult对象
|
||||
var collisionResult = new CollisionResult
|
||||
{
|
||||
ClashGuid = clashResult.Guid,
|
||||
DisplayName = clashResult.DisplayName,
|
||||
Status = clashResult.Status,
|
||||
Item1 = compositeItem1,
|
||||
Item2 = compositeItem2,
|
||||
Center = clashResult.Center,
|
||||
Distance = clashResult.Distance,
|
||||
CreatedTime = DateTime.Now,
|
||||
OriginalItem1 = clashResult.Item1,
|
||||
OriginalItem2 = clashResult.Item2,
|
||||
HasContainerMapping = true
|
||||
};
|
||||
|
||||
clashResults.Add(collisionResult);
|
||||
mergedKeys.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"复合对象处理完成:原始{collisionGroup.Children.Count}个碰撞,合并后{clashResults.Count}个碰撞");
|
||||
|
||||
return clashResults;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象当前位置
|
||||
/// </summary>
|
||||
@ -536,11 +485,18 @@ namespace NavisworksTransport
|
||||
/// <param name="virtualVehicleLength">虚拟车辆长度(米)</param>
|
||||
/// <param name="virtualVehicleWidth">虚拟车辆宽度(米)</param>
|
||||
/// <param name="virtualVehicleHeight">虚拟车辆高度(米)</param>
|
||||
public void CreateAllAnimationCollisionTests(List<CollisionResult> precomputedCollisions, double detectionGap,
|
||||
string pathName = "未知路径", string routeId = null,
|
||||
ModelItem animatedObject = null, bool isVirtualVehicle = false,
|
||||
int frameRate = 30, double duration = 10.0,
|
||||
double virtualVehicleLength = 0, double virtualVehicleWidth = 0, double virtualVehicleHeight = 0)
|
||||
public void CreateAllAnimationCollisionTests(
|
||||
List<CollisionResult> precomputedCollisions,
|
||||
double detectionGap,
|
||||
string pathName,
|
||||
string routeId,
|
||||
ModelItem animatedObject,
|
||||
bool isVirtualVehicle,
|
||||
int frameRate,
|
||||
double duration,
|
||||
double virtualVehicleLength,
|
||||
double virtualVehicleWidth,
|
||||
double virtualVehicleHeight)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -813,7 +769,30 @@ namespace NavisworksTransport
|
||||
LogManager.Info($"[分组测试] ClashDetective检测完成: 确认碰撞 {confirmedCount} 组, 跳过 {skippedCount} 个冗余检测点");
|
||||
|
||||
// 第三步:处理碰撞结果
|
||||
var clashResults = MergeCompositeCollisions(collisionGroup);
|
||||
// 🔥 重要:ClashDetective 返回的是几何体级别的碰撞结果,需要向上查找有意义的父级容器
|
||||
var clashResults = new List<CollisionResult>();
|
||||
foreach (var child in collisionGroup.Children)
|
||||
{
|
||||
if (child is ClashResult clashResult)
|
||||
{
|
||||
// 向上查找有意义的父级容器
|
||||
var compositeItem1 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item1);
|
||||
var compositeItem2 = ModelItemAnalysisHelper.FindNamedParentContainer(clashResult.Item2);
|
||||
|
||||
var collisionResult = new CollisionResult
|
||||
{
|
||||
ClashGuid = clashResult.Guid,
|
||||
DisplayName = clashResult.DisplayName,
|
||||
Status = clashResult.Status,
|
||||
Item1 = compositeItem1,
|
||||
Item2 = compositeItem2,
|
||||
Center = clashResult.Center,
|
||||
Distance = clashResult.Distance,
|
||||
CreatedTime = DateTime.Now
|
||||
};
|
||||
clashResults.Add(collisionResult);
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
lock (_clashResultsCacheLock)
|
||||
@ -1159,17 +1138,22 @@ namespace NavisworksTransport
|
||||
return;
|
||||
}
|
||||
|
||||
// 智能收集通道相关节点(包括父节点、同级节点、子节点等)
|
||||
// 🔥 优化:只提取复合对象(IsComposite = true)
|
||||
// 原因:通道对象通常是复合对象(如通道、楼梯、电梯)
|
||||
// 我们只需要检测复合对象,不需要检测其子节点
|
||||
foreach (var channelItem in allChannelItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
var itemRelatedNodes = ModelItemAnalysisHelper.CollectRelatedNodes(channelItem);
|
||||
|
||||
// 将结果添加到缓存中
|
||||
foreach (var node in itemRelatedNodes)
|
||||
// 只添加复合对象
|
||||
if (channelItem.IsComposite)
|
||||
{
|
||||
_channelObjectsCache.Add(node);
|
||||
_channelObjectsCache.Add(channelItem);
|
||||
}
|
||||
else if (channelItem.HasGeometry)
|
||||
{
|
||||
// 非复合对象但有几何数据(独立实体节点):也添加
|
||||
_channelObjectsCache.Add(channelItem);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -1256,24 +1240,34 @@ namespace NavisworksTransport
|
||||
if (item.IsHidden)
|
||||
continue;
|
||||
|
||||
// 收集:如果是几何体,加入缓存
|
||||
// 注意:这里我们假设只有几何体(HasGeometry)才是我们需要碰撞检测的对象
|
||||
// 如果父组没有Geometry但子节点有,我们会继续遍历子节点
|
||||
if (item.HasGeometry)
|
||||
// 🔥 优化:只提取复合对象(IsComposite = true)
|
||||
// 原因:复合对象(如窗户)包含多个实体节点(玻璃、框架、把手)
|
||||
// 我们只需要检测复合对象,不需要检测其子节点
|
||||
// Clash Detective API 会自动处理复合对象的子节点几何体
|
||||
if (item.IsComposite)
|
||||
{
|
||||
// 复合对象:添加到缓存,不继续遍历子节点
|
||||
_allGeometryItemsCache.Add(item);
|
||||
visibleCount++;
|
||||
}
|
||||
|
||||
// 将子节点压入栈 (倒序压入以保持遍历顺序,虽不 strictly necessary)
|
||||
foreach (var child in item.Children)
|
||||
else if (item.HasGeometry)
|
||||
{
|
||||
stack.Push(child);
|
||||
// 非复合对象但有几何数据(独立实体节点):添加到缓存
|
||||
_allGeometryItemsCache.Add(item);
|
||||
visibleCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 空节点:继续遍历子节点
|
||||
foreach (var child in item.Children)
|
||||
{
|
||||
stack.Push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cacheStopwatch.Stop();
|
||||
LogManager.Info($"几何对象列表缓存构建完成 (DFS遍历 + 自动剪枝),耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
|
||||
LogManager.Info($"复合对象列表缓存构建完成 (DFS遍历 + 自动剪枝),耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
|
||||
LogManager.Info($" - 缓存可见对象: {visibleCount}");
|
||||
LogManager.Info($" - 策略: 深度优先搜索 (父节点隐藏则跳过分支)");
|
||||
}
|
||||
@ -1286,14 +1280,14 @@ namespace NavisworksTransport
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取几何对象缓存(供外部使用)
|
||||
/// 获取复合对象缓存(供外部使用)
|
||||
/// </summary>
|
||||
/// <returns>几何对象集合,如果缓存不存在则返回null</returns>
|
||||
/// <returns>复合对象集合,如果缓存不存在则返回null</returns>
|
||||
public static ModelItemCollection GetAllGeometryItemsCache()
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
return _allGeometryItemsCache; // 直接返回 ModelItemCollection,避免 ToList() 转换
|
||||
return _allGeometryItemsCache;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1453,19 +1447,6 @@ namespace NavisworksTransport
|
||||
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;
|
||||
|
||||
// IEquatable<CollisionResult> 实现:基于碰撞对象进行去重
|
||||
public bool Equals(CollisionResult other)
|
||||
{
|
||||
|
||||
@ -2145,22 +2145,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
if (UseVirtualVehicle)
|
||||
{
|
||||
// 使用虚拟车辆:优先获取已存在的实例
|
||||
// 使用虚拟车辆:总是调用 ShowVirtualVehicle 来更新尺寸
|
||||
LogManager.Info("[ExecuteGenerateAnimation] 显示并更新虚拟车辆...");
|
||||
UpdateMainStatus("正在显示虚拟车辆...", -1, true);
|
||||
|
||||
VirtualVehicleManager.Instance.ShowVirtualVehicle(
|
||||
VirtualVehicleLength,
|
||||
VirtualVehicleWidth,
|
||||
VirtualVehicleHeight
|
||||
);
|
||||
animatedObject = VirtualVehicleManager.Instance.CurrentVirtualVehicle;
|
||||
|
||||
if (animatedObject == null)
|
||||
{
|
||||
LogManager.Info("[ExecuteGenerateAnimation] 虚拟车辆不存在,正在显示...");
|
||||
UpdateMainStatus("正在显示虚拟车辆...", -1, true);
|
||||
|
||||
VirtualVehicleManager.Instance.ShowVirtualVehicle(
|
||||
VirtualVehicleLength,
|
||||
VirtualVehicleWidth,
|
||||
VirtualVehicleHeight
|
||||
);
|
||||
animatedObject = VirtualVehicleManager.Instance.CurrentVirtualVehicle;
|
||||
}
|
||||
|
||||
if (animatedObject == null)
|
||||
{
|
||||
LogManager.Error("[动画生成] 虚拟车辆显示失败");
|
||||
@ -2168,6 +2163,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
// 将虚拟车辆移到路径起点
|
||||
if (CurrentPathRoute != null && CurrentPathRoute.Points != null)
|
||||
{
|
||||
var points = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
|
||||
_pathAnimationManager?.MoveVehicleToPathStart(animatedObject, points);
|
||||
LogManager.Info("[ExecuteGenerateAnimation] 虚拟车辆已移到路径起点");
|
||||
}
|
||||
|
||||
LogManager.Info($"使用虚拟车辆: {animatedObject.DisplayName}");
|
||||
}
|
||||
else
|
||||
|
||||
@ -357,7 +357,8 @@ namespace NavisworksTransport.Utils
|
||||
foreach (var clash in clashResults)
|
||||
{
|
||||
// 只高亮被撞物体(Item2),排除车辆对象(Item1)
|
||||
if (clash.Item2 != null && !highlightItems.Contains(clash.Item2))
|
||||
// 🔥 移除去重逻辑:应该显示所有被撞物体,包括重复的
|
||||
if (clash.Item2 != null)
|
||||
{
|
||||
highlightItems.Add(clash.Item2);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user