NavisworksTransport/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs

421 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
#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;
/// <summary>
/// 是否启用剖面盒(聚焦当前路径)
/// </summary>
public bool IsClipBoxEnabled
{
get => _isClipBoxEnabled;
set
{
if (SetProperty(ref _isClipBoxEnabled, value))
{
if (value)
{
EnableClipBoxForCurrentRoute();
}
else
{
SectionClipHelper.ClearClipBox();
LogManager.Info("[剖面盒] 已关闭");
}
}
}
}
/// <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 (IsClipBoxEnabled)
{
LogManager.Info("[剖面盒] 路径已切换,重新设置剖面盒聚焦到新路径");
EnableClipBoxForCurrentRoute();
}
};
}
// 初始化状态
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
/// <summary>
/// 启用剖面盒并聚焦到当前路径
/// </summary>
private void EnableClipBoxForCurrentRoute()
{
try
{
var currentRoute = _pathPlanningManager?.CurrentRoute;
if (currentRoute?.Points == null || currentRoute.Points.Count == 0)
{
LogManager.Warning("[剖面盒] 当前没有路径,无法设置剖面盒");
IsClipBoxEnabled = false;
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("[剖面盒] 设置失败");
IsClipBoxEnabled = false;
}
}
catch (Exception ex)
{
LogManager.Error($"[剖面盒] 启用失败: {ex.Message}");
IsClipBoxEnabled = false;
}
}
/// <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
}
}