# Human-in-the-Loop 碰撞优化功能 - 实现完成报告 ## 当前状态(2026-02-09) ### 已完成功能 #### 第一阶段(基础分析)✅ 1. **碰撞热点自动分析** - 预计算完成后自动分析碰撞结果 2. **自动高亮显示** - 自动高亮所有预计算碰撞结果(紫色 #9C27B0) 3. **分析对话框** - 显示详细的碰撞统计和建议排除选项 4. **光标修复** - 对话框显示时光标恢复正常 #### 第二阶段(排除列表集成)✅ 1. **排除列表数据结构** - `PathAnimationManager` 中使用 `HashSet` 2. **管理方法** - `SetExcludedObjectsAndClearCache`、`AddExcludedObjects`、`ClearExcludedObjects` 3. **预计算过滤** - 在 `PrecomputeAnimationFrames()` 中应用排除列表过滤 4. **对话框集成** - 分析结果自动合并到排除列表 5. **UI区域** - 新增"检测排除对象"区域,支持手工添加和显示排除列表 #### 第三阶段(UI优化)✅ 1. **碰撞分析对话框增强** - 序号列显示 - 行点击自动高亮对应物体(紫色预计算风格) - 候选碰撞数字黑色粗体显示 - 预计检测时间显示(xxx秒/xx.x分钟) - 智能自动选中(碰撞>100次 或 占比>50%) - 单一"继续生成动画"按钮(自动判断是否有排除对象) 2. **排除列表UI完善** - 添加"清除高亮"按钮 - 删除列表项时自动清除高亮 - 使用 `ModelItemEquals` 正确比较对象(修复 InstanceGuid 问题) #### 第四阶段(数据库持久化)✅ 1. **数据库表结构** - `ExcludedObjects` - 存储排除对象(PathId、DisplayName、ModelIndex等) - `CollisionReportExcludedObjects` - 碰撞报告与排除对象关联表 - `ClashDetectiveExcludedObjects` - ClashDetective结果与排除对象关联表 2. **数据持久化** - 排除对象随碰撞报告一起保存到数据库 - 支持从数据库加载排除对象(通过PathId查找) - 碰撞历史和报告中显示当时使用的排除对象列表 3. **核心方法** - `SaveExcludedObjectsToDatabase()` - 保存排除对象到数据库 - `LoadExcludedObjectsFromDatabase()` - 从数据库加载排除对象 - `LinkExcludedObjectsToCollisionReport()` - 关联排除对象到碰撞报告 --- ## 新增/修改文件 | 文件 | 说明 | |------|------| | `src/UI/WPF/Views/CollisionAnalysisDialog.xaml` | 分析对话框UI(已统一项目风格) | | `src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs` | 分析对话框逻辑(序号、高亮、智能选中) | | `src/UI/WPF/ViewModels/AnimationControlViewModel.cs` | 添加分析逻辑、排除列表管理、数据库保存 | | `src/Core/Animation/PathAnimationManager.cs` | 排除列表字段、预计算过滤、数据库加载/保存 | | `src/UI/WPF/Views/AnimationControlView.xaml` | "检测排除对象"UI区域 | | `src/Utils/ModelItemAnalysisHelper.cs` | 添加 `ModelItemEquals` 正确比较方法 | | `src/Core/PathDatabase.cs` | 添加排除列表相关的数据库表和方法 | --- ## 技术实现细节 ### 排除列表存储与比较 ```csharp // 使用 HashSet 存储排除对象 private HashSet _excludedObjects = new HashSet(); // 正确的 ModelItem 比较(使用底层原生对象比较) public static bool ModelItemEquals(ModelItem item1, ModelItem item2) { if (item1 == null && item2 == null) return true; if (item1 == null || item2 == null) return false; return item1.Equals(item2); // 使用 Navisworks API 的 Equals } ``` **重要修复**:之前使用 `InstanceGuid` 比较会导致不同对象被误判为相同,现在统一使用 `ModelItemEquals`。 ### 预计算过滤点 ```csharp // 在 PrecomputeAnimationFrames 的循环中 var nearbyObjects = spatialIndexManager.FindInAABB(searchBounds, excludeObject: _animatedObject); // 应用排除列表过滤 nearbyObjects = nearbyObjects.Where(obj => !_excludedObjects.Contains(obj)); ``` ### 智能自动选中逻辑 ```csharp // 碰撞分析对话框中的智能选中条件 IsExcluded = h.CollisionCount > 100 || h.Percentage > 50 ``` ### 行点击高亮 ```csharp // 使用预计算碰撞结果高亮类别(紫色 #9C27B0) private const string PrecomputedHighlightCategory = ModelHighlightHelper.PrecomputeCollisionResultsCategory; // 点击行时高亮对应物体 ModelHighlightHelper.HighlightItems(PrecomputedHighlightCategory, items); ``` ### UI 绑定 ```csharp // ViewModel 属性 public ThreadSafeObservableCollection ExcludedObjects { get; } public string ExcludedObjectSummary { get; set; } public bool HasExcludedObjects { get; } // 命令 public ICommand AddExcludedObjectsFromSelectionCommand { get; } public ICommand ClearExcludedObjectsCommand { get; } public ICommand RemoveExcludedObjectCommand { get; } public ICommand HighlightAllExcludedObjectsCommand { get; } public ICommand ClearExcludedHighlightCommand { get; } ``` ### 数据库持久化 #### 数据库表结构 ```sql -- 排除对象表 CREATE TABLE ExcludedObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, RouteId TEXT, -- 关联路径ID(可为空表示全局排除) ModelIndex INTEGER NOT NULL, PathId TEXT NOT NULL, -- 格式: "模型索引:路径索引数组" DisplayName TEXT, ObjectName TEXT, ExcludedTime DATETIME DEFAULT CURRENT_TIMESTAMP, Reason TEXT, IsGlobal INTEGER DEFAULT 0 ); -- 碰撞报告与排除对象关联表 CREATE TABLE CollisionReportExcludedObjects ( Id INTEGER PRIMARY KEY AUTOINCREMENT, ReportId INTEGER NOT NULL, ExcludedObjectId INTEGER NOT NULL ); ``` #### 保存排除对象到数据库 ```csharp // 在生成碰撞报告时保存排除对象 public void SaveExcludedObjectsToDatabase(PathDatabase database, string routeId) { var records = new List(); foreach (var obj in _excludedObjects) { var record = new ExcludedObjectRecord { RouteId = routeId, ModelIndex = GetModelIndex(obj), PathId = GetModelItemPathId(obj), // 格式: "0:1,2,3" DisplayName = obj.DisplayName, ObjectName = GetModelItemObjectName(obj), ExcludedTime = DateTime.Now, Reason = "用户排除" }; records.Add(record); } database.SaveExcludedObjects(records); } ``` #### 从数据库加载排除对象 ```csharp // 根据PathId查找ModelItem private ModelItem FindModelItemByPathId(Document doc, string pathId) { // PathId格式: "模型索引:路径索引数组" // 例如: "0:1,2,3" 表示第0个模型的第1->2->3个节点 var parts = pathId.Split(':'); int modelIndex = int.Parse(parts[0]); var pathIndices = parts[1].Split(',').Select(int.Parse).ToArray(); var model = doc.Models[modelIndex]; ModelItem current = model.RootItem; foreach (var index in pathIndices) { var childrenList = current.Children.ToList(); current = childrenList[index]; } return current; } ``` --- ## 工作流程 ### 1. 动画生成流程 ``` 用户点击"生成动画" ↓ 预计算碰撞检测 ↓ 分析碰撞热点(>5次或>10%) ↓ 显示分析对话框(自动选中高频物体) ↓ 用户调整勾选/点击行查看高亮 ↓ 点击"继续生成动画" ↓ [如果有排除对象] 添加到排除列表 → 清除缓存 → 重新生成 [如果无排除对象] 直接继续 ``` ### 2. 排除列表管理流程 ``` 手工添加:选择物体 → 点击"从选择添加" → 添加到列表 分析添加:分析对话框勾选 → 点击"继续生成动画" → 合并到列表 删除单个:点击列表项的删除 → 清除高亮 → 同步到Manager 清除所有:点击"清除所有" → 清空列表 → 清除高亮 高亮显示:点击"全部高亮"/"清除高亮" ``` ### 3. 数据库持久化流程 ``` 生成碰撞报告 ↓ 保存排除对象到数据库(ExcludedObjects表) ↓ 关联排除对象到碰撞报告(CollisionReportExcludedObjects表) ↓ 查看碰撞历史时 ↓ 加载碰撞报告关联的排除对象列表 ↓ 显示"本次检测排除了X个对象" ``` --- ## 使用指南 ### 1. 通过分析对话框排除 1. 生成动画时自动触发预计算分析 2. 分析对话框显示高频碰撞物体(已自动选中>100次或>50%的) 3. 点击表格行可在3D视图中高亮对应物体(紫色) 4. 调整勾选状态后点击"继续生成动画" 5. 勾选的对象会被加入排除列表并重新生成 ### 2. 手工管理排除列表 1. 在动画控制面板的"检测排除对象"区域 2. 在模型中选择要排除的物体 3. 点击"从选择添加" 4. 已排除物体在碰撞检测中会被忽略 5. 可随时"全部高亮"查看或"清除所有"清空 --- ## 预期效果 ### 减少 ClashDetective 测试数量 - 典型场景:379 次碰撞 -> 排除地面后可能减少到 50 次以下 - 按每次测试 133ms 计算:50s -> 6.6s,节省 86% 时间 ### 提高结果质量 - 减少地面/楼板假阳性碰撞 - 让用户专注于真实障碍物 - 更准确的碰撞报告 --- ## 后续可能的改进方向 ### 智能建议增强 - [ ] 基于包围盒大小识别大型物体(>1000立方米) - [ ] 基于位置识别:位于路径下方的物体可能是地面 - [ ] 学习用户历史排除选择,优先建议 ### 碰撞类型分类 - [ ] 区分"疑似假阳性"(地面/楼板接触) - [ ] 区分"真实碰撞"(墙体/障碍物) ### 性能优化 - [ ] 增量预计算(仅重新计算受影响的帧) - [ ] 分析结果持久化到数据库 --- ## 注意事项 1. **排除列表生命周期**:当前为运行时管理,切换路径后保留,关闭文档后失效 2. **ModelItem比较**:必须使用 `ModelItemEquals` 方法,不能用 `InstanceGuid` 3. **缓存机制**:排除列表变更会触发 `SetExcludedObjectsAndClearCache` 清除动画缓存 4. **高亮颜色**: - 排除对象:绿色 (#4CAF50) - 预计算碰撞:紫色 (#9C27B0) - 手动目标:橙色 (#FFAA00)