From f596935c761a7cb600983288656355fdd48293b2 Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Thu, 12 Mar 2026 09:40:14 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=89=96=E9=9D=A2=E7=9B=92?=
=?UTF-8?q?=E5=AF=BC=EF=BC=8C=E6=8F=90=E9=AB=98=E6=80=A7=E8=83=BD=EF=BC=8C?=
=?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=BC=8F=E7=AE=97=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
TransportPlugin.csproj | 1 +
src/Core/SectionBoxExporter.cs | 167 +++++++++++++-----
.../ViewModels/LayerManagementViewModel.cs | 118 +++++++++++--
src/Utils/NwdExportHelper.cs | 116 ++++++++++++
src/Utils/VisibilityHelper.cs | 54 ++++++
5 files changed, 394 insertions(+), 62 deletions(-)
create mode 100644 src/Utils/NwdExportHelper.cs
diff --git a/TransportPlugin.csproj b/TransportPlugin.csproj
index fd5b58d..9fe4324 100644
--- a/TransportPlugin.csproj
+++ b/TransportPlugin.csproj
@@ -334,6 +334,7 @@
+
diff --git a/src/Core/SectionBoxExporter.cs b/src/Core/SectionBoxExporter.cs
index c825443..5cf71b8 100644
--- a/src/Core/SectionBoxExporter.cs
+++ b/src/Core/SectionBoxExporter.cs
@@ -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
}
///
- /// 获取剖面盒内的所有对象(公开方法供外部调用)
+ /// 剖面盒遍历结果:同时包含盒内对象和需要隐藏的节点
///
- public List GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
+ public class SectionBoxTraversalResult
{
- var result = new List();
+ ///
+ /// 剖面盒内的对象
+ ///
+ public List ObjectsInBox { get; set; } = new List();
- // 手动遍历所有模型
+ ///
+ /// 需要隐藏的节点(子树完全不在盒内的根节点)
+ ///
+ public ModelItemCollection ItemsToHide { get; set; } = new ModelItemCollection();
+ }
+
+ ///
+ /// 【优化】一次遍历同时获取剖面盒内对象和可见节点集合
+ /// 然后使用原来的兄弟节点算法计算隐藏节点
+ ///
+ public SectionBoxTraversalResult GetObjectsAndHiddenItems(Document document, BoundingBox3D sectionBox)
+ {
+ var result = new SectionBoxTraversalResult();
+ var visibleSet = new HashSet(); // 收集需要保留的节点(对象+祖先)
+
+ // 第一遍:遍历整个模型树,收集盒内对象和可见节点
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;
}
///
- /// 递归遍历模型项 - 剪枝策略:遇到几何体就停止向下遍历
+ /// 递归遍历收集盒内对象和可见节点
///
- public void TraverseModelItem(ModelItem item, BoundingBox3D sectionBox, List result)
+ private void TraverseAndCollect(ModelItem item, BoundingBox3D sectionBox,
+ List objectsInBox, HashSet 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);
}
}
+ ///
+ /// 【使用原来的兄弟节点算法】计算需要隐藏的节点
+ ///
+ private void CalculateHiddenItems(HashSet visibleSet, ModelItemCollection itemsToHide)
+ {
+ // 1. 收集唯一的父节点
+ var uniqueParents = new HashSet();
+ foreach (var visibleItem in visibleSet)
+ {
+ if (visibleItem.Parent != null)
+ {
+ uniqueParents.Add(visibleItem.Parent);
+ }
+ }
+
+ // 2. 收集这些父节点的所有子节点(兄弟节点)
+ var hiddenSet = new HashSet();
+ 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);
+ }
+ }
+
+ ///
+ /// 获取剖面盒内的所有对象(公开方法供外部调用)
+ /// 【已优化】内部使用 GetObjectsAndHiddenItems 实现一次遍历
+ ///
+ public List GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox)
+ {
+ var result = GetObjectsAndHiddenItems(document, sectionBox);
+ return result.ObjectsInBox;
+ }
+
///
/// 检查两个包围盒是否相交
///
@@ -338,36 +410,33 @@ namespace NavisworksTransport.Core
}
///
- /// 导出为NWD文件(公开方法供外部调用)
+ /// 导出为NWD文件(委托给 NwdExportHelper)
+ /// 适用于通用场景(如保存选择项)
///
+ /// Navisworks文档
+ /// 要导出的对象列表
+ /// 导出文件路径
+ /// 导出文件路径,失败返回null
public string ExportToNwd(Document document, List 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;
+ ///
+ /// 【优化】导出为NWD文件,使用预计算的遍历结果
+ /// 适用于剖面盒导出场景,避免重复遍历模型树
+ ///
+ /// Navisworks文档
+ /// 预计算的遍历结果(包含对象和隐藏节点)
+ /// 导出文件路径
+ /// 导出文件路径,失败返回null
+ public string ExportToNwd(Document document, SectionBoxTraversalResult traversalResult, string filePath)
+ {
+ return NwdExportHelper.ExportToNwdWithPrecomputedHiddenItems(
+ traversalResult.ObjectsInBox,
+ traversalResult.ItemsToHide,
+ filePath,
+ "剖面盒导出");
}
///
diff --git a/src/UI/WPF/ViewModels/LayerManagementViewModel.cs b/src/UI/WPF/ViewModels/LayerManagementViewModel.cs
index 121325a..6762718 100644
--- a/src/UI/WPF/ViewModels/LayerManagementViewModel.cs
+++ b/src/UI/WPF/ViewModels/LayerManagementViewModel.cs
@@ -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 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);
}
+ ///
+ /// 【优化】导出剖面盒到NWD,使用预计算的遍历结果
+ /// 避免重复遍历模型树
+ ///
+ 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 = "";
+ }
+ }
+
///
/// 测试ExportToNwd API - 专门的导出API
///
diff --git a/src/Utils/NwdExportHelper.cs b/src/Utils/NwdExportHelper.cs
new file mode 100644
index 0000000..b35f7b9
--- /dev/null
+++ b/src/Utils/NwdExportHelper.cs
@@ -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
+{
+ ///
+ /// NWD 文件导出工具类
+ /// 提供通用的 NWD 导出功能,与具体业务场景解耦
+ ///
+ public static class NwdExportHelper
+ {
+ ///
+ /// 导出指定对象到 NWD 文件
+ /// 使用 ExecuteInIsolation 自动计算并隐藏无关节点
+ ///
+ /// 要导出的对象列表
+ /// 导出文件路径
+ /// 是否排除隐藏项,默认 true
+ /// 导出文件路径,失败返回 null
+ public static string ExportToNwd(List 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;
+ }
+
+ ///
+ /// 使用预计算的隐藏节点列表导出到 NWD 文件
+ /// 适用于已经遍历并计算好隐藏节点的场景(如剖面盒导出)
+ ///
+ /// 要导出的对象列表
+ /// 预计算的隐藏节点集合
+ /// 导出文件路径
+ /// 操作名称(用于日志)
+ /// 导出文件路径,失败返回 null
+ public static string ExportToNwdWithPrecomputedHiddenItems(
+ List 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;
+ }
+ }
+}
diff --git a/src/Utils/VisibilityHelper.cs b/src/Utils/VisibilityHelper.cs
index 075e12e..441e4ae 100644
--- a/src/Utils/VisibilityHelper.cs
+++ b/src/Utils/VisibilityHelper.cs
@@ -244,6 +244,60 @@ namespace NavisworksTransport
}
}
+ ///
+ /// 【新增】应用预计算的隐藏节点列表,并保存当前状态用于后续恢复
+ /// 用于优化场景:遍历和计算隐藏节点已经完成,只需直接应用
+ ///
+ /// 预计算的隐藏节点集合
+ /// 在应用隐藏后执行的操作
+ /// 操作名称(用于日志)
+ /// 操作结果
+ public static T ExecuteWithPrecomputedHiddenItems(ModelItemCollection itemsToHide, Func 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);
+ }
+ }
+
+ ///
+ /// 【新增】应用预计算的隐藏节点列表(无返回值版本)
+ ///
+ public static void ExecuteWithPrecomputedHiddenItems(ModelItemCollection itemsToHide, Action action, string operationName = "预计算隐藏")
+ {
+ ExecuteWithPrecomputedHiddenItems