321 lines
9.9 KiB
Markdown
321 lines
9.9 KiB
Markdown
# Human-in-the-Loop 碰撞优化功能 - 实现完成报告
|
||
|
||
## 当前状态(2026-02-09)
|
||
|
||
### 已完成功能
|
||
|
||
#### 第一阶段(基础分析)✅
|
||
|
||
1. **碰撞热点自动分析** - 预计算完成后自动分析碰撞结果
|
||
2. **自动高亮显示** - 自动高亮所有预计算碰撞结果(紫色 #9C27B0)
|
||
3. **分析对话框** - 显示详细的碰撞统计和建议排除选项
|
||
4. **光标修复** - 对话框显示时光标恢复正常
|
||
|
||
#### 第二阶段(排除列表集成)✅
|
||
|
||
1. **排除列表数据结构** - `PathAnimationManager` 中使用 `HashSet<ModelItem>`
|
||
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<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`。
|
||
|
||
### 预计算过滤点
|
||
|
||
```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<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; }
|
||
```
|
||
|
||
### 数据库持久化
|
||
|
||
#### 数据库表结构
|
||
|
||
```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<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);
|
||
}
|
||
```
|
||
|
||
#### 从数据库加载排除对象
|
||
|
||
```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)
|