From 579434fbff93e09a78d1a57a11a310f722ca4fe2 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 12 Feb 2026 20:30:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=BC=E5=87=BA=E5=89=96?= =?UTF-8?q?=E9=9D=A2=E7=9B=92=E7=9B=B8=E4=BA=A4=E5=85=83=E7=B4=A0=E7=9A=84?= =?UTF-8?q?=E5=8C=85=E5=9B=B4=E7=9B=92=E4=BF=A1=E6=81=AF=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=88=E7=94=A8=E4=BA=8E=E4=B8=89=E7=BB=B4=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E9=87=8D=E5=BB=BA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NavisworksTransportPlugin.csproj | 2 + src/Core/SectionBoxExporter.cs | 354 ++++++++++++++++++ .../ViewModels/SystemManagementViewModel.cs | 64 ++++ src/UI/WPF/Views/SystemManagementView.xaml | 5 + 4 files changed, 425 insertions(+) create mode 100644 src/Core/SectionBoxExporter.cs diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 47e48a8..1efdb79 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -131,6 +131,8 @@ + + diff --git a/src/Core/SectionBoxExporter.cs b/src/Core/SectionBoxExporter.cs new file mode 100644 index 0000000..6c1e322 --- /dev/null +++ b/src/Core/SectionBoxExporter.cs @@ -0,0 +1,354 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using Autodesk.Navisworks.Api; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace NavisworksTransport.Core +{ + /// + /// 剖面盒导出器 - 导出剖面盒内的对象包围盒信息到JSON + /// + public class SectionBoxExporter + { + /// + /// 导出剖面盒信息到JSON文件 + /// + /// Navisworks文档 + /// 导出的文件路径,失败返回null + public string ExportToJson(Document document) + { + if (document == null) + throw new ArgumentNullException(nameof(document)); + + // 1. 获取当前视点的裁剪平面 + var viewpoint = document.CurrentViewpoint.Value; + var clipPlanes = viewpoint.ClipPlanes; + + if (clipPlanes == null) + { + MessageBox.Show( + "当前没有活动的剖面盒!\n请先使用 Navisworks 的剖面工具创建剖面盒。", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return null; + } + + // 检查是否启用 + if (!clipPlanes.Enabled) + { + MessageBox.Show( + "剖面盒未启用!\n请先在菜单【视点】-【启用剖分】,然后在菜单【剖分工具】的【模式】中选择【长方体】。\n【移动】、【缩放】或【旋转】剖面盒到适当的位置,或者用【适应选择】调整", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return null; + } + + // 检查是否为长方体模式(Box),排除平面裁剪模式(Clip) + if (clipPlanes.Mode != ClipPlaneSetMode.Box) + { + MessageBox.Show( + "当前是平面裁剪模式,请切换为剖面盒模式!\n在 Navisworks 剖面工具中选择'Box'模式。", + "提示", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return null; + } + + // 2. 获取剖面盒的包围盒 + BoundingBox3D sectionBoxBounds; + if (!GetSectionBoxBounds(clipPlanes, out sectionBoxBounds)) + { + MessageBox.Show( + "无法获取剖面盒边界!\n请确保剖面盒已正确设置。", + "错误", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + return null; + } + + // 3. 选择保存路径 + string filePath = ShowSaveFileDialog(); + if (string.IsNullOrEmpty(filePath)) + return null; + + try + { + // 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; + } + catch (Exception ex) + { + LogManager.Error($"导出剖面盒失败: {ex.Message}", ex); + MessageBox.Show( + $"导出失败:{ex.Message}", + "错误", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + return null; + } + } + + /// + /// 获取剖面盒的包围盒 + /// + private bool GetSectionBoxBounds(ClipPlaneSet clipPlanes, out BoundingBox3D result) + { + result = default(BoundingBox3D); + + try + { + // 直接获取 Box 属性(长方体模式) + var box = clipPlanes.Box; + if (box == null) + { + LogManager.Warning("ClipPlanes.Box 为 null"); + return false; + } + + // 转换为 BoundingBox3D + result = new BoundingBox3D( + new Point3D(box.Min.X, box.Min.Y, box.Min.Z), + new Point3D(box.Max.X, box.Max.Y, box.Max.Z) + ); + return true; + } + catch (Exception ex) + { + LogManager.Warning($"获取剖面盒边界失败: {ex.Message}"); + return false; + } + } + + /// + /// 获取剖面盒内的所有对象 + /// + private List GetObjectsInSectionBox(Document document, BoundingBox3D sectionBox) + { + var result = new List(); + + // 手动遍历所有模型 + foreach (var model in document.Models) + { + var rootItem = model.RootItem; + if (rootItem != null) + { + TraverseModelItem(rootItem, sectionBox, result); + } + } + + return result; + } + + /// + /// 递归遍历模型项 - 剪枝策略:遇到几何体就停止向下遍历 + /// + private void TraverseModelItem(ModelItem item, BoundingBox3D sectionBox, List result) + { + if (item == null) return; + + // 如果节点隐藏,跳过 + if (item.IsHidden) + return; + + // 如果有几何体信息,检查并添加,然后停止遍历该分支(剪枝) + if (item.HasGeometry) + { + var bbox = item.BoundingBox(); + + // 检查是否有体积(三个方向尺寸都大于0) + double sizeX = bbox.Max.X - bbox.Min.X; + double sizeY = bbox.Max.Y - bbox.Min.Y; + 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); + } + // 找到几何体,停止向下遍历(无论是否相交) + return; + } + + // 没有几何体,继续遍历子节点 + foreach (var child in item.Children) + { + TraverseModelItem(child, sectionBox, result); + } + } + + /// + /// 检查两个包围盒是否相交 + /// + private bool BoundingBoxesIntersect(BoundingBox3D a, BoundingBox3D b) + { + return (a.Min.X <= b.Max.X && a.Max.X >= b.Min.X) && + (a.Min.Y <= b.Max.Y && a.Max.Y >= b.Min.Y) && + (a.Min.Z <= b.Max.Z && a.Max.Z >= b.Min.Z); + } + + /// + /// 构建导出数据 + /// + private JObject BuildExportData(BoundingBox3D sectionBox, List objects, Document document) + { + var root = new JObject(); + + // 1. 导出剖面盒信息 + var sectionBoxInfo = new JObject + { + ["min"] = new JArray { sectionBox.Min.X, sectionBox.Min.Y, sectionBox.Min.Z }, + ["max"] = new JArray { sectionBox.Max.X, sectionBox.Max.Y, sectionBox.Max.Z }, + ["center"] = new JArray + { + (sectionBox.Min.X + sectionBox.Max.X) / 2, + (sectionBox.Min.Y + sectionBox.Max.Y) / 2, + (sectionBox.Min.Z + sectionBox.Max.Z) / 2 + }, + ["size"] = new JArray + { + sectionBox.Max.X - sectionBox.Min.X, + sectionBox.Max.Y - sectionBox.Min.Y, + sectionBox.Max.Z - sectionBox.Min.Z + } + }; + root["sectionBox"] = sectionBoxInfo; + + // 2. 导出文档信息 + var docInfo = new JObject + { + ["fileName"] = document.FileName ?? "Unknown", + ["units"] = document.Units.ToString(), + ["exportTime"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), + ["objectCount"] = objects.Count + }; + root["documentInfo"] = docInfo; + + // 3. 导出对象列表 + var objectsArray = new JArray(); + foreach (var item in objects) + { + var bbox = item.BoundingBox(); + var objInfo = new JObject + { + ["name"] = GetFullPathName(item), + ["boundingBox"] = new JObject + { + ["min"] = new JArray { bbox.Min.X, bbox.Min.Y, bbox.Min.Z }, + ["max"] = new JArray { bbox.Max.X, bbox.Max.Y, bbox.Max.Z }, + ["center"] = new JArray + { + (bbox.Min.X + bbox.Max.X) / 2, + (bbox.Min.Y + bbox.Max.Y) / 2, + (bbox.Min.Z + bbox.Max.Z) / 2 + }, + ["size"] = new JArray + { + bbox.Max.X - bbox.Min.X, + bbox.Max.Y - bbox.Min.Y, + bbox.Max.Z - bbox.Min.Z + } + } + }; + + // 添加Transform信息(仅非单位变换) + try + { + var components = item.Transform.Factor(); + var rotation = components.Rotation.ToAxisAndAngle(); + bool isIdentity = Math.Abs(rotation.Angle) < 0.0001 && + Math.Abs(components.Scale.X - 1) < 0.0001 && + Math.Abs(components.Scale.Y - 1) < 0.0001 && + Math.Abs(components.Scale.Z - 1) < 0.0001 && + Math.Abs(components.Translation.X) < 0.0001 && + Math.Abs(components.Translation.Y) < 0.0001 && + Math.Abs(components.Translation.Z) < 0.0001; + + if (!isIdentity) + { + objInfo["transform"] = new JObject + { + ["translation"] = new JArray { components.Translation.X, components.Translation.Y, components.Translation.Z }, + ["rotation"] = new JObject + { + ["axis"] = new JArray { rotation.Axis.X, rotation.Axis.Y, rotation.Axis.Z }, + ["angle"] = rotation.Angle + }, + ["scale"] = new JArray { components.Scale.X, components.Scale.Y, components.Scale.Z } + }; + } + } + catch (Exception ex) + { + LogManager.Debug($"获取Transform信息失败: {ex.Message}"); + } + + objectsArray.Add(objInfo); + } + root["objects"] = objectsArray; + + return root; + } + + /// + /// 获取对象的全路径名称 + /// + private string GetFullPathName(ModelItem item) + { + if (item == null) return ""; + + var pathParts = new List(); + + // 从当前节点向上遍历到根节点 + var current = item; + while (current != null) + { + string name = current.DisplayName; + if (!string.IsNullOrEmpty(name)) + { + pathParts.Insert(0, name); + } + current = current.Parent; + } + + // 使用 "/" 连接路径 + return string.Join("/", pathParts); + } + + /// + /// 显示保存文件对话框 + /// + private string ShowSaveFileDialog() + { + 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"; + dialog.Title = "导出剖面盒信息"; + + if (dialog.ShowDialog() == DialogResult.OK) + { + return dialog.FileName; + } + } + return null; + } + } +} diff --git a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs index b16fb64..7d5060e 100644 --- a/src/UI/WPF/ViewModels/SystemManagementViewModel.cs +++ b/src/UI/WPF/ViewModels/SystemManagementViewModel.cs @@ -171,6 +171,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels public ICommand TestVoxelPathFindingCommand { get; private set; } public ICommand ReadTransformTestCommand { get; private set; } public ICommand CoordinateSystemExplorerCommand { get; private set; } + public ICommand ExportSectionBoxCommand { get; private set; } // 坐标系设置 public ObservableCollection CoordinateSystemOptions { get; private set; } @@ -308,6 +309,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels TestVoxelPathFindingCommand = new RelayCommand(() => ExecuteTestVoxelPathFinding()); ReadTransformTestCommand = new RelayCommand(() => ExecuteReadTransformTest()); CoordinateSystemExplorerCommand = new RelayCommand(() => ExecuteCoordinateSystemExplorer()); + ExportSectionBoxCommand = new RelayCommand(() => ExecuteExportSectionBox()); // 初始化坐标系选项 CoordinateSystemOptions = new ObservableCollection { "AutoDetect", "ZUp", "YUp" }; @@ -1764,6 +1766,68 @@ namespace NavisworksTransport.UI.WPF.ViewModels }, "坐标系探索"); } + /// + /// 执行导出剖面盒功能 + /// + private void ExecuteExportSectionBox() + { + SafeExecute(() => + { + try + { + UpdateMainStatus("正在导出剖面盒信息..."); + LogManager.Info("开始导出剖面盒信息"); + + var doc = Autodesk.Navisworks.Api.Application.ActiveDocument; + if (doc == null || doc.IsClear) + { + System.Windows.MessageBox.Show( + "没有活动的文档!请先打开一个模型。", + "错误", + System.Windows.MessageBoxButton.OK, + System.Windows.MessageBoxImage.Error); + UpdateMainStatus("导出剖面盒失败:无活动文档"); + return; + } + + // 使用导出功能类 + var exporter = new NavisworksTransport.Core.SectionBoxExporter(); + string filePath = exporter.ExportToJson(doc); + + if (!string.IsNullOrEmpty(filePath)) + { + var result = System.Windows.MessageBox.Show( + $"剖面盒信息已导出到:\n{filePath}\n\n是否打开文件所在目录?", + "导出成功", + System.Windows.MessageBoxButton.YesNo, + System.Windows.MessageBoxImage.Information); + + if (result == System.Windows.MessageBoxResult.Yes) + { + System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\""); + } + + UpdateMainStatus($"剖面盒信息已导出: {filePath}"); + LogManager.Info($"剖面盒信息导出成功: {filePath}"); + } + else + { + UpdateMainStatus("导出剖面盒被取消或失败"); + } + } + catch (Exception ex) + { + LogManager.Error($"导出剖面盒异常: {ex.Message}", ex); + System.Windows.MessageBox.Show( + $"导出剖面盒出现异常:\n{ex.Message}", + "错误", + System.Windows.MessageBoxButton.OK, + System.Windows.MessageBoxImage.Error); + UpdateMainStatus($"导出剖面盒异常: {ex.Message}"); + } + }, "导出剖面盒"); + } + #endregion #region 辅助方法 diff --git a/src/UI/WPF/Views/SystemManagementView.xaml b/src/UI/WPF/Views/SystemManagementView.xaml index 4492eca..535e408 100644 --- a/src/UI/WPF/Views/SystemManagementView.xaml +++ b/src/UI/WPF/Views/SystemManagementView.xaml @@ -236,6 +236,11 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav