From 3b801c6bd42e8dfa6c0f2870059e73d29bad5f11 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Wed, 11 Mar 2026 19:03:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=99=E2=80=9C=E5=AF=BC=E5=87=BA=E5=89=96?= =?UTF-8?q?=E9=9D=A2=E7=9B=92=E2=80=9D=E5=A2=9E=E5=8A=A0=E5=AF=BC=E5=87=BA?= =?UTF-8?q?nwd=E6=96=87=E4=BB=B6=EF=BC=9B=E9=87=8D=E6=9E=84=E4=BA=86?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=E6=80=A7=E7=9A=84=E4=BF=9D=E5=AD=98=E5=92=8C?= =?UTF-8?q?=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/requirement/todo_features.md | 4 + src/Core/SectionBoxExporter.cs | 76 ++++-- .../ViewModels/LayerManagementViewModel.cs | 123 +--------- .../WPF/ViewModels/ModelSettingsViewModel.cs | 49 ++-- src/Utils/VisibilityHelper.cs | 225 ++++++++++++++++++ 5 files changed, 314 insertions(+), 163 deletions(-) diff --git a/doc/requirement/todo_features.md b/doc/requirement/todo_features.md index 9eb638c..e7a8cce 100644 --- a/doc/requirement/todo_features.md +++ b/doc/requirement/todo_features.md @@ -2,6 +2,10 @@ ## 功能点 +### [2026/3/11] + +1. [x] (功能)给“导出剖面盒”增加导出nwd文件的选项 + ### [2026/3/9] 1. [x] (功能)支持记录并查看路径文件操作的历史记录 diff --git a/src/Core/SectionBoxExporter.cs b/src/Core/SectionBoxExporter.cs index 6c1e322..d9554d6 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 NavisApplication = Autodesk.Navisworks.Api.Application; namespace NavisworksTransport.Core { @@ -15,7 +16,7 @@ namespace NavisworksTransport.Core public class SectionBoxExporter { /// - /// 导出剖面盒信息到JSON文件 + /// 导出剖面盒信息 /// /// Navisworks文档 /// 导出的文件路径,失败返回null @@ -72,8 +73,8 @@ namespace NavisworksTransport.Core return null; } - // 3. 选择保存路径 - string filePath = ShowSaveFileDialog(); + // 3. 选择保存路径和格式 + string filePath = ShowSaveFileDialog(out bool exportAsNwd); if (string.IsNullOrEmpty(filePath)) return null; @@ -82,15 +83,20 @@ namespace NavisworksTransport.Core // 4. 获取剖面盒内的对象 var objectsInSectionBox = GetObjectsInSectionBox(document, sectionBoxBounds); - // 5. 构建导出数据 - var exportData = BuildExportData(sectionBoxBounds, objectsInSectionBox, document); - - // 6. 序列化为JSON并保存 - string json = JsonConvert.SerializeObject(exportData, Formatting.Indented); - File.WriteAllText(filePath, json); - - LogManager.Info($"剖面盒导出完成: {objectsInSectionBox.Count} 个对象 -> {filePath}"); - return filePath; + if (exportAsNwd) + { + // 导出为NWD格式 + return ExportToNwd(document, objectsInSectionBox, filePath); + } + else + { + // 导出为JSON格式(原有逻辑) + var exportData = BuildExportData(sectionBoxBounds, objectsInSectionBox, document); + string json = JsonConvert.SerializeObject(exportData, Formatting.Indented); + File.WriteAllText(filePath, json); + LogManager.Info($"剖面盒导出完成: {objectsInSectionBox.Count} 个对象 -> {filePath}"); + return filePath; + } } catch (Exception ex) { @@ -331,20 +337,58 @@ namespace NavisworksTransport.Core return string.Join("/", pathParts); } + /// + /// 导出为NWD文件 + /// + private 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); + + // 在隔离模式下执行导出,自动保存和恢复可见性 + 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; + } + /// /// 显示保存文件对话框 /// - private string ShowSaveFileDialog() + private string ShowSaveFileDialog(out bool exportAsNwd) { + exportAsNwd = true; // 默认导出为NWD using (var dialog = new SaveFileDialog()) { - dialog.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"; - dialog.DefaultExt = "json"; - dialog.FileName = $"SectionBox_Export_{DateTime.Now:yyyyMMdd_HHmmss}.json"; + // NWD 作为默认格式,JSON 作为备选 + dialog.Filter = "Navisworks files (*.nwd)|*.nwd|JSON files (*.json)|*.json"; + dialog.DefaultExt = "nwd"; + dialog.FileName = $"SectionBox_Export_{DateTime.Now:yyyyMMdd_HHmmss}"; dialog.Title = "导出剖面盒信息"; if (dialog.ShowDialog() == DialogResult.OK) { + // 如果用户选择了JSON(FilterIndex为2或文件扩展名为json) + exportAsNwd = !(dialog.FilterIndex == 2 || + dialog.FileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase)); return dialog.FileName; } } diff --git a/src/UI/WPF/ViewModels/LayerManagementViewModel.cs b/src/UI/WPF/ViewModels/LayerManagementViewModel.cs index e0de785..58ca869 100644 --- a/src/UI/WPF/ViewModels/LayerManagementViewModel.cs +++ b/src/UI/WPF/ViewModels/LayerManagementViewModel.cs @@ -1853,15 +1853,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels LogManager.Info($"[LayerManagementViewModel] 准备导出 {itemsToExport.Count} 个节点"); - // 保存当前可见性状态 - var originalVisibilityState = SaveCurrentVisibilityState(document); - - try + // 在隔离模式下执行导出,自动恢复可见性 + VisibilityHelper.ExecuteInIsolation(itemsToExport, () => { - // 隔离显示 - bool isolateSuccess = VisibilityHelper.IsolateSpecificItems(itemsToExport); - if (!isolateSuccess) throw new InvalidOperationException("隔离显示失败"); - // 导出选项 var exportOptions = new Autodesk.Navisworks.Api.NwdExportOptions { @@ -1873,12 +1867,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels document.ExportToNwd(saveFilePath, exportOptions); exportResult = true; LogManager.Info("[LayerManagementViewModel] ExportToNwd API调用完成"); - } - finally - { - // 恢复可见性 - RestoreVisibilityState(document, originalVisibilityState); - } + }); } catch (Exception ex) { @@ -1930,112 +1919,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels } } - /// - /// 保存当前可见性状态 - /// - private Dictionary SaveCurrentVisibilityState(Document document) - { - var visibilityState = new Dictionary(); - - try - { - // 获取所有顶级项目并记录它们的可见性状态 - foreach (Model model in document.Models) - { - foreach (ModelItem topLevelItem in model.RootItem.Children) - { - try - { - // 记录是否隐藏(IsHidden为true表示隐藏,我们存储可见性所以取反) - visibilityState[topLevelItem] = !topLevelItem.IsHidden; - } - catch - { - // 如果无法获取状态,默认为可见 - visibilityState[topLevelItem] = true; - } - } - } - - LogManager.Info($"[LayerManagementViewModel] 保存可见性状态完成,记录了 {visibilityState.Count} 个顶级项目"); - } - catch (Exception ex) - { - LogManager.Error($"[LayerManagementViewModel] 保存可见性状态失败: {ex.Message}"); - } - - return visibilityState; - } - - /// - /// 恢复可见性状态 - /// - private void RestoreVisibilityState(Document document, Dictionary visibilityState) - { - try - { - LogManager.Info("[LayerManagementViewModel] 恢复可见性状态"); - - if (visibilityState == null || visibilityState.Count == 0) - { - LogManager.Warning("[LayerManagementViewModel] 没有可见性状态需要恢复,重置为全部可见"); - document.Models.ResetAllHidden(); - return; - } - - // 使用成熟的可见性控制模式 - 分别收集要显示和隐藏的项目 - var itemsToShow = new ModelItemCollection(); - var itemsToHide = new ModelItemCollection(); - - foreach (var kvp in visibilityState) - { - try - { - if (kvp.Value) // 原来是可见的 - { - itemsToShow.Add(kvp.Key); - } - else // 原来是隐藏的 - { - itemsToHide.Add(kvp.Key); - } - } - catch (Exception ex) - { - LogManager.Warning($"[LayerManagementViewModel] 处理项目可见性状态时出错: {ex.Message}"); - } - } - - // 首先重置所有项目为可见状态 - document.Models.ResetAllHidden(); - - // 然后隐藏原来应该隐藏的项目 - if (itemsToHide.Count > 0) - { - document.Models.SetHidden(itemsToHide, true); - LogManager.Info($"[LayerManagementViewModel] 恢复隐藏 {itemsToHide.Count} 个项目"); - } - - LogManager.Info("[LayerManagementViewModel] 可见性状态恢复完成"); - } - catch (Exception ex) - { - LogManager.Error($"[LayerManagementViewModel] 恢复可见性状态失败: {ex.Message}"); - // 失败时至少确保模型处于可见状态 - try - { - document.Models.ResetAllHidden(); - LogManager.Info("[LayerManagementViewModel] 已重置为全部可见状态"); - } - catch (Exception resetEx) - { - LogManager.Error($"[LayerManagementViewModel] 重置可见性也失败: {resetEx.Message}"); - } - } - } - - - /// /// 测试ExportToNwd API - 专门的导出API /// diff --git a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs index f9a3f35..1e0093e 100644 --- a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs +++ b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs @@ -1435,35 +1435,29 @@ namespace NavisworksTransport.UI.WPF.ViewModels } else { - ShowAllInternal(); + // 取消"仅显示物流元素",退出隔离模式 + if (VisibilityHelper.ExitIsolation()) + { + // 更新物流模型列表中的可见性状态 + foreach (var model in LogisticsModels) + { + model.IsVisible = true; + } + + UpdateMainStatus("已恢复可见性状态"); + LogManager.Info("[UI-ModelSettings] 退出隔离模式,恢复可见性状态"); + } + else + { + // 没有隔离状态,显示全部 + VisibilityHelper.ShowAllItems(); + UpdateMainStatus("已显示全部元素"); + LogManager.Info("[UI-ModelSettings] 没有隔离状态,显示全部"); + } } }, "应用可见性模式", runOnUIThread: true); } - /// - /// 内部方法:显示所有元素 - /// - private void ShowAllInternal() - { - bool success = VisibilityHelper.ShowAllItems(); - - if (success) - { - foreach (var model in LogisticsModels) - { - model.IsVisible = true; - } - - UpdateMainStatus("显示所有元素"); - LogManager.Info("切换到显示全部模式"); - } - else - { - UpdateMainStatus("显示全部失败"); - LogManager.Error("显示全部失败"); - } - } - /// /// 内部方法:仅显示物流元素 /// @@ -1492,7 +1486,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels LogManager.Info($"[UI-ModelSettings] 找到 {logisticsItems.Count} 个物流元素"); - bool success = VisibilityHelper.IsolateSpecificItems(logisticsItems); + // 进入隔离模式(自动保存当前状态) + bool success = VisibilityHelper.EnterIsolation(logisticsItems); if (success) { @@ -1507,7 +1502,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels } else { - LogManager.Error("[UI-ModelSettings] 隔离显示失败"); + LogManager.Error("[UI-ModelSettings] 进入隔离模式失败"); UpdateMainStatus("操作失败"); } } diff --git a/src/Utils/VisibilityHelper.cs b/src/Utils/VisibilityHelper.cs index c5b9cf8..1dd9e26 100644 --- a/src/Utils/VisibilityHelper.cs +++ b/src/Utils/VisibilityHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Autodesk.Navisworks.Api; using NavisApplication = Autodesk.Navisworks.Api.Application; @@ -141,5 +142,229 @@ namespace NavisworksTransport } } + // 可见性状态栈(支持嵌套隔离) + private static readonly Stack> _visibilityStateStack = new Stack>(); + + /// + /// 进入隔离模式:保存当前可见性状态,并隔离显示指定项目 + /// + /// 要显示的项目集合 + /// 操作是否成功 + public static bool EnterIsolation(ModelItemCollection itemsToShow) + { + try + { + var document = NavisApplication.ActiveDocument; + if (document == null) + { + LogManager.Warning("[VisibilityHelper] 进入隔离模式失败:没有活动文档"); + return false; + } + + // 保存当前状态到栈 + var state = SaveVisibilityStateInternal(document); + _visibilityStateStack.Push(state); + + // 执行隔离显示 + bool success = IsolateSpecificItems(itemsToShow); + if (!success) + { + // 隔离失败,弹出状态 + _visibilityStateStack.Pop(); + return false; + } + + LogManager.Info($"[VisibilityHelper] 进入隔离模式,当前栈深度: {_visibilityStateStack.Count}"); + return true; + } + catch (Exception ex) + { + LogManager.Error($"[VisibilityHelper] 进入隔离模式失败: {ex.Message}"); + return false; + } + } + + /// + /// 退出隔离模式:恢复之前保存的可见性状态 + /// + /// 操作是否成功 + public static bool ExitIsolation() + { + try + { + if (_visibilityStateStack.Count == 0) + { + LogManager.Warning("[VisibilityHelper] 退出隔离模式失败:没有保存的状态"); + return false; + } + + var document = NavisApplication.ActiveDocument; + if (document == null) + { + LogManager.Warning("[VisibilityHelper] 退出隔离模式失败:没有活动文档"); + return false; + } + + // 弹出并恢复状态 + var state = _visibilityStateStack.Pop(); + RestoreVisibilityStateInternal(document, state); + + LogManager.Info($"[VisibilityHelper] 退出隔离模式,当前栈深度: {_visibilityStateStack.Count}"); + return true; + } + catch (Exception ex) + { + LogManager.Error($"[VisibilityHelper] 退出隔离模式失败: {ex.Message}"); + return false; + } + } + + /// + /// 在隔离模式下执行操作,自动恢复可见性 + /// + /// 要显示的项目集合 + /// 要执行的操作 + public static void ExecuteInIsolation(ModelItemCollection itemsToShow, Action action) + { + bool entered = EnterIsolation(itemsToShow); + try + { + action?.Invoke(); + } + finally + { + if (entered) + { + ExitIsolation(); + } + } + } + + /// + /// 在隔离模式下执行操作(带返回值),自动恢复可见性 + /// + /// 返回值类型 + /// 要显示的项目集合 + /// 要执行的函数 + /// 函数执行结果 + public static T ExecuteInIsolation(ModelItemCollection itemsToShow, Func func) + { + bool entered = EnterIsolation(itemsToShow); + try + { + return func.Invoke(); + } + finally + { + if (entered) + { + ExitIsolation(); + } + } + } + + /// + /// 获取当前隔离栈深度(用于调试) + /// + public static int IsolationDepth => _visibilityStateStack.Count; + + /// + /// 清空所有保存的可见性状态(谨慎使用) + /// + public static void ClearIsolationStack() + { + _visibilityStateStack.Clear(); + LogManager.Warning("[VisibilityHelper] 已清空所有隔离状态"); + } + + #region 内部实现 + + /// + /// 保存当前可见性状态(内部实现) + /// + private static Dictionary SaveVisibilityStateInternal(Document document) + { + var visibilityState = new Dictionary(); + + try + { + if (document?.Models == null) + return visibilityState; + + foreach (Model model in document.Models) + { + if (model.RootItem?.Children == null) + continue; + + foreach (ModelItem topLevelItem in model.RootItem.Children) + { + try + { + visibilityState[topLevelItem] = !topLevelItem.IsHidden; + } + catch + { + visibilityState[topLevelItem] = true; + } + } + } + } + catch (Exception ex) + { + LogManager.Error($"[VisibilityHelper] 保存可见性状态失败: {ex.Message}"); + } + + return visibilityState; + } + + /// + /// 恢复可见性状态(内部实现) + /// + private static void RestoreVisibilityStateInternal(Document document, Dictionary visibilityState) + { + try + { + if (document?.Models == null) + return; + + if (visibilityState == null || visibilityState.Count == 0) + { + document.Models.ResetAllHidden(); + return; + } + + var itemsToHide = new ModelItemCollection(); + + foreach (var kvp in visibilityState) + { + try + { + if (!kvp.Value) // 原来是隐藏的 + { + itemsToHide.Add(kvp.Key); + } + } + catch { } + } + + document.Models.ResetAllHidden(); + + if (itemsToHide.Count > 0) + { + document.Models.SetHidden(itemsToHide, true); + } + } + catch (Exception ex) + { + LogManager.Error($"[VisibilityHelper] 恢复可见性状态失败: {ex.Message}"); + try + { + document.Models.ResetAllHidden(); + } + catch { } + } + } + + #endregion } } \ No newline at end of file