优化剖面盒导,提高性能,解决漏算的问题

This commit is contained in:
tian 2026-03-12 09:40:14 +08:00
parent e495ea4620
commit f596935c76
5 changed files with 394 additions and 62 deletions

View File

@ -334,6 +334,7 @@
<Compile Include="src\Utils\CoordinateSystem\CoordinateSystemManager.cs" />
<Compile Include="src\Utils\ViewpointHelper.cs" />
<Compile Include="src\Utils\VisibilityHelper.cs" />
<Compile Include="src\Utils\NwdExportHelper.cs" />
<Compile Include="src\Utils\ModelItemTransformHelper.cs" />
<Compile Include="src\Utils\CachedTriangle3D.cs" />
<Compile Include="src\Utils\PathHelper.cs" />

View File

@ -6,6 +6,7 @@ using System.Windows.Forms;
using Autodesk.Navisworks.Api;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NavisworksTransport.Utils;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.Core
@ -80,21 +81,21 @@ namespace NavisworksTransport.Core
try
{
// 4. 获取剖面盒内对象
var objectsInSectionBox = GetObjectsInSectionBox(document, sectionBoxBounds);
// 4. 【优化】一次遍历同时获取剖面盒内对象和需要隐藏的节点
var traversalResult = GetObjectsAndHiddenItems(document, sectionBoxBounds);
if (exportAsNwd)
{
// 导出为NWD格式
return ExportToNwd(document, objectsInSectionBox, filePath);
// 导出为NWD格式(使用预计算结果)
return ExportToNwd(document, traversalResult, filePath);
}
else
{
// 导出为JSON格式原有逻辑
var exportData = BuildExportData(sectionBoxBounds, objectsInSectionBox, document);
var exportData = BuildExportData(sectionBoxBounds, traversalResult.ObjectsInBox, document);
string json = JsonConvert.SerializeObject(exportData, Formatting.Indented);
File.WriteAllText(filePath, json);
LogManager.Info($"剖面盒导出完成: {objectsInSectionBox.Count} 个对象 -> {filePath}");
LogManager.Info($"剖面盒导出完成: {traversalResult.ObjectsInBox.Count} 个对象 -> {filePath}");
return filePath;
}
}
@ -142,37 +143,56 @@ namespace NavisworksTransport.Core
}
/// <summary>
/// 获取剖面盒内的所有对象(公开方法供外部调用)
/// 剖面盒遍历结果:同时包含盒内对象和需要隐藏的节点
/// </summary>
public List<ModelItem> GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
public class SectionBoxTraversalResult
{
var result = new List<ModelItem>();
/// <summary>
/// 剖面盒内的对象
/// </summary>
public List<ModelItem> ObjectsInBox { get; set; } = new List<ModelItem>();
// 手动遍历所有模型
/// <summary>
/// 需要隐藏的节点(子树完全不在盒内的根节点)
/// </summary>
public ModelItemCollection ItemsToHide { get; set; } = new ModelItemCollection();
}
/// <summary>
/// 【优化】一次遍历同时获取剖面盒内对象和可见节点集合
/// 然后使用原来的兄弟节点算法计算隐藏节点
/// </summary>
public SectionBoxTraversalResult GetObjectsAndHiddenItems(Document document, BoundingBox3D sectionBox)
{
var result = new SectionBoxTraversalResult();
var visibleSet = new HashSet<ModelItem>(); // 收集需要保留的节点(对象+祖先)
// 第一遍:遍历整个模型树,收集盒内对象和可见节点
foreach (var model in document.Models)
{
var rootItem = model.RootItem;
if (rootItem != null)
{
TraverseModelItem(rootItem, sectionBox, result);
TraverseAndCollect(model.RootItem, sectionBox, result.ObjectsInBox, visibleSet);
}
}
// 第二遍(局部):使用原来的兄弟节点算法计算隐藏节点
// 只遍历 uniqueParents 的子节点,不是整个模型
CalculateHiddenItems(visibleSet, result.ItemsToHide);
return result;
}
/// <summary>
/// 递归遍历模型项 - 剪枝策略:遇到几何体就停止向下遍历
/// 递归遍历收集盒内对象和可见节点
/// </summary>
public void TraverseModelItem(ModelItem item, BoundingBox3D sectionBox, List<ModelItem> result)
private void TraverseAndCollect(ModelItem item, BoundingBox3D sectionBox,
List<ModelItem> objectsInBox, HashSet<ModelItem> visibleSet)
{
if (item == null) return;
if (item == null || item.IsHidden) return;
// 如果节点隐藏,跳过
if (item.IsHidden)
return;
// 如果有几何体信息,检查并添加,然后停止遍历该分支(剪枝)
// 如果有几何体,检查是否在盒内
if (item.HasGeometry)
{
var bbox = item.BoundingBox();
@ -183,22 +203,74 @@ namespace NavisworksTransport.Core
double sizeZ = bbox.Max.Z - bbox.Min.Z;
bool hasVolume = sizeX > 0.0001 && sizeY > 0.0001 && sizeZ > 0.0001;
// 有体积且与剖面盒相交才添加
// 有体积且与剖面盒相交 → 是目标对象
if (hasVolume && BoundingBoxesIntersect(bbox, sectionBox))
{
result.Add(item);
objectsInBox.Add(item);
// 将该对象及其所有祖先标记为可见
foreach (var ancestor in item.AncestorsAndSelf)
{
visibleSet.Add(ancestor);
}
}
// 找到几何体,停止向下遍历(无论是否相交)
// 找到几何体,停止向下遍历(原逻辑)
return;
}
// 没有几何体,继续遍历子节点
foreach (var child in item.Children)
{
TraverseModelItem(child, sectionBox, result);
TraverseAndCollect(child, sectionBox, objectsInBox, visibleSet);
}
}
/// <summary>
/// 【使用原来的兄弟节点算法】计算需要隐藏的节点
/// </summary>
private void CalculateHiddenItems(HashSet<ModelItem> visibleSet, ModelItemCollection itemsToHide)
{
// 1. 收集唯一的父节点
var uniqueParents = new HashSet<ModelItem>();
foreach (var visibleItem in visibleSet)
{
if (visibleItem.Parent != null)
{
uniqueParents.Add(visibleItem.Parent);
}
}
// 2. 收集这些父节点的所有子节点(兄弟节点)
var hiddenSet = new HashSet<ModelItem>();
foreach (var parent in uniqueParents)
{
foreach (var sibling in parent.Children)
{
hiddenSet.Add(sibling);
}
}
// 3. 从隐藏集合中移除可见项
hiddenSet.ExceptWith(visibleSet);
// 4. 转换为 ModelItemCollection
foreach (var item in hiddenSet)
{
itemsToHide.Add(item);
}
}
/// <summary>
/// 获取剖面盒内的所有对象(公开方法供外部调用)
/// 【已优化】内部使用 GetObjectsAndHiddenItems 实现一次遍历
/// </summary>
public List<ModelItem> GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
{
var result = GetObjectsAndHiddenItems(document, sectionBox);
return result.ObjectsInBox;
}
/// <summary>
/// 检查两个包围盒是否相交
/// </summary>
@ -338,36 +410,33 @@ namespace NavisworksTransport.Core
}
/// <summary>
/// 导出为NWD文件公开方法供外部调用
/// 导出为NWD文件委托给 NwdExportHelper
/// 适用于通用场景(如保存选择项)
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <param name="objects">要导出的对象列表</param>
/// <param name="filePath">导出文件路径</param>
/// <returns>导出文件路径失败返回null</returns>
public string ExportToNwd(Document document, List<ModelItem> objects, string filePath)
{
// 必须在主线程执行Navisworks API操作
string result = null;
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
// 准备要显示的项目
var itemsToShow = new ModelItemCollection();
foreach (var item in objects)
itemsToShow.Add(item);
return NwdExportHelper.ExportToNwd(objects, filePath);
}
// 在隔离模式下执行导出,自动保存和恢复可见性
result = VisibilityHelper.ExecuteInIsolation(itemsToShow, () =>
{
// 创建导出选项
var exportOptions = new NwdExportOptions
{
ExcludeHiddenItems = true
};
// 执行导出
document.ExportToNwd(filePath, exportOptions);
LogManager.Info($"[SectionBoxExporter] NWD导出完成: {objects.Count} 个对象 -> {filePath}");
return filePath;
});
});
return result;
/// <summary>
/// 【优化】导出为NWD文件使用预计算的遍历结果
/// 适用于剖面盒导出场景,避免重复遍历模型树
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <param name="traversalResult">预计算的遍历结果(包含对象和隐藏节点)</param>
/// <param name="filePath">导出文件路径</param>
/// <returns>导出文件路径失败返回null</returns>
public string ExportToNwd(Document document, SectionBoxTraversalResult traversalResult, string filePath)
{
return NwdExportHelper.ExportToNwdWithPrecomputedHiddenItems(
traversalResult.ObjectsInBox,
traversalResult.ItemsToHide,
filePath,
"剖面盒导出");
}
/// <summary>

