From 17d774154ddc1b678c2c162ae8aea503f24ca091 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Tue, 30 Jun 2026 20:37:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=B3=E9=97=AD=E5=89=96=E9=9D=A2?= =?UTF-8?q?=E7=9B=92=E5=90=8E=E6=81=A2=E5=A4=8D=E5=89=96=E9=9D=A2=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=97=B6=E5=90=8C=E6=97=B6=E6=98=BE=E7=A4=BAUI?= =?UTF-8?q?=E6=8C=87=E7=A4=BA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JSON 透传方式不触发 Navisworks 剖面工具 UI 指示器。 改为解析保存的 JSON 自动识别 Box/Planes 模式: - Box 模式:提取包围盒,用 ApplyClipBox 重建(已知能显示指示器) - Planes 模式:提取平面数据 (Normal/Distance),重建 Planes JSON 移除旧注释中关于手动对齐的说明 --- .../ViewModels/LogisticsControlViewModel.cs | 29 ++- src/UI/WPF/Views/LogisticsControlPanel.xaml | 4 + src/Utils/SectionClipHelper.cs | 195 +++++++++++++++++- src/Utils/VisibilityHelper.cs | 7 +- 4 files changed, 226 insertions(+), 9 deletions(-) diff --git a/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs b/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs index 182f913..0126b23 100644 --- a/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs +++ b/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs @@ -155,6 +155,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels private bool _isUpdatingClipBoxState; private ClipBoxFocusMode _clipBoxFocusMode = ClipBoxFocusMode.None; private readonly SelectionClipBoxLockState _selectionClipBoxLockState = new SelectionClipBoxLockState(); + private string _savedClipStateJson; /// /// 是否启用剖面盒(聚焦当前路径) @@ -382,12 +383,24 @@ namespace NavisworksTransport.UI.WPF.ViewModels private void ActivatePathClipBox() { + // 开启前保存当前剖面状态(包括 Box/Plane 模式) + if (_clipBoxFocusMode == ClipBoxFocusMode.None) + { + _savedClipStateJson = SectionClipHelper.SaveClipState(); + LogManager.Debug("[剖面盒] 已保存开启前的剖面状态"); + } SetClipBoxState(ClipBoxFocusMode.Path); EnableClipBoxForCurrentRoute(); } private void ActivateSelectionClipBox() { + // 开启前保存当前剖面状态(包括 Box/Plane 模式) + if (_clipBoxFocusMode == ClipBoxFocusMode.None) + { + _savedClipStateJson = SectionClipHelper.SaveClipState(); + LogManager.Debug("[剖面盒] 已保存开启前的剖面状态"); + } SetClipBoxState(ClipBoxFocusMode.Selection); EnableClipBoxForCurrentSelection(); } @@ -396,7 +409,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels { SetClipBoxState(ClipBoxFocusMode.None); _selectionClipBoxLockState.Clear(); - SectionClipHelper.ClearClipBox(); + // 重建保存的剖面状态(自动识别 Box/Planes 模式), + // 同时确保 Navisworks 剖面 UI 指示器显示。 + if (!string.IsNullOrEmpty(_savedClipStateJson)) + { + bool reconstructed = SectionClipHelper.ReconstructClipStateWithIndicator(_savedClipStateJson); + if (!reconstructed) + { + SectionClipHelper.ClearClipBox(); + } + _savedClipStateJson = null; + } + else + { + SectionClipHelper.ClearClipBox(); + } LogManager.Info("[剖面盒] 已关闭"); } diff --git a/src/UI/WPF/Views/LogisticsControlPanel.xaml b/src/UI/WPF/Views/LogisticsControlPanel.xaml index 55e5a2b..150aeca 100644 --- a/src/UI/WPF/Views/LogisticsControlPanel.xaml +++ b/src/UI/WPF/Views/LogisticsControlPanel.xaml @@ -193,6 +193,10 @@ NavisworksTransport 主控制面板 - 采用与其他视图一致的Navisworks 2 + + public static string SaveClipState() + { + try + { + return Application.ActiveDocument.ActiveView.GetClippingPlanes(); + } + catch (Exception ex) + { + LogManager.Error($"[剖面盒] 保存状态失败: {ex.Message}"); + return null; + } + } + + /// + /// 恢复剖面盒的完整状态(包括 Box/Plane 模式)。 + /// 使用 SaveClipState 返回的 JSON 字符串。 + /// + public static void RestoreClipState(string savedJson) + { + if (string.IsNullOrEmpty(savedJson)) + return; + try + { + var view = Application.ActiveDocument.ActiveView; + view.TrySetClippingPlanes(savedJson); + LogManager.Info("[剖面盒] 已恢复保存的剖面状态"); + } + catch (Exception ex) + { + LogManager.Error($"[剖面盒] 恢复状态失败: {ex.Message}"); + } + } + + /// + /// 重建剖面状态,同时确保剖面 UI 指示器显示。 + /// 解析保存的 JSON,自动识别 Box/Planes 模式, + /// 用各自的方式重建剖面几何并刷新 UI 指示器。 + /// + /// 由 SaveClipState 返回的 JSON + /// 是否成功重建 + public static bool ReconstructClipStateWithIndicator(string savedJson) + { + if (string.IsNullOrEmpty(savedJson)) + return false; + + try + { + var jObj = JObject.Parse(savedJson); + + // === Box 模式:提取包围盒,用 ApplyClipBox 重建(已知能显示指示器) === + if (jObj["OrientedBox"] != null) + { + var boxArr = jObj["OrientedBox"]?["Box"]; + if (boxArr != null && boxArr.Count() >= 2) + { + Point3D min, max; + if (TryParseJArrayToPoint3D(boxArr[0], out min) && + TryParseJArrayToPoint3D(boxArr[1], out max)) + { + ApplyClipBox(new BoundingBox3D(min, max)); + LogManager.Info("[剖面盒] 已通过 Box 模式重建剖面状态(含指示器)"); + return true; + } + } + } + + // === Planes 模式:提取平面数据,重建 Planes JSON === + if (jObj["Planes"] != null) + { + bool enabled = jObj["Enabled"]?.Value() ?? true; + double range = jObj["Range"]?.Value() ?? 0.0; + + string planesJson = BuildClipPlaneSetPlanesJson(jObj["Planes"] as JArray, enabled, range); + var view = Application.ActiveDocument.ActiveView; + bool success = view.TrySetClippingPlanes(planesJson); + + if (success) + { + LogManager.Info("[剖面盒] 已通过 Planes 模式重建剖面状态(含指示器)"); + return true; + } + + // Try 失败则尝试 Set 版本 + view.SetClippingPlanes(planesJson); + LogManager.Info("[剖面盒] 已通过 Planes 模式重建剖面状态(含指示器,Set 路径)"); + return true; + } + + LogManager.Warning("[剖面盒] 无法识别保存的剖面 JSON 结构"); + return false; + } + catch (Exception ex) + { + LogManager.Error($"[剖面盒] 重建剖面状态失败: {ex.Message}"); + return false; + } + } + + /// + /// 从 JToken 解析 Point3D(数组格式 [x, y, z]),返回是否解析成功 + /// + private static bool TryParseJArrayToPoint3D(JToken token, out Point3D point) + { + point = new Point3D(); + try + { + if (token is JArray arr && arr.Count >= 3) + { + point = new Point3D( + arr[0].Value(), + arr[1].Value(), + arr[2].Value()); + return true; + } + return false; + } + catch + { + return false; + } + } + + /// + /// 构建 Planes 模式的 ClipPlaneSet JSON, + /// 用与 BuildClipPlaneSetJson 一致的格式风格确保指示器显示。 + /// + private static string BuildClipPlaneSetPlanesJson(JArray planesArray, bool enabled, double range) + { + // 逐个提取平面数据 + var planeJsons = new List(); + foreach (var planeToken in planesArray) + { + double? nx = null, ny = null, nz = null, distance = null; + bool planeEnabled = true; + + // 支持 Normal 为数组格式 [x, y, z] 或对象格式 {X, Y, Z} + var normal = planeToken["Normal"]; + if (normal is JArray normalArr && normalArr.Count >= 3) + { + nx = normalArr[0].Value(); + ny = normalArr[1].Value(); + nz = normalArr[2].Value(); + } + else if (normal is JObject normalObj) + { + nx = normalObj["X"]?.Value(); + ny = normalObj["Y"]?.Value(); + nz = normalObj["Z"]?.Value(); + } + + distance = planeToken["Distance"]?.Value(); + planeEnabled = planeToken["Enabled"]?.Value() ?? true; + + if (nx.HasValue && ny.HasValue && nz.HasValue && distance.HasValue) + { + planeJsons.Add(string.Format( + "{{\"Type\":\"ClipPlane\",\"Version\":1," + + "\"Normal\":[{0},{1},{2}]," + + "\"Distance\":{3},\"Enabled\":{4}}}", + nx.Value.ToString("G17"), + ny.Value.ToString("G17"), + nz.Value.ToString("G17"), + distance.Value.ToString("G17"), + planeEnabled.ToString().ToLowerInvariant())); + } + } + + string planesArrayStr = string.Join(",", planeJsons); + string rangeStr = range > 0 ? $",\"Range\":{range.ToString("G17")}" : ""; + + return string.Format( + "{{\"Type\":\"ClipPlaneSet\",\"Version\":1," + + "\"Planes\":[{0}]{1},\"Enabled\":{2}}}", + planesArrayStr, rangeStr, enabled.ToString().ToLowerInvariant()); + } + /// /// 检查剖面盒是否已启用 /// @@ -710,13 +891,19 @@ namespace NavisworksTransport.Utils { // 构建 JSON 格式的 ClipPlaneSet string json = BuildClipPlaneSetJson(box, true); - - // 使用 View.SetClippingPlanes 方法设置 + ApplyClipJson(json); + } + + /// + /// 向视口应用剪裁 JSON(通用方法,Box 和 Planes 共用) + /// + private static void ApplyClipJson(string json) + { var view = Application.ActiveDocument.ActiveView; - + // TrySetClippingPlanes 返回 bool 表示是否成功,不会抛出异常 bool success = view.TrySetClippingPlanes(json); - + if (!success) { // 如果 Try 方法失败,尝试使用 SetClippingPlanes(会抛出异常) diff --git a/src/Utils/VisibilityHelper.cs b/src/Utils/VisibilityHelper.cs index e35434f..43cd555 100644 --- a/src/Utils/VisibilityHelper.cs +++ b/src/Utils/VisibilityHelper.cs @@ -267,8 +267,7 @@ namespace NavisworksTransport // 保存剖面盒状态(Navisworks ExportToNwd 会内部关闭剖分) bool clipWasEnabled = SectionClipHelper.IsClipBoxEnabled; - BoundingBox3D savedClipBox = default; - bool hasClipBox = SectionClipHelper.TryGetCurrentClipBox(out savedClipBox); + string savedClipJson = SectionClipHelper.SaveClipState(); try { @@ -291,9 +290,9 @@ namespace NavisworksTransport RestoreVisibilityStateInternal(document, savedState); // 5. 恢复剖面盒(Navisworks 导出时会关闭) - if (clipWasEnabled && !SectionClipHelper.IsClipBoxEnabled && hasClipBox) + if (clipWasEnabled && !SectionClipHelper.IsClipBoxEnabled && !string.IsNullOrEmpty(savedClipJson)) { - SectionClipHelper.RestoreClipBox(savedClipBox); + SectionClipHelper.RestoreClipState(savedClipJson); } } }