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);
}
}
}