View File

@ -1866,12 +1866,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
bool exportResult = false;
string errorMessage = "";
// 使用 SectionBoxExporter 进行导出
var exporter = new SectionBoxExporter();
if (exportAsJson)
{
// JSON导出
// JSON导出使用 SectionBoxExporter
var exporter = new SectionBoxExporter();
await Task.Run(() =>
{
var result = exporter.ExportToJson(document, items, saveFilePath);
@ -1880,10 +1878,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
else
{
// NWD导出
// NWD导出(使用 NwdExportHelper
await Task.Run(() =>
{
var result = exporter.ExportToNwd(document, items, saveFilePath);
var result = NwdExportHelper.ExportToNwd(items, saveFilePath);
exportResult = !string.IsNullOrEmpty(result);
});
}
@ -1985,29 +1983,30 @@ namespace NavisworksTransport.UI.WPF.ViewModels
new Point3D(box.Max.X, box.Max.Y, box.Max.Z)
);
// 异步获取剖面盒内的对象
// 【优化】异步获取剖面盒内的对象和需要隐藏的节点(一次遍历完成)
CurrentOperationText = "正在查找剖面盒内的对象...";
List<ModelItem> objectsInSectionBox = null;
SectionBoxExporter.SectionBoxTraversalResult traversalResult = null;
await Task.Run(() =>
{
objectsInSectionBox = GetObjectsInSectionBox(document, sectionBoxBounds);
var exporter = new SectionBoxExporter();
traversalResult = exporter.GetObjectsAndHiddenItems(document, sectionBoxBounds);
});
if (objectsInSectionBox.Count == 0)
if (traversalResult.ObjectsInBox.Count == 0)
{
MessageBox.Show("剖面盒内没有找到对象!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
LogManager.Info($"[LayerManagementViewModel] 剖面盒内找到 {objectsInSectionBox.Count} 个对象");
LogManager.Info($"[LayerManagementViewModel] 剖面盒内找到 {traversalResult.ObjectsInBox.Count} 个对象,可隐藏 {traversalResult.ItemsToHide.Count} 个节点");
// 生成默认文件名
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string defaultFileName = $"SectionBox_Export_{timestamp}";
// 调用通用导出方法
await ExportItemsToNwdAsync(objectsInSectionBox, "导出剖面盒", defaultFileName, false);
// 【优化】使用预计算结果的导出方法
await ExportSectionBoxToNwdAsync(traversalResult, "导出剖面盒", defaultFileName);
}
catch (Exception ex)
{
@ -2030,6 +2029,99 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return exporter.GetObjectsInSectionBox(document, sectionBox);
}
/// <summary>
/// 【优化】导出剖面盒到NWD使用预计算的遍历结果
/// 避免重复遍历模型树
/// </summary>
private async Task ExportSectionBoxToNwdAsync(
SectionBoxExporter.SectionBoxTraversalResult traversalResult,
string dialogTitle,
string defaultFileName)
{
if (traversalResult?.ObjectsInBox == null || traversalResult.ObjectsInBox.Count == 0)
{
MessageBox.Show("没有要导出的对象", "导出提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
try
{
LogManager.Info($"[LayerManagementViewModel] 开始导出 {traversalResult.ObjectsInBox.Count} 个对象,预计算隐藏节点 {traversalResult.ItemsToHide.Count} 个");
IsProcessing = true;
CurrentOperationText = "准备导出...";
var document = Autodesk.Navisworks.Api.Application.ActiveDocument;
// 获取保存路径
string saveFilePath = null;
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
var saveDialog = new Microsoft.Win32.SaveFileDialog
{
Title = dialogTitle,
Filter = "Navisworks文件 (*.nwd)|*.nwd",
DefaultExt = "nwd",
FileName = defaultFileName
};
if (saveDialog.ShowDialog() == true)
{
saveFilePath = saveDialog.FileName;
}
});
if (string.IsNullOrEmpty(saveFilePath))
{
return;
}
bool exportResult = false;
string errorMessage = "";
// 【优化】使用预计算结果直接导出,无需再次计算隐藏节点
await Task.Run(() =>
{
try
{
var result = NwdExportHelper.ExportToNwdWithPrecomputedHiddenItems(
traversalResult.ObjectsInBox,
traversalResult.ItemsToHide,
saveFilePath,
"剖面盒导出");
exportResult = !string.IsNullOrEmpty(result);
}
catch (Exception ex)
{
errorMessage = ex.Message;
LogManager.Error($"[LayerManagementViewModel] 导出失败: {ex.Message}");
}
});
// 显示结果
if (exportResult && File.Exists(saveFilePath))
{
var fileInfo = new FileInfo(saveFilePath);
MessageBox.Show(
$"NWD导出成功\n\n文件: {saveFilePath}\n大小: {fileInfo.Length / 1024} KB\n对象数: {traversalResult.ObjectsInBox.Count}",
"导出结果", MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
MessageBox.Show($"NWD导出失败: {errorMessage}", "导出错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
catch (Exception ex)
{
LogManager.Error($"[LayerManagementViewModel] 导出过程异常: {ex.Message}", ex);
MessageBox.Show($"导出过程异常: {ex.Message}", "导出错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsProcessing = false;
CurrentOperationText = "";
}
}
/// <summary>
/// 测试ExportToNwd API - 专门的导出API
/// </summary>

View File

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Windows;
using Autodesk.Navisworks.Api;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.Utils
{
/// <summary>
/// NWD 文件导出工具类
/// 提供通用的 NWD 导出功能,与具体业务场景解耦
/// </summary>
public static class NwdExportHelper
{
/// <summary>
/// 导出指定对象到 NWD 文件
/// 使用 ExecuteInIsolation 自动计算并隐藏无关节点
/// </summary>
/// <param name="objects">要导出的对象列表</param>
/// <param name="filePath">导出文件路径</param>
/// <param name="excludeHiddenItems">是否排除隐藏项,默认 true</param>
/// <returns>导出文件路径,失败返回 null</returns>
public static string ExportToNwd(List<ModelItem> objects, string filePath, bool excludeHiddenItems = true)
{
if (objects == null || objects.Count == 0)
throw new ArgumentException("对象列表不能为空", nameof(objects));
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
var document = NavisApplication.ActiveDocument;
if (document == null)
{
LogManager.Warning("[NwdExportHelper] 没有活动文档");
return null;
}
// 必须在主线程执行
string result = null;
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
// 准备要显示的项目
var itemsToShow = new ModelItemCollection();
foreach (var item in objects)
itemsToShow.Add(item);
// 在隔离模式下执行导出
result = VisibilityHelper.ExecuteInIsolation(itemsToShow, () =>
{
var exportOptions = new NwdExportOptions
{
ExcludeHiddenItems = excludeHiddenItems
};
document.ExportToNwd(filePath, exportOptions);
LogManager.Info($"[NwdExportHelper] NWD导出完成: {objects.Count} 个对象 -> {filePath}");
return filePath;
});
});
return result;
}
/// <summary>
/// 使用预计算的隐藏节点列表导出到 NWD 文件
/// 适用于已经遍历并计算好隐藏节点的场景(如剖面盒导出)
/// </summary>
/// <param name="objects">要导出的对象列表</param>
/// <param name="itemsToHide">预计算的隐藏节点集合</param>
/// <param name="filePath">导出文件路径</param>
/// <param name="operationName">操作名称(用于日志)</param>
/// <returns>导出文件路径,失败返回 null</returns>
public static string ExportToNwdWithPrecomputedHiddenItems(
List<ModelItem> objects,
ModelItemCollection itemsToHide,
string filePath,
string operationName = "NWD导出")
{
if (objects == null || objects.Count == 0)
throw new ArgumentException("对象列表不能为空", nameof(objects));
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
var document = NavisApplication.ActiveDocument;
if (document == null)
{
LogManager.Warning($"[NwdExportHelper] {operationName}失败:没有活动文档");
return null;
}
// 必须在主线程执行
string result = null;
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
// 使用预计算隐藏节点的方法
result = VisibilityHelper.ExecuteWithPrecomputedHiddenItems(
itemsToHide,
() =>
{
var exportOptions = new NwdExportOptions
{
ExcludeHiddenItems = true
};
document.ExportToNwd(filePath, exportOptions);
LogManager.Info($"[NwdExportHelper] {operationName}完成: {objects.Count} 个对象,隐藏 {itemsToHide?.Count ?? 0} 个节点 -> {filePath}");
return filePath;
},
operationName
);
});
return result;
}
}
}

View File

@ -244,6 +244,60 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 【新增】应用预计算的隐藏节点列表,并保存当前状态用于后续恢复
/// 用于优化场景:遍历和计算隐藏节点已经完成,只需直接应用
/// </summary>
/// <param name="itemsToHide">预计算的隐藏节点集合</param>
/// <param name="action">在应用隐藏后执行的操作</param>
/// <param name="operationName">操作名称(用于日志)</param>
/// <returns>操作结果</returns>
public static T ExecuteWithPrecomputedHiddenItems<T>(ModelItemCollection itemsToHide, Func<T> action, string operationName = "预计算隐藏")
{
var document = NavisApplication.ActiveDocument;
if (document == null)
{
LogManager.Warning($"[VisibilityHelper] {operationName}失败:没有活动文档");
return default(T);
}
// 保存当前状态
var savedState = SaveVisibilityStateInternal(document);
try
{
// 1. 全部显示
document.Models.ResetAllHidden();
// 2. 直接应用预计算的隐藏节点
if (itemsToHide != null && itemsToHide.Count > 0)
{
document.Models.SetHidden(itemsToHide, true);
LogManager.Debug($"[VisibilityHelper] {operationName}:应用 {itemsToHide.Count} 个预计算隐藏节点");
}
// 3. 执行操作
return action.Invoke();
}
finally
{
// 4. 恢复状态
RestoreVisibilityStateInternal(document, savedState);
}
}
/// <summary>
/// 【新增】应用预计算的隐藏节点列表(无返回值版本)
/// </summary>
public static void ExecuteWithPrecomputedHiddenItems(ModelItemCollection itemsToHide, Action action, string operationName = "预计算隐藏")
{
ExecuteWithPrecomputedHiddenItems<object>(itemsToHide, () =>
{
action?.Invoke();
return null;
}, operationName);
}
/// <summary>
/// 在隔离模式下执行操作,自动恢复可见性
/// </summary>