9.9 KiB
9.9 KiB
Human-in-the-Loop 碰撞优化功能 - 实现完成报告
当前状态(2026-02-09)
已完成功能
第一阶段(基础分析)✅
- 碰撞热点自动分析 - 预计算完成后自动分析碰撞结果
- 自动高亮显示 - 自动高亮所有预计算碰撞结果(紫色 #9C27B0)
- 分析对话框 - 显示详细的碰撞统计和建议排除选项
- 光标修复 - 对话框显示时光标恢复正常
第二阶段(排除列表集成)✅
- 排除列表数据结构 -
PathAnimationManager中使用HashSet<ModelItem> - 管理方法 -
SetExcludedObjectsAndClearCache、AddExcludedObjects、ClearExcludedObjects - 预计算过滤 - 在
PrecomputeAnimationFrames()中应用排除列表过滤 - 对话框集成 - 分析结果自动合并到排除列表
- UI区域 - 新增"检测排除对象"区域,支持手工添加和显示排除列表
第三阶段(UI优化)✅
-
碰撞分析对话框增强
- 序号列显示
- 行点击自动高亮对应物体(紫色预计算风格)
- 候选碰撞数字黑色粗体显示
- 预计检测时间显示(xxx秒/xx.x分钟)
- 智能自动选中(碰撞>100次 或 占比>50%)
- 单一"继续生成动画"按钮(自动判断是否有排除对象)
-
排除列表UI完善
- 添加"清除高亮"按钮
- 删除列表项时自动清除高亮
- 使用
ModelItemEquals正确比较对象(修复 InstanceGuid 问题)
第四阶段(数据库持久化)✅
-
数据库表结构
ExcludedObjects- 存储排除对象(PathId、DisplayName、ModelIndex等)CollisionReportExcludedObjects- 碰撞报告与排除对象关联表ClashDetectiveExcludedObjects- ClashDetective结果与排除对象关联表
-
数据持久化
- 排除对象随碰撞报告一起保存到数据库
- 支持从数据库加载排除对象(通过PathId查找)
- 碰撞历史和报告中显示当时使用的排除对象列表
-
核心方法
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 |
添加排除列表相关的数据库表和方法 |
技术实现细节
排除列表存储与比较
// 使用 HashSet<ModelItem> 存储排除对象
private HashSet<ModelItem> _excludedObjects = new HashSet<ModelItem>();
// 正确的 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。
预计算过滤点
// 在 PrecomputeAnimationFrames 的循环中
var nearbyObjects = spatialIndexManager.FindInAABB(searchBounds, excludeObject: _animatedObject);
// 应用排除列表过滤
nearbyObjects = nearbyObjects.Where(obj => !_excludedObjects.Contains(obj));
智能自动选中逻辑
// 碰撞分析对话框中的智能选中条件
IsExcluded = h.CollisionCount > 100 || h.Percentage > 50
行点击高亮
// 使用预计算碰撞结果高亮类别(紫色 #9C27B0)
private const string PrecomputedHighlightCategory = ModelHighlightHelper.PrecomputeCollisionResultsCategory;
// 点击行时高亮对应物体
ModelHighlightHelper.HighlightItems(PrecomputedHighlightCategory, items);
UI 绑定
// ViewModel 属性
public ThreadSafeObservableCollection<ExcludedObjectViewModel> 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; }
数据库持久化
数据库表结构
-- 排除对象表
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
);
保存排除对象到数据库
// 在生成碰撞报告时保存排除对象
public void SaveExcludedObjectsToDatabase(PathDatabase database, string routeId)
{
var records = new List<ExcludedObjectRecord>();
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);
}
从数据库加载排除对象
// 根据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. 通过分析对话框排除
- 生成动画时自动触发预计算分析
- 分析对话框显示高频碰撞物体(已自动选中>100次或>50%的)
- 点击表格行可在3D视图中高亮对应物体(紫色)
- 调整勾选状态后点击"继续生成动画"
- 勾选的对象会被加入排除列表并重新生成
2. 手工管理排除列表
- 在动画控制面板的"检测排除对象"区域
- 在模型中选择要排除的物体
- 点击"从选择添加"
- 已排除物体在碰撞检测中会被忽略
- 可随时"全部高亮"查看或"清除所有"清空
预期效果
减少 ClashDetective 测试数量
- 典型场景:379 次碰撞 -> 排除地面后可能减少到 50 次以下
- 按每次测试 133ms 计算:50s -> 6.6s,节省 86% 时间
提高结果质量
- 减少地面/楼板假阳性碰撞
- 让用户专注于真实障碍物
- 更准确的碰撞报告
后续可能的改进方向
智能建议增强
- 基于包围盒大小识别大型物体(>1000立方米)
- 基于位置识别:位于路径下方的物体可能是地面
- 学习用户历史排除选择,优先建议
碰撞类型分类
- 区分"疑似假阳性"(地面/楼板接触)
- 区分"真实碰撞"(墙体/障碍物)
性能优化
- 增量预计算(仅重新计算受影响的帧)
- 分析结果持久化到数据库
注意事项
- 排除列表生命周期:当前为运行时管理,切换路径后保留,关闭文档后失效
- ModelItem比较:必须使用
ModelItemEquals方法,不能用InstanceGuid - 缓存机制:排除列表变更会触发
SetExcludedObjectsAndClearCache清除动画缓存 - 高亮颜色:
- 排除对象:绿色 (#4CAF50)
- 预计算碰撞:紫色 (#9C27B0)
- 手动目标:橙色 (#FFAA00)