Add selection-based clip box controls

This commit is contained in:
tian 2026-04-11 22:10:21 +08:00
parent 18b530ef5f
commit bbd72345c9
4 changed files with 299 additions and 16 deletions

View File

@ -14,6 +14,13 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// </summary>
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;
/// <summary>
/// 是否启用剖面盒(聚焦当前路径)
@ -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();
}
}
}
}
/// <summary>
/// 是否启用剖面盒(聚焦当前选择对象)
/// </summary>
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();
}
/// <summary>
/// 启用剖面盒并聚焦到当前路径
/// </summary>
@ -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);
}
}
/// <summary>
/// 启用剖面盒并聚焦到当前选择对象
/// </summary>
private void EnableClipBoxForCurrentSelection()
{
try
{
var document = NavisApplication.ActiveDocument;
var selectedItems = document?.CurrentSelection?.SelectedItems?.Cast<ModelItem>().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);
}
}

View File

@ -146,18 +146,48 @@ NavisworksTransport 主控制面板 - 采用与其他视图一致的Navisworks 2
Margin="8,0,6,0"
Foreground="#FFCCCCCC"
FontSize="14"/>
<!-- 剖面盒开关 -->
<!-- 选择对象剖面盒开关 -->
<ToggleButton IsChecked="{Binding IsSelectionClipBoxEnabled}"
ToolTip="剖面盒(聚焦当前选择对象)"
Width="28" Height="24"
Margin="2,0">
<Viewbox Width="18" Height="18" Stretch="Uniform">
<Canvas Width="20" Height="20">
<Path Data="M 2,6 L 14,6 L 14,18 L 2,18 Z M 2,6 L 6,2 L 18,2 L 14,6 M 14,18 L 18,14 L 18,2 L 14,6"
Stroke="{Binding IsSelectionClipBoxEnabled, Converter={StaticResource BoolToBrushConverter}, ConverterParameter=Green}"
StrokeThickness="1.3"
Fill="Transparent"/>
<Rectangle Canvas.Left="6"
Canvas.Top="10"
Width="4"
Height="4"
Stroke="{Binding IsSelectionClipBoxEnabled, Converter={StaticResource BoolToBrushConverter}, ConverterParameter=Green}"
StrokeThickness="1.2"
Fill="Transparent"/>
</Canvas>
</Viewbox>
</ToggleButton>
<!-- 路径剖面盒开关 -->
<ToggleButton IsChecked="{Binding IsClipBoxEnabled}"
ToolTip="剖面盒(聚焦当前路径)"
Width="28" Height="24"
Margin="2,0">
<Path Data="M 2,6 L 14,6 L 14,18 L 2,18 Z M 2,6 L 6,2 L 18,2 L 14,6 M 14,18 L 18,14 L 18,2 L 14,6"
Stroke="{Binding IsClipBoxEnabled, Converter={StaticResource BoolToBrushConverter}, ConverterParameter=Red}"
StrokeThickness="1.5"
Fill="Transparent"
Stretch="Uniform"
Margin="2"/>
<Viewbox Width="18" Height="18" Stretch="Uniform">
<Canvas Width="20" Height="20">
<Path Data="M 2,6 L 14,6 L 14,18 L 2,18 Z M 2,6 L 6,2 L 18,2 L 14,6 M 14,18 L 18,14 L 18,2 L 14,6"
Stroke="{Binding IsClipBoxEnabled, Converter={StaticResource BoolToBrushConverter}, ConverterParameter=Green}"
StrokeThickness="1.3"
Fill="Transparent"/>
<Polyline Points="5.5,13 7.2,11 8.1,12 10.5,10"
Stroke="{Binding IsClipBoxEnabled, Converter={StaticResource BoolToBrushConverter}, ConverterParameter=Green}"
StrokeThickness="1.2"
StrokeLineJoin="Round"
StrokeStartLineCap="Round"
StrokeEndLineCap="Round"/>
</Canvas>
</Viewbox>
</ToggleButton>
</StackPanel>

View File

@ -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
}
}
/// <summary>
/// 根据模型项集合设置剖面盒
/// </summary>
/// <param name="modelItems">模型项集合</param>
/// <param name="marginInMeters">水平边距(米)</param>
/// <param name="heightMarginInMeters">高度边距(米,上下各延伸)</param>
/// <returns>是否成功设置</returns>
public static bool SetClipBoxByModelItems(
IEnumerable<ModelItem> 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;
}
}
/// <summary>
/// 清除剖面盒(显示全部模型)
/// 使用 JSON 字符串方式设置 Enabled = false

View File

@ -689,6 +689,36 @@ namespace NavisworksTransport.Utils
FocusOnModelItem(modelItem, profile.ViewAngleDegrees, profile.TargetViewRatio);
}
public static void FocusOnModelItems(IEnumerable<ModelItem> modelItems, ViewpointStrategy strategy)
{
if (modelItems == null)
{
throw new ArgumentNullException(nameof(modelItems));
}
List<ModelItem> 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);
}
/// <summary>
/// 聚焦到多个模型元素(如碰撞中的两个对象)
/// </summary>