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

11 KiB
Raw Permalink Blame History

剖面盒优化碰撞检测方案

概述

通过 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 字符串设置剖面盒配置。

/// <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)
内存占用分析 待测 无显著增加

已知问题与解决方案

问题1Object is Read-Only 错误

现象:用户手工打开剖面功能后,调用 clipPlanes.Box = box 抛出异常。

原因Navisworks 的 ClipPlaneSet 对象在手工操作后变为只读。

解决:使用 JSON 字符串方式设置剖面盒(见上文技术实现)。

问题2路径跨越多层时优化效果降低

现象:垂直跨越多个楼层的长路径,剖面盒范围大,过滤率下降。

建议

  1. 分段设置剖面盒(每层单独检测)
  2. 使用多个剖面盒Navisworks 支持最多 6 个剖面)
  3. 动态调整剖面盒大小

问题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;
}

步骤3UI 交互控制

<!-- 在路径编辑界面添加剖面盒控制 -->
<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 属性(只读场景下慎用)