11 KiB
11 KiB
剖面盒优化碰撞检测方案
概述
通过 Navisworks 剖面盒功能,只处理路径周围的对象,大幅提升大型模型碰撞检测性能。
预期性能提升
| 模型规模 | 优化前 | 优化后 | 提升倍数 |
|---|---|---|---|
| 10层建筑(全模型) | 80秒 | ~1秒 | 80x |
| 单层检测 | 80秒 | ~0.5秒 | 160x |
核心思路
- 路径范围分析 → 计算路径的3D包围盒
- 设置剖面盒 → 只保留路径周围一定范围内的模型
- 裁剪缓存构建 → 遍历模型时快速跳过剖面外对象
- ClashDetective检测 → 在裁剪后的空间内执行碰撞检测
技术实现
1. 剖面盒辅助类 (src/Utils/SectionClipHelper.cs)
提供剖面盒设置、查询、测试等基础功能。
关键实现:JSON 方式设置剖面盒
⚠️ 重要发现:Navisworks 的
ClipPlaneSet对象在用户手工打开剖面功能后会变为只读,直接修改属性会导致NotSupportedException: Object is Read-Only错误。解决方案:使用
View.TrySetClippingPlanes(string json)方法,通过 JSON 字符串设置剖面盒配置。
/// <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 方法,支持剖面过滤:
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 方法:
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 利用剖面盒二次过滤:
public IEnumerable<ModelItem> QueryVisibleItems()
{
// 先查询空间索引
var candidates = _spatialIndex.Query(_viewFrustum);
// 再用剖面盒过滤
return candidates.Where(item =>
!SectionClipHelper.IsClipBoxEnabled ||
SectionClipHelper.IntersectsClipBox(item.BoundingBox()));
}
API 使用说明
设置剖面盒
// 根据路径设置(推荐用于路径规划场景)
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();
测试点/包围盒
// 测试点是否在剖面内
bool inside = SectionClipHelper.IsPointInClipBox(point);
// 测试包围盒是否与剖面相交
bool intersects = SectionClipHelper.IntersectsClipBox(box);
// 获取当前剖面盒
BoundingBox3D clipBox;
if (SectionClipHelper.TryGetCurrentClipBox(out clipBox))
{
// 使用 clipBox 进行自定义过滤
}
统计对象数量(调试用)
// 输出剖面盒内/外对象数量到日志
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) |
| 内存占用分析 | ⏳ 待测 | 无显著增加 |
已知问题与解决方案
问题1:Object is Read-Only 错误
现象:用户手工打开剖面功能后,调用 clipPlanes.Box = box 抛出异常。
原因:Navisworks 的 ClipPlaneSet 对象在手工操作后变为只读。
解决:使用 JSON 字符串方式设置剖面盒(见上文技术实现)。
问题2:路径跨越多层时优化效果降低
现象:垂直跨越多个楼层的长路径,剖面盒范围大,过滤率下降。
建议:
- 分段设置剖面盒(每层单独检测)
- 使用多个剖面盒(Navisworks 支持最多 6 个剖面)
- 动态调整剖面盒大小
问题3:坐标系单位转换
注意:SectionClipHelper 的输入参数使用米为单位,内部自动转换为模型单位。
// 正确:使用米作为输入
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:路径规划后自动设置剖面盒
// 在 PathPlanningManager 中
public void GeneratePath(PathRequest request)
{
// ... 路径计算 ...
// 自动设置剖面盒
if (pathPoints != null && pathPoints.Count > 0)
{
SectionClipHelper.SetClipBoxByPath(
pathPoints,
marginMeters: 3.0, // 可配置
heightMarginMeters: 2.0);
}
// ... 后续处理 ...
}
步骤2:碰撞检测前应用过滤
// 在 ClashDetectiveIntegration 中
public async Task<CollisionResult> PerformCollisionDetectionAsync()
{
// 1. 构建缓存时自动应用剖面过滤
BuildNonHidddenGeometryItemsCache();
// 2. 执行检测(只处理剖面内对象)
var result = await RunClashDetectionAsync();
// 3. 可选:检测完成后清除剖面盒
// SectionClipHelper.ClearClipBox();
return result;
}
步骤3:UI 交互控制
<!-- 在路径编辑界面添加剖面盒控制 -->
<CheckBox Content="启用剖面盒优化" IsChecked="{Binding EnableSectionClip}" />
<Button Content="设置剖面盒到当前路径" Command="{Binding SetClipBoxCommand}" />
<Button Content="清除剖面盒" Command="{Binding ClearClipBoxCommand}" />
后续优化方向
- 多剖面盒支持:利用 Navisworks 支持 6 个剖面的特性,处理复杂路径
- 动态剖面盒:动画播放时动态调整剖面盒位置
- 智能边距计算:根据车辆尺寸自动计算合适的边距
- 剖面盒可视化:在 3D 视图中显示剖面盒边界
参考文档
- Navisworks API:
doc/navisworks_api/NET/documentation/NetAPIHtml/html/T_Autodesk_Navisworks_Api_ClipPlaneSet.htm View.SetClippingPlanes- JSON 方式设置剖面盒View.TrySetClippingPlanes- 安全设置剖面盒(返回 bool)ClipPlaneSet属性(只读场景下慎用)