fix: 关闭剖面盒后恢复剖面状态时同时显示UI指示器

JSON 透传方式不触发 Navisworks 剖面工具 UI 指示器。
改为解析保存的 JSON 自动识别 Box/Planes 模式:
- Box 模式:提取包围盒,用 ApplyClipBox 重建(已知能显示指示器)
- Planes 模式:提取平面数据 (Normal/Distance),重建 Planes JSON
移除旧注释中关于手动对齐的说明
This commit is contained in:
tian 2026-06-30 20:37:29 +08:00
parent 0d4498e03d
commit 17d774154d
4 changed files with 226 additions and 9 deletions

View File

@ -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;
/// <summary>
/// 是否启用剖面盒(聚焦当前路径)
@ -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("[剖面盒] 已关闭");
}

View File

@ -193,6 +193,10 @@ NavisworksTransport 主控制面板 - 采用与其他视图一致的Navisworks 2
<!-- 右侧:原有按钮 -->
<StackPanel Grid.Column="2" Orientation="Horizontal">
<Button Content="恢复剖面"
Command="{Binding RestoreClipStateCommand}"
Style="{StaticResource SecondaryButtonStyle}"
ToolTip="实验:恢复保存的剖面状态"/>
<Button Content="帮助"
Click="HelpButton_Click"
Style="{StaticResource SecondaryButtonStyle}"

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using Newtonsoft.Json.Linq;
namespace NavisworksTransport.Utils
{
@ -228,6 +229,186 @@ namespace NavisworksTransport.Utils
}
}
/// <summary>
/// 保存当前剖面盒的完整状态(包括 Box/Plane 模式)。
/// 返回 JSON 字符串,可通过 RestoreClipState 恢复。
/// </summary>
public static string SaveClipState()
{
try
{
return Application.ActiveDocument.ActiveView.GetClippingPlanes();
}
catch (Exception ex)
{
LogManager.Error($"[剖面盒] 保存状态失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 恢复剖面盒的完整状态(包括 Box/Plane 模式)。
/// 使用 SaveClipState 返回的 JSON 字符串。
/// </summary>
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}");
}
}
/// <summary>
/// 重建剖面状态,同时确保剖面 UI 指示器显示。
/// 解析保存的 JSON自动识别 Box/Planes 模式,
/// 用各自的方式重建剖面几何并刷新 UI 指示器。
/// </summary>
/// <param name="savedJson">由 SaveClipState 返回的 JSON</param>
/// <returns>是否成功重建</returns>
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<bool>() ?? true;
double range = jObj["Range"]?.Value<double>() ?? 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;
}
}
/// <summary>
/// 从 JToken 解析 Point3D数组格式 [x, y, z]),返回是否解析成功
/// </summary>
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<double>(),
arr[1].Value<double>(),
arr[2].Value<double>());
return true;
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// 构建 Planes 模式的 ClipPlaneSet JSON
/// 用与 BuildClipPlaneSetJson 一致的格式风格确保指示器显示。
/// </summary>
private static string BuildClipPlaneSetPlanesJson(JArray planesArray, bool enabled, double range)
{
// 逐个提取平面数据
var planeJsons = new List<string>();
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<double>();
ny = normalArr[1].Value<double>();
nz = normalArr[2].Value<double>();
}
else if (normal is JObject normalObj)
{
nx = normalObj["X"]?.Value<double>();
ny = normalObj["Y"]?.Value<double>();
nz = normalObj["Z"]?.Value<double>();
}
distance = planeToken["Distance"]?.Value<double>();
planeEnabled = planeToken["Enabled"]?.Value<bool>() ?? 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());
}
/// <summary>
/// 检查剖面盒是否已启用
/// </summary>
@ -710,13 +891,19 @@ namespace NavisworksTransport.Utils
{
// 构建 JSON 格式的 ClipPlaneSet
string json = BuildClipPlaneSetJson(box, true);
// 使用 View.SetClippingPlanes 方法设置
ApplyClipJson(json);
}
/// <summary>
/// 向视口应用剪裁 JSON通用方法Box 和 Planes 共用)
/// </summary>
private static void ApplyClipJson(string json)
{
var view = Application.ActiveDocument.ActiveView;
// TrySetClippingPlanes 返回 bool 表示是否成功,不会抛出异常
bool success = view.TrySetClippingPlanes(json);
if (!success)
{
// 如果 Try 方法失败,尝试使用 SetClippingPlanes会抛出异常

View File

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