590 lines
21 KiB
C#
590 lines
21 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Autodesk.Navisworks.Api;
|
||
using NavisApplication = Autodesk.Navisworks.Api.Application;
|
||
using NavisworksTransport.Core;
|
||
using NavisworksTransport.Core.Config;
|
||
using NavisworksTransport.Utils;
|
||
|
||
namespace NavisworksTransport.UI.WPF.ViewModels
|
||
{
|
||
/// <summary>
|
||
/// 物流控制面板主ViewModel
|
||
/// </summary>
|
||
public class LogisticsControlViewModel : ViewModelBase
|
||
{
|
||
private enum ClipBoxFocusMode
|
||
{
|
||
None,
|
||
Path,
|
||
Selection
|
||
}
|
||
|
||
#region 私有字段
|
||
|
||
private string _statusText;
|
||
private bool _isProcessing = false;
|
||
private bool _showControlVisualization = true;
|
||
|
||
// UI状态管理和命令框架
|
||
private readonly UIStateManager _uiStateManager;
|
||
private readonly NavisworksTransport.Commands.CommandManager _commandManager;
|
||
private readonly PathPlanningManager _pathPlanningManager;
|
||
|
||
#endregion
|
||
|
||
#region 公共属性
|
||
|
||
|
||
/// <summary>
|
||
/// 状态文本
|
||
/// </summary>
|
||
public string StatusText
|
||
{
|
||
get => _statusText;
|
||
set => SetProperty(ref _statusText, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否正在处理中(用于统一状态栏进度条显示)
|
||
/// </summary>
|
||
public bool IsProcessing
|
||
{
|
||
get => _isProcessing;
|
||
set => SetProperty(ref _isProcessing, value);
|
||
}
|
||
|
||
private double _progressPercentage = 0;
|
||
|
||
/// <summary>
|
||
/// 进度百分比(0-100,-1表示不确定进度)
|
||
/// </summary>
|
||
public double ProgressPercentage
|
||
{
|
||
get => _progressPercentage;
|
||
set
|
||
{
|
||
if (SetProperty(ref _progressPercentage, value))
|
||
{
|
||
OnPropertyChanged(nameof(IsIndeterminateProgress));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否使用不确定进度模式
|
||
/// </summary>
|
||
public bool IsIndeterminateProgress => ProgressPercentage < 0;
|
||
|
||
/// <summary>
|
||
/// 是否显示控制点连线
|
||
/// </summary>
|
||
public bool ShowControlVisualization
|
||
{
|
||
get => _showControlVisualization;
|
||
set
|
||
{
|
||
if (SetProperty(ref _showControlVisualization, value))
|
||
{
|
||
// 更新所有路径的控制点显示
|
||
var renderPlugin = PathPointRenderPlugin.Instance;
|
||
if (renderPlugin != null)
|
||
{
|
||
lock (typeof(PathPointRenderPlugin))
|
||
{
|
||
var allVisualizations = renderPlugin.GetAllPathVisualizations();
|
||
foreach (var visualization in allVisualizations)
|
||
{
|
||
visualization.ShowControlVisualization = value;
|
||
}
|
||
}
|
||
// 触发视图刷新
|
||
if (NavisApplication.ActiveDocument?.ActiveView != null)
|
||
{
|
||
NavisApplication.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool _showPathLines = true;
|
||
private bool _showObjectSpace = false;
|
||
|
||
/// <summary>
|
||
/// 是否显示路径线/地面连线
|
||
/// </summary>
|
||
public bool ShowPathLines
|
||
{
|
||
get => _showPathLines;
|
||
set
|
||
{
|
||
if (SetProperty(ref _showPathLines, value))
|
||
{
|
||
var renderPlugin = PathPointRenderPlugin.Instance;
|
||
if (renderPlugin != null)
|
||
{
|
||
renderPlugin.ShowPathLines = value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否显示通行空间
|
||
/// </summary>
|
||
public bool ShowObjectSpace
|
||
{
|
||
get => _showObjectSpace;
|
||
set
|
||
{
|
||
if (SetProperty(ref _showObjectSpace, value))
|
||
{
|
||
var renderPlugin = PathPointRenderPlugin.Instance;
|
||
if (renderPlugin != null)
|
||
{
|
||
renderPlugin.ShowObjectSpace = value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool _isClipBoxEnabled;
|
||
private bool _isSelectionClipBoxEnabled;
|
||
private bool _isUpdatingClipBoxState;
|
||
private ClipBoxFocusMode _clipBoxFocusMode = ClipBoxFocusMode.None;
|
||
private readonly SelectionClipBoxLockState _selectionClipBoxLockState = new SelectionClipBoxLockState();
|
||
|
||
/// <summary>
|
||
/// 是否启用剖面盒(聚焦当前路径)
|
||
/// </summary>
|
||
public bool IsClipBoxEnabled
|
||
{
|
||
get => _isClipBoxEnabled;
|
||
set
|
||
{
|
||
if (SetProperty(ref _isClipBoxEnabled, value))
|
||
{
|
||
if (_isUpdatingClipBoxState)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (value)
|
||
{
|
||
ActivatePathClipBox();
|
||
}
|
||
else if (_clipBoxFocusMode == ClipBoxFocusMode.Path)
|
||
{
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 PathPointRenderPlugin 同步渲染状态
|
||
/// </summary>
|
||
private void SyncVisualizationStateFromPlugin()
|
||
{
|
||
var renderPlugin = PathPointRenderPlugin.Instance;
|
||
if (renderPlugin != null)
|
||
{
|
||
_showPathLines = renderPlugin.ShowPathLines;
|
||
_showObjectSpace = renderPlugin.ShowObjectSpace;
|
||
OnPropertyChanged(nameof(ShowPathLines));
|
||
OnPropertyChanged(nameof(ShowObjectSpace));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前路径是否可以使用路径线(仅地面路径)
|
||
/// </summary>
|
||
public bool CanUsePathLines
|
||
{
|
||
get
|
||
{
|
||
var currentRoute = _pathPlanningManager?.CurrentRoute;
|
||
if (currentRoute == null) return true;
|
||
return currentRoute.PathType == PathType.Ground;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 构造函数
|
||
|
||
public LogisticsControlViewModel() : base()
|
||
{
|
||
try
|
||
{
|
||
// 获取UI状态管理器和命令管理器实例
|
||
_uiStateManager = UIStateManager.Instance;
|
||
_commandManager = NavisworksTransport.Commands.CommandManager.Instance;
|
||
_pathPlanningManager = PathPlanningManager.Instance;
|
||
|
||
// 验证关键组件是否正常初始化
|
||
if (_uiStateManager == null)
|
||
{
|
||
LogManager.Error("UIStateManager初始化失败");
|
||
throw new InvalidOperationException("UIStateManager初始化失败");
|
||
}
|
||
|
||
if (_commandManager == null)
|
||
{
|
||
LogManager.Error("CommandManager初始化失败");
|
||
throw new InvalidOperationException("CommandManager初始化失败");
|
||
}
|
||
|
||
// 订阅可视化状态变更事件,同步按钮状态
|
||
if (_pathPlanningManager != null)
|
||
{
|
||
_pathPlanningManager.VisualizationStateChanged += (s, e) =>
|
||
{
|
||
// 通知 CanUsePathLines 变更(影响LN按钮的启用状态)
|
||
OnPropertyChanged(nameof(CanUsePathLines));
|
||
|
||
// 从 PathPointRenderPlugin 同步最新的渲染状态
|
||
SyncVisualizationStateFromPlugin();
|
||
};
|
||
|
||
// 订阅当前路径切换事件(总是触发,包括UI调用SetCurrentRoute时)
|
||
// 用于剖面盒自动跟随路径切换
|
||
_pathPlanningManager.CurrentRouteSwitched += (s, e) =>
|
||
{
|
||
if (_clipBoxFocusMode == ClipBoxFocusMode.Path)
|
||
{
|
||
if (_pathPlanningManager.PathEditState == PathEditState.Creating)
|
||
{
|
||
LogManager.Info("[剖面盒] 当前处于新建路径流程,跳过自动跟随");
|
||
return;
|
||
}
|
||
|
||
LogManager.Info("[剖面盒] 路径已切换,重新设置剖面盒聚焦到新路径");
|
||
EnableClipBoxForCurrentRoute();
|
||
}
|
||
};
|
||
}
|
||
|
||
if (NavisApplication.ActiveDocument?.CurrentSelection != null)
|
||
{
|
||
NavisApplication.ActiveDocument.CurrentSelection.Changed += OnCurrentSelectionChanged;
|
||
}
|
||
|
||
// 初始化状态
|
||
StatusText = "插件已就绪";
|
||
|
||
LogManager.Info("LogisticsControlViewModel构造函数执行完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"LogisticsControlViewModel构造函数异常: {ex.Message}", ex);
|
||
|
||
// 在构造函数中尽量保证对象处于可用状态
|
||
StatusText = "初始化失败,请检查日志";
|
||
throw;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 统一状态栏方法
|
||
|
||
/// <summary>
|
||
/// 更新统一状态栏信息(简单版本,不显示进度条)
|
||
/// </summary>
|
||
/// <param name="message">状态消息</param>
|
||
public void UpdateStatus(string message)
|
||
{
|
||
SafeExecute(() =>
|
||
{
|
||
StatusText = message ?? "就绪";
|
||
IsProcessing = false; // 简单状态更新不显示进度条
|
||
}, "更新统一状态栏", runOnUIThread: true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新统一状态栏信息(完整版本)
|
||
/// </summary>
|
||
/// <param name="message">状态消息</param>
|
||
/// <param name="progress">进度值 (0-100),默认不显示进度条</param>
|
||
/// <param name="isProcessing">是否正在处理中(显示进度条动画)</param>
|
||
public void UpdateStatus(string message, double progress = -1, bool? isProcessing = null)
|
||
{
|
||
SafeExecute(() =>
|
||
{
|
||
StatusText = message ?? "就绪";
|
||
if (progress >= 0)
|
||
{
|
||
ProgressPercentage = Math.Max(0, Math.Min(100, progress));
|
||
}
|
||
if (isProcessing.HasValue)
|
||
{
|
||
IsProcessing = isProcessing.Value;
|
||
}
|
||
}, "更新统一状态栏", runOnUIThread: true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新统一状态栏信息(支持百分比进度)
|
||
/// </summary>
|
||
/// <param name="message">状态消息</param>
|
||
/// <param name="percentage">进度百分比 (0-100)</param>
|
||
public void UpdateStatus(string message, double percentage)
|
||
{
|
||
SafeExecute(() =>
|
||
{
|
||
StatusText = message ?? "就绪";
|
||
ProgressPercentage = Math.Max(0, Math.Min(100, percentage));
|
||
IsProcessing = true; // 有百分比进度时显示进度条
|
||
}, "更新统一状态栏(百分比模式)", runOnUIThread: true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置状态栏到默认状态
|
||
/// </summary>
|
||
public void ResetStatus()
|
||
{
|
||
UpdateStatus("插件已就绪");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 剖面盒功能
|
||
|
||
private void ActivatePathClipBox()
|
||
{
|
||
SetClipBoxState(ClipBoxFocusMode.Path);
|
||
EnableClipBoxForCurrentRoute();
|
||
}
|
||
|
||
private void ActivateSelectionClipBox()
|
||
{
|
||
SetClipBoxState(ClipBoxFocusMode.Selection);
|
||
EnableClipBoxForCurrentSelection();
|
||
}
|
||
|
||
private void ClearActiveClipBox()
|
||
{
|
||
SetClipBoxState(ClipBoxFocusMode.None);
|
||
_selectionClipBoxLockState.Clear();
|
||
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;
|
||
}
|
||
|
||
if (!_selectionClipBoxLockState.ShouldRefreshOnSelectionChanged(isSelectionClipBoxMode: true))
|
||
{
|
||
LogManager.Info($"[剖面盒] 选择已变化,但当前剖面盒保持锁定,锁定对象数: {_selectionClipBoxLockState.LockedSelectionCount}");
|
||
return;
|
||
}
|
||
|
||
LogManager.Info("[剖面盒] 选择已变化,重新聚焦到当前选择对象");
|
||
EnableClipBoxForCurrentSelection();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启用剖面盒并聚焦到当前路径
|
||
/// </summary>
|
||
private void EnableClipBoxForCurrentRoute()
|
||
{
|
||
try
|
||
{
|
||
var currentRoute = _pathPlanningManager?.CurrentRoute;
|
||
if (currentRoute?.Points == null || currentRoute.Points.Count == 0)
|
||
{
|
||
LogManager.Warning("[剖面盒] 当前没有路径,无法设置剖面盒");
|
||
SectionClipHelper.ClearClipBox();
|
||
SetClipBoxState(ClipBoxFocusMode.None);
|
||
return;
|
||
}
|
||
|
||
// 获取虚拟物体默认高度(米)
|
||
double objectHeightInMeters = ConfigManager.Instance.Current.PathEditing.ObjectHeightMeters;
|
||
|
||
// ========== 第1步:处理路径,计算包含虚拟物体高度的包围盒 ==========
|
||
|
||
// 转换路径点,并根据路径类型调整Z值范围(包含虚拟物体高度)
|
||
var adjustedPoints = currentRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
|
||
|
||
// 根据路径类型,添加包含虚拟物体高度的额外点
|
||
if (currentRoute.PathType == PathType.Ground)
|
||
{
|
||
// 地面路径:在路径点上方添加物体高度点
|
||
foreach (var p in currentRoute.Points)
|
||
{
|
||
adjustedPoints.Add(new Point3D(p.X, p.Y, p.Z + objectHeightInMeters));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 空中路径:在路径点下方添加物体高度点
|
||
foreach (var p in currentRoute.Points)
|
||
{
|
||
adjustedPoints.Add(new Point3D(p.X, p.Y, p.Z - objectHeightInMeters));
|
||
}
|
||
}
|
||
|
||
// ========== 第2步:先清除旧的剖面盒,再设置新的 ==========
|
||
SectionClipHelper.ClearClipBox();
|
||
|
||
// ========== 第3步:统一扩展(水平4米,高度2米)==========
|
||
bool success = SectionClipHelper.SetClipBoxByPath(adjustedPoints,
|
||
marginInMeters: 4.0, // 水平方向扩展4米
|
||
heightMarginInMeters: 2.0 // 高度方向上下各扩展2米
|
||
);
|
||
|
||
if (success)
|
||
{
|
||
LogManager.Info($"[剖面盒] 已启用,路径点数量: {currentRoute.Points.Count}, 各方向扩展: 2m");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("[剖面盒] 设置失败");
|
||
SectionClipHelper.ClearClipBox();
|
||
SetClipBoxState(ClipBoxFocusMode.None);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[剖面盒] 启用失败: {ex.Message}");
|
||
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("[剖面盒] 当前没有选中的模型对象,无法设置剖面盒");
|
||
_selectionClipBoxLockState.Clear();
|
||
SectionClipHelper.ClearClipBox();
|
||
SetClipBoxState(ClipBoxFocusMode.None);
|
||
return;
|
||
}
|
||
|
||
_selectionClipBoxLockState.LockSelection(selectedItems);
|
||
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("[剖面盒] 按选择对象设置失败");
|
||
_selectionClipBoxLockState.Clear();
|
||
SectionClipHelper.ClearClipBox();
|
||
SetClipBoxState(ClipBoxFocusMode.None);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[剖面盒] 按选择对象启用失败: {ex.Message}");
|
||
_selectionClipBoxLockState.Clear();
|
||
SectionClipHelper.ClearClipBox();
|
||
SetClipBoxState(ClipBoxFocusMode.None);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算点列表的包围盒
|
||
/// </summary>
|
||
private (Point3D Center, double SizeX, double SizeY, double SizeZ) CalculatePointsBoundingBox(List<Point3D> points)
|
||
{
|
||
double minX = double.MaxValue, minY = double.MaxValue, minZ = double.MaxValue;
|
||
double maxX = double.MinValue, maxY = double.MinValue, maxZ = double.MinValue;
|
||
|
||
foreach (var p in points)
|
||
{
|
||
minX = Math.Min(minX, p.X);
|
||
minY = Math.Min(minY, p.Y);
|
||
minZ = Math.Min(minZ, p.Z);
|
||
maxX = Math.Max(maxX, p.X);
|
||
maxY = Math.Max(maxY, p.Y);
|
||
maxZ = Math.Max(maxZ, p.Z);
|
||
}
|
||
|
||
var center = new Point3D((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
|
||
return (center, maxX - minX, maxY - minY, maxZ - minZ);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|