NavisworksTransport/doc/working/section-clip-optimization-plan.md

400 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 剖面盒优化碰撞检测方案
## 概述
通过 Navisworks 剖面盒功能,只处理路径周围的对象,大幅提升大型模型碰撞检测性能。
## 预期性能提升
| 模型规模 | 优化前 | 优化后 | 提升倍数 |
|---------|--------|--------|---------|
| 10层建筑全模型 | 80秒 | ~1秒 | **80x** |
| 单层检测 | 80秒 | ~0.5秒 | **160x** |
## 核心思路
1. **路径范围分析** → 计算路径的3D包围盒
2. **设置剖面盒** → 只保留路径周围一定范围内的模型
3. **裁剪缓存构建** → 遍历模型时快速跳过剖面外对象
4. **ClashDetective检测** → 在裁剪后的空间内执行碰撞检测
---
## 技术实现
### 1. 剖面盒辅助类 (`src/Utils/SectionClipHelper.cs`)
提供剖面盒设置、查询、测试等基础功能。
#### 关键实现JSON 方式设置剖面盒
> ⚠️ **重要发现**Navisworks 的 `ClipPlaneSet` 对象在用户手工打开剖面功能后会变为**只读**,直接修改属性会导致 `NotSupportedException: Object is Read-Only` 错误。
>
> **解决方案**:使用 `View.TrySetClippingPlanes(string json)` 方法,通过 JSON 字符串设置剖面盒配置。
```csharp
/// <summary>
/// 构建 ClipPlaneSet 的 JSON 字符串
/// </summary>
private static string BuildClipPlaneSetJson(BoundingBox3D box, bool enabled)
{
// Navisworks ClipPlaneSet JSON 格式:
// {
// "Type": "ClipPlaneSet",
// "Version": 1,
// "OrientedBox": {
// "Type": "OrientedBox3D",
// "Version": 1,
// "Box": [[minX, minY, minZ], [maxX, maxY, maxZ]],
// "Rotation": [0, 0, 0]
// },
// "Enabled": true/false
// }
return string.Format(
"{{\"Type\":\"ClipPlaneSet\",\"Version\":1," +
"\"OrientedBox\":{{\"Type\":\"OrientedBox3D\",\"Version\":1," +
"\"Box\":[[{0},{1},{2}],[{3},{4},{5}]]," +
"\"Rotation\":[0,0,0]}},\"Enabled\":{6}}}",
box.Min.X.ToString("G17"),
box.Min.Y.ToString("G17"),
box.Min.Z.ToString("G17"),
box.Max.X.ToString("G17"),
box.Max.Y.ToString("G17"),
box.Max.Z.ToString("G17"),
enabled.ToString().ToLowerInvariant());
}
/// <summary>
/// 应用剖面盒到视口
/// </summary>
private static void ApplyClipBox(BoundingBox3D box)
{
string json = BuildClipPlaneSetJson(box, true);
var view = Application.ActiveDocument.ActiveView;
// 使用 TrySetClippingPlanes 避免异常
bool success = view.TrySetClippingPlanes(json);
if (!success)
{
// 备选方案:使用 SetClippingPlanes可能抛出异常
view.SetClippingPlanes(json);
}
}
```
#### API 方法列表
| 方法 | 功能 | 参数 |
|------|------|------|
| `SetClipBoxByPath` | 根据路径点列表设置剖面盒 | `pathPoints`, `marginMeters`, `heightMarginMeters` |
| `SetClipBoxByPoint` | 根据中心点设置剖面盒 | `centerPoint`, `rangeMeters`, `heightRangeMeters` |
| `SetClipBoxByFloor` | 根据楼层设置剖面盒 | `floorItem`, `marginMeters` |
| `ClearClipBox` | 清除剖面盒 | - |
| `IsPointInClipBox` | 测试点是否在剖面盒内 | `point` |
| `IntersectsClipBox` | 测试包围盒是否与剖面盒相交 | `box` |
| `CountObjectsInClipBox` | 统计剖面盒内/外对象数量 | - |
---
### 2. 几何缓存优化 (`Core/Collision/ClashDetectiveIntegration.cs`)
修改 `BuildNonHidddenGeometryItemsCache` 方法,支持剖面过滤:
```csharp
private void BuildNonHidddenGeometryItemsCache()
{
// ... 原有代码 ...
// 获取当前剖面盒(如果启用)
BoundingBox3D clipBox;
bool hasClipBox = SectionClipHelper.TryGetCurrentClipBox(out clipBox);
// 遍历模型树
var stack = new Stack<ModelItem>();
foreach (var model in _document.Models)
{
stack.Push(model.RootItem);
}
while (stack.Count > 0)
{
var item = stack.Pop();
// 剖面盒过滤:如果对象在剖面盒外,整棵子树跳过
if (hasClipBox)
{
BoundingBox3D itemBox = item.BoundingBox();
if (!clipBox.Intersects(itemBox))
{
continue; // 剪枝:跳过此节点及其所有子节点
}
}
// ... 原有缓存逻辑 ...
}
}
```
---
### 3. 通道缓存优化
修改 `BuildChannelObjectsCache` 方法:
```csharp
private void BuildChannelObjectsCache()
{
// ... 原有代码 ...
// 获取当前剖面盒
BoundingBox3D clipBox;
bool hasClipBox = SectionClipHelper.TryGetCurrentClipBox(out clipBox);
foreach (var channel in channels)
{
// 剖面盒过滤
if (hasClipBox)
{
BoundingBox3D channelBox = channel.BoundingBox();
if (!clipBox.Intersects(channelBox))
{
continue; // 跳过剖面外的通道
}
}
// ... 原有通道缓存逻辑 ...
}
}
```
---
### 4. 空间索引优化(可选)
`SpatialIndexManager` 利用剖面盒二次过滤:
```csharp
public IEnumerable<ModelItem> QueryVisibleItems()
{
// 先查询空间索引
var candidates = _spatialIndex.Query(_viewFrustum);
// 再用剖面盒过滤
return candidates.Where(item =>
!SectionClipHelper.IsClipBoxEnabled ||
SectionClipHelper.IntersectsClipBox(item.BoundingBox()));
}
```
---
## API 使用说明
### 设置剖面盒
```csharp
// 根据路径设置(推荐用于路径规划场景)
SectionClipHelper.SetClipBoxByPath(
pathPoints,
marginMeters: 3.0, // 路径周围水平边距
heightMarginMeters: 2.0 // 上下高度边距
);
// 根据点设置(推荐用于吊装路径单点场景)
SectionClipHelper.SetClipBoxByPoint(
centerPoint,
rangeMeters: 10.0, // 水平范围
heightRangeMeters: 5.0 // 高度范围
);
// 根据楼层设置(推荐用于单层检测)
SectionClipHelper.SetClipBoxByFloor(
floorItem,
marginMeters: 1.0 // 楼层周围边距
);
// 清除剖面盒(恢复全模型显示)
SectionClipHelper.ClearClipBox();
```
### 测试点/包围盒
```csharp
// 测试点是否在剖面内
bool inside = SectionClipHelper.IsPointInClipBox(point);
// 测试包围盒是否与剖面相交
bool intersects = SectionClipHelper.IntersectsClipBox(box);
// 获取当前剖面盒
BoundingBox3D clipBox;
if (SectionClipHelper.TryGetCurrentClipBox(out clipBox))
{
// 使用 clipBox 进行自定义过滤
}
```
### 统计对象数量(调试用)
```csharp
// 输出剖面盒内/外对象数量到日志
SectionClipHelper.CountObjectsInClipBox(
out int total,
out int inside,
out int outside);
LogManager.Info($"过滤率: {(outside * 100.0 / total):F1}%");
```
---
## 测试结果
### 基础功能测试(✅ 已完成)
**测试场景**10层建筑模型在模型中心设置 10m x 10m x 5m 剖面盒
| 指标 | 数值 | 说明 |
|------|------|------|
| 总对象数 | 848 | 模型总对象 |
| 剖面盒内 | 10 | 需要检测的对象 |
| 剖面盒外 | 838 | 可跳过的对象 |
| **过滤率** | **98.8%** | 性能提升关键指标 |
**点检测测试**
- 中心点 (-108.50, -2.94, 27.24): `True`
- 远点 (-8.50, 97.06, 127.24): `False`
### 性能测试计划
| 测试项 | 状态 | 预期结果 |
|--------|------|----------|
| 10层建筑全模型 | ⏳ 待测 | 80秒 → ~1秒 (80x) |
| 单层检测 | ⏳ 待测 | 80秒 → ~0.5秒 (160x) |
| 内存占用分析 | ⏳ 待测 | 无显著增加 |
---
## 已知问题与解决方案
### 问题1Object is Read-Only 错误
**现象**:用户手工打开剖面功能后,调用 `clipPlanes.Box = box` 抛出异常。
**原因**Navisworks 的 `ClipPlaneSet` 对象在手工操作后变为只读。
**解决**:使用 JSON 字符串方式设置剖面盒(见上文技术实现)。
### 问题2路径跨越多层时优化效果降低
**现象**:垂直跨越多个楼层的长路径,剖面盒范围大,过滤率下降。
**建议**
1. 分段设置剖面盒(每层单独检测)
2. 使用多个剖面盒Navisworks 支持最多 6 个剖面)
3. 动态调整剖面盒大小
### 问题3坐标系单位转换
**注意**`SectionClipHelper` 的输入参数使用**米**为单位,内部自动转换为模型单位。
```csharp
// 正确:使用米作为输入
SectionClipHelper.SetClipBoxByPoint(centerPoint, rangeMeters: 10.0);
// 内部转换逻辑
double factor = UnitsConverter.GetMetersToUnitsConversionFactor(document.Units);
double rangeInModelUnits = rangeMeters * factor;
```
---
## 文件变更清单
### 新增文件
| 文件路径 | 说明 |
|----------|------|
| `src/Utils/SectionClipHelper.cs` | 剖面盒辅助类JSON 方式设置) |
### 修改文件
| 文件路径 | 修改内容 |
|----------|----------|
| `src/Core/Collision/ClashDetectiveIntegration.cs` | 添加剖面过滤支持到缓存构建 |
| `src/UI/WPF/Views/SystemManagementView.xaml` | 添加剖面盒测试按钮 |
| `src/UI/WPF/ViewModels/SystemManagementViewModel.cs` | 添加 `ExecuteTestSectionClip()` 方法 |
---
## 集成到碰撞检测流程
### 步骤1路径规划后自动设置剖面盒
```csharp
// 在 PathPlanningManager 中
public void GeneratePath(PathRequest request)
{
// ... 路径计算 ...
// 自动设置剖面盒
if (pathPoints != null && pathPoints.Count > 0)
{
SectionClipHelper.SetClipBoxByPath(
pathPoints,
marginMeters: 3.0, // 可配置
heightMarginMeters: 2.0);
}
// ... 后续处理 ...
}
```
### 步骤2碰撞检测前应用过滤
```csharp
// 在 ClashDetectiveIntegration 中
public async Task<CollisionResult> PerformCollisionDetectionAsync()
{
// 1. 构建缓存时自动应用剖面过滤
BuildNonHidddenGeometryItemsCache();
// 2. 执行检测(只处理剖面内对象)
var result = await RunClashDetectionAsync();
// 3. 可选:检测完成后清除剖面盒
// SectionClipHelper.ClearClipBox();
return result;
}
```
### 步骤3UI 交互控制
```xml
<!-- 在路径编辑界面添加剖面盒控制 -->
<CheckBox Content="启用剖面盒优化" IsChecked="{Binding EnableSectionClip}" />
<Button Content="设置剖面盒到当前路径" Command="{Binding SetClipBoxCommand}" />
<Button Content="清除剖面盒" Command="{Binding ClearClipBoxCommand}" />
```
---
## 后续优化方向
1. **多剖面盒支持**:利用 Navisworks 支持 6 个剖面的特性,处理复杂路径
2. **动态剖面盒**:动画播放时动态调整剖面盒位置
3. **智能边距计算**:根据车辆尺寸自动计算合适的边距
4. **剖面盒可视化**:在 3D 视图中显示剖面盒边界
---
## 参考文档
- Navisworks API: `doc/navisworks_api/NET/documentation/NetAPIHtml/html/T_Autodesk_Navisworks_Api_ClipPlaneSet.htm`
- `View.SetClippingPlanes` - JSON 方式设置剖面盒
- `View.TrySetClippingPlanes` - 安全设置剖面盒(返回 bool
- `ClipPlaneSet` 属性(只读场景下慎用)