From bbd72345c90c9cea4c9b20f32d259d205469ce98 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sat, 11 Apr 2026 22:10:21 +0800 Subject: [PATCH] Add selection-based clip box controls --- .../ViewModels/LogisticsControlViewModel.cs | 166 +++++++++++++++++- src/UI/WPF/Views/LogisticsControlPanel.xaml | 46 ++++- src/Utils/SectionClipHelper.cs | 73 ++++++++ src/Utils/ViewpointHelper.cs | 30 ++++ 4 files changed, 299 insertions(+), 16 deletions(-) diff --git a/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs b/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs index 17b7089..29ff351 100644 --- a/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs +++ b/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs @@ -14,6 +14,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// public class LogisticsControlViewModel : ViewModelBase { + private enum ClipBoxFocusMode + { + None, + Path, + Selection + } + #region 私有字段 private string _statusText; @@ -144,6 +151,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels } private bool _isClipBoxEnabled; + private bool _isSelectionClipBoxEnabled; + private bool _isUpdatingClipBoxState; + private ClipBoxFocusMode _clipBoxFocusMode = ClipBoxFocusMode.None; /// /// 是否启用剖面盒(聚焦当前路径) @@ -155,14 +165,45 @@ namespace NavisworksTransport.UI.WPF.ViewModels { if (SetProperty(ref _isClipBoxEnabled, value)) { + if (_isUpdatingClipBoxState) + { + return; + } + if (value) { - EnableClipBoxForCurrentRoute(); + ActivatePathClipBox(); } - else + else if (_clipBoxFocusMode == ClipBoxFocusMode.Path) { - SectionClipHelper.ClearClipBox(); - LogManager.Info("[剖面盒] 已关闭"); + ClearActiveClipBox(); + } + } + } + } + + /// + /// 是否启用剖面盒(聚焦当前选择对象) + /// + public bool IsSelectionClipBoxEnabled + { + get => _isSelectionClipBoxEnabled; + set + { + if (SetProperty(ref _isSelectionClipBoxEnabled, value)) + { + if (_isUpdatingClipBoxState) + { + return; + } + + if (value) + { + ActivateSelectionClipBox(); + } + else if (_clipBoxFocusMode == ClipBoxFocusMode.Selection) + { + ClearActiveClipBox(); } } } @@ -238,7 +279,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 用于剖面盒自动跟随路径切换 _pathPlanningManager.CurrentRouteSwitched += (s, e) => { - if (IsClipBoxEnabled) + if (_clipBoxFocusMode == ClipBoxFocusMode.Path) { if (_pathPlanningManager.PathEditState == PathEditState.Creating) { @@ -252,6 +293,11 @@ namespace NavisworksTransport.UI.WPF.ViewModels }; } + if (NavisApplication.ActiveDocument?.CurrentSelection != null) + { + NavisApplication.ActiveDocument.CurrentSelection.Changed += OnCurrentSelectionChanged; + } + // 初始化状态 StatusText = "插件已就绪"; @@ -333,6 +379,63 @@ namespace NavisworksTransport.UI.WPF.ViewModels #region 剖面盒功能 + private void ActivatePathClipBox() + { + SetClipBoxState(ClipBoxFocusMode.Path); + EnableClipBoxForCurrentRoute(); + } + + private void ActivateSelectionClipBox() + { + SetClipBoxState(ClipBoxFocusMode.Selection); + EnableClipBoxForCurrentSelection(); + } + + private void ClearActiveClipBox() + { + SetClipBoxState(ClipBoxFocusMode.None); + SectionClipHelper.ClearClipBox(); + LogManager.Info("[剖面盒] 已关闭"); + } + + private void SetClipBoxState(ClipBoxFocusMode mode) + { + _clipBoxFocusMode = mode; + bool pathEnabled = mode == ClipBoxFocusMode.Path; + bool selectionEnabled = mode == ClipBoxFocusMode.Selection; + + _isUpdatingClipBoxState = true; + try + { + if (_isClipBoxEnabled != pathEnabled) + { + _isClipBoxEnabled = pathEnabled; + OnPropertyChanged(nameof(IsClipBoxEnabled)); + } + + if (_isSelectionClipBoxEnabled != selectionEnabled) + { + _isSelectionClipBoxEnabled = selectionEnabled; + OnPropertyChanged(nameof(IsSelectionClipBoxEnabled)); + } + } + finally + { + _isUpdatingClipBoxState = false; + } + } + + private void OnCurrentSelectionChanged(object sender, EventArgs e) + { + if (_clipBoxFocusMode != ClipBoxFocusMode.Selection) + { + return; + } + + LogManager.Info("[剖面盒] 选择已变化,重新聚焦到当前选择对象"); + EnableClipBoxForCurrentSelection(); + } + /// /// 启用剖面盒并聚焦到当前路径 /// @@ -344,7 +447,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels if (currentRoute?.Points == null || currentRoute.Points.Count == 0) { LogManager.Warning("[剖面盒] 当前没有路径,无法设置剖面盒"); - IsClipBoxEnabled = false; + SectionClipHelper.ClearClipBox(); + SetClipBoxState(ClipBoxFocusMode.None); return; } @@ -390,13 +494,59 @@ namespace NavisworksTransport.UI.WPF.ViewModels else { LogManager.Warning("[剖面盒] 设置失败"); - IsClipBoxEnabled = false; + SectionClipHelper.ClearClipBox(); + SetClipBoxState(ClipBoxFocusMode.None); } } catch (Exception ex) { LogManager.Error($"[剖面盒] 启用失败: {ex.Message}"); - IsClipBoxEnabled = false; + SectionClipHelper.ClearClipBox(); + SetClipBoxState(ClipBoxFocusMode.None); + } + } + + /// + /// 启用剖面盒并聚焦到当前选择对象 + /// + private void EnableClipBoxForCurrentSelection() + { + try + { + var document = NavisApplication.ActiveDocument; + var selectedItems = document?.CurrentSelection?.SelectedItems?.Cast().Where(item => item != null).ToList(); + if (selectedItems == null || selectedItems.Count == 0) + { + LogManager.Warning("[剖面盒] 当前没有选中的模型对象,无法设置剖面盒"); + SectionClipHelper.ClearClipBox(); + SetClipBoxState(ClipBoxFocusMode.None); + return; + } + + SectionClipHelper.ClearClipBox(); + + bool success = SectionClipHelper.SetClipBoxByModelItems( + selectedItems, + marginInMeters: 2.0, + heightMarginInMeters: 2.0); + + if (success) + { + ViewpointHelper.FocusOnModelItems(selectedItems, ViewpointHelper.ViewpointStrategy.ModelFocus); + LogManager.Info($"[剖面盒] 已按当前选择对象启用,选择数量: {selectedItems.Count}"); + } + else + { + LogManager.Warning("[剖面盒] 按选择对象设置失败"); + SectionClipHelper.ClearClipBox(); + SetClipBoxState(ClipBoxFocusMode.None); + } + } + catch (Exception ex) + { + LogManager.Error($"[剖面盒] 按选择对象启用失败: {ex.Message}"); + SectionClipHelper.ClearClipBox(); + SetClipBoxState(ClipBoxFocusMode.None); } } diff --git a/src/UI/WPF/Views/LogisticsControlPanel.xaml b/src/UI/WPF/Views/LogisticsControlPanel.xaml index 90ac1a5..55e5a2b 100644 --- a/src/UI/WPF/Views/LogisticsControlPanel.xaml +++ b/src/UI/WPF/Views/LogisticsControlPanel.xaml @@ -146,18 +146,48 @@ NavisworksTransport 主控制面板 - 采用与其他视图一致的Navisworks 2 Margin="8,0,6,0" Foreground="#FFCCCCCC" FontSize="14"/> - - + + + + + + + + + + + + - + + + + + + diff --git a/src/Utils/SectionClipHelper.cs b/src/Utils/SectionClipHelper.cs index e74f68c..1bd7757 100644 --- a/src/Utils/SectionClipHelper.cs +++ b/src/Utils/SectionClipHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Autodesk.Navisworks.Api; namespace NavisworksTransport.Utils @@ -131,6 +132,78 @@ namespace NavisworksTransport.Utils } } + /// + /// 根据模型项集合设置剖面盒 + /// + /// 模型项集合 + /// 水平边距(米) + /// 高度边距(米,上下各延伸) + /// 是否成功设置 + public static bool SetClipBoxByModelItems( + IEnumerable modelItems, + double marginInMeters = 2.0, + double heightMarginInMeters = 2.0) + { + try + { + if (modelItems == null) + { + LogManager.Warning("[剖面盒] 模型项集合为空"); + return false; + } + + var validItems = modelItems.Where(item => item != null).ToList(); + if (validItems.Count == 0) + { + LogManager.Warning("[剖面盒] 没有可用的模型项,无法设置剖面盒"); + return false; + } + + bool hasBounds = false; + double minX = double.MaxValue, minY = double.MaxValue, minZ = double.MaxValue; + double maxX = double.MinValue, maxY = double.MinValue, maxZ = double.MinValue; + + foreach (var item in validItems) + { + BoundingBox3D bounds = item.BoundingBox(); + minX = Math.Min(minX, bounds.Min.X); + minY = Math.Min(minY, bounds.Min.Y); + minZ = Math.Min(minZ, bounds.Min.Z); + maxX = Math.Max(maxX, bounds.Max.X); + maxY = Math.Max(maxY, bounds.Max.Y); + maxZ = Math.Max(maxZ, bounds.Max.Z); + hasBounds = true; + } + + if (!hasBounds) + { + LogManager.Warning("[剖面盒] 未能从模型项中计算有效包围盒"); + return false; + } + + double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units); + double margin = marginInMeters * metersToUnits; + double heightMargin = heightMarginInMeters * metersToUnits; + + var selectionBounds = new BoundingBox3D( + new Point3D(minX, minY, minZ), + new Point3D(maxX, maxY, maxZ)); + var clipBox = ExpandBoundingBox(selectionBounds, margin, heightMargin); + + ApplyClipBox(clipBox); + + LogManager.Info($"[剖面盒] 已按选择对象设置 - 对象数: {validItems.Count}, 水平边距: {marginInMeters}m, 高度边距: {heightMarginInMeters}m, " + + $"范围: X[{clipBox.Min.X:F2}, {clipBox.Max.X:F2}], Y[{clipBox.Min.Y:F2}, {clipBox.Max.Y:F2}], Z[{clipBox.Min.Z:F2}, {clipBox.Max.Z:F2}]"); + + return true; + } + catch (Exception ex) + { + LogManager.Error($"[剖面盒] 按选择对象设置失败: {ex.Message}"); + return false; + } + } + /// /// 清除剖面盒(显示全部模型) /// 使用 JSON 字符串方式设置 Enabled = false diff --git a/src/Utils/ViewpointHelper.cs b/src/Utils/ViewpointHelper.cs index 6b4c0b0..5afb99a 100644 --- a/src/Utils/ViewpointHelper.cs +++ b/src/Utils/ViewpointHelper.cs @@ -689,6 +689,36 @@ namespace NavisworksTransport.Utils FocusOnModelItem(modelItem, profile.ViewAngleDegrees, profile.TargetViewRatio); } + public static void FocusOnModelItems(IEnumerable modelItems, ViewpointStrategy strategy) + { + if (modelItems == null) + { + throw new ArgumentNullException(nameof(modelItems)); + } + + List items = modelItems.Where(item => item != null).ToList(); + if (items.Count == 0) + { + throw new ArgumentException("至少需要一个模型元素", nameof(modelItems)); + } + + BoundingBox3D combinedBounds = items[0].BoundingBox(); + for (int i = 1; i < items.Count; i++) + { + BoundingBox3D itemBounds = items[i].BoundingBox(); + combinedBounds.Extend(itemBounds.Min); + combinedBounds.Extend(itemBounds.Max); + } + + Point3D center = new Point3D( + (combinedBounds.Min.X + combinedBounds.Max.X) / 2.0, + (combinedBounds.Min.Y + combinedBounds.Max.Y) / 2.0, + (combinedBounds.Min.Z + combinedBounds.Max.Z) / 2.0); + double targetSize = GetMaxDimension(combinedBounds); + + FocusOnPosition(center, targetSize, strategy); + } + /// /// 聚焦到多个模型元素(如碰撞中的两个对象) ///