2898 lines
112 KiB
C#
2898 lines
112 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.Collections.Specialized;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Input;
|
||
using Autodesk.Navisworks.Api;
|
||
using Autodesk.Navisworks.Api.Clash;
|
||
using NavisworksTransport.Core;
|
||
using NavisworksTransport.Commands;
|
||
using NavisworksTransport.UI.WPF.Collections;
|
||
using NavisworksTransport.Utils;
|
||
using Color = Autodesk.Navisworks.Api.Color;
|
||
|
||
namespace NavisworksTransport.UI.WPF.ViewModels
|
||
{
|
||
/// <summary>
|
||
/// 碰撞报告数据
|
||
/// </summary>
|
||
public class CollisionReportData
|
||
{
|
||
public bool IsValid { get; set; }
|
||
public string ErrorMessage { get; set; } = string.Empty;
|
||
public DateTime GeneratedTime { get; set; }
|
||
public string AnimatedObjectName { get; set; } = string.Empty;
|
||
public string PathName { get; set; } = string.Empty;
|
||
public int TotalTests { get; set; }
|
||
public int TotalCollisions { get; set; }
|
||
public List<CollisionTestInfo> Tests { get; set; } = new List<CollisionTestInfo>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞测试信息
|
||
/// </summary>
|
||
public class CollisionTestInfo
|
||
{
|
||
public string TestName { get; set; } = string.Empty;
|
||
public string TestType { get; set; } = string.Empty;
|
||
public double Tolerance { get; set; }
|
||
public int CollisionCount { get; set; }
|
||
public string Status { get; set; } = string.Empty;
|
||
public List<CollisionDetailInfo> Collisions { get; set; } = new List<CollisionDetailInfo>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 碰撞详细信息
|
||
/// </summary>
|
||
public class CollisionDetailInfo
|
||
{
|
||
public string CollisionId { get; set; } = string.Empty;
|
||
public string Object1Name { get; set; } = string.Empty;
|
||
public string Object1Position { get; set; } = string.Empty;
|
||
public string Object2Name { get; set; } = string.Empty;
|
||
public string Object2Position { get; set; } = string.Empty;
|
||
public string CollisionCenter { get; set; } = string.Empty;
|
||
public double Distance { get; set; }
|
||
public string Status { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手工指定的碰撞检测对象
|
||
/// </summary>
|
||
public class ManualCollisionTargetViewModel
|
||
{
|
||
public ManualCollisionTargetViewModel(ModelItem modelItem, string displayName, string modelPath)
|
||
{
|
||
ModelItem = modelItem ?? throw new ArgumentNullException(nameof(modelItem));
|
||
DisplayName = displayName;
|
||
ModelPath = modelPath;
|
||
InstanceGuid = modelItem.InstanceGuid;
|
||
}
|
||
|
||
public ModelItem ModelItem { get; }
|
||
public string DisplayName { get; }
|
||
public string ModelPath { get; }
|
||
public Guid InstanceGuid { get; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// ClashDetective碰撞检测结果视图模型
|
||
/// </summary>
|
||
public class ClashDetectiveResultViewModel
|
||
{
|
||
private readonly Action _refreshCallback;
|
||
|
||
public ClashDetectiveResultViewModel(ClashDetectiveResultRecord record, Action refreshCallback)
|
||
{
|
||
Record = record ?? throw new ArgumentNullException(nameof(record));
|
||
_refreshCallback = refreshCallback ?? throw new ArgumentNullException(nameof(refreshCallback));
|
||
ViewCommand = new RelayCommand(() => ExecuteView());
|
||
ReportCommand = new RelayCommand(() => ExecuteReport());
|
||
DeleteCommand = new RelayCommand(() => ExecuteDelete());
|
||
}
|
||
|
||
public ClashDetectiveResultRecord Record { get; }
|
||
public string TestTimeDisplay => Record.TestTime.ToString("MM-dd HH:mm:ss");
|
||
public string CollisionCountDisplay => $"{Record.CollisionCount}个";
|
||
public ICommand ViewCommand { get; }
|
||
public ICommand ReportCommand { get; }
|
||
public ICommand DeleteCommand { get; }
|
||
|
||
private void ExecuteView()
|
||
{
|
||
// 高亮显示该次检测的碰撞对象
|
||
var clashIntegration = ClashDetectiveIntegration.Instance;
|
||
ModelHighlightHelper.HighlightClashDetectiveResults(Record.TestName, clashIntegration.GetCurrentPathClashResults);
|
||
}
|
||
|
||
private void ExecuteReport()
|
||
{
|
||
// 打开该次检测的详细报告
|
||
try
|
||
{
|
||
var testName = Record.TestName;
|
||
LogManager.Info($"[历史报告] 开始生成历史碰撞报告: {testName}");
|
||
|
||
// 直接使用现有的报告生成命令,但指定测试名称
|
||
var command = GenerateCollisionReportCommand.CreateComprehensive(autoHighlight: false);
|
||
command.SetTestName(testName);
|
||
command.ExecuteAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[历史报告] 生成历史报告失败: {ex.Message}", ex);
|
||
System.Windows.MessageBox.Show($"生成历史报告失败: {ex.Message}", "错误",
|
||
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
private void ExecuteDelete()
|
||
{
|
||
// 删除该次检测记录
|
||
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
|
||
if (pathDatabase != null)
|
||
{
|
||
pathDatabase.DeleteClashDetectiveResult(Record.Id);
|
||
// 通过回调刷新列表
|
||
_refreshCallback?.Invoke();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动画控制视图模型
|
||
/// 专门处理路径动画播放和碰撞检测功能
|
||
/// </summary>
|
||
public class AnimationControlViewModel : ViewModelBase, IDisposable
|
||
{
|
||
#region 私有字段
|
||
|
||
private readonly Core.Animation.PathAnimationManager _pathAnimationManager;
|
||
private readonly ClashDetectiveIntegration _clashIntegration;
|
||
private readonly UIStateManager _uiStateManager;
|
||
|
||
// 动画参数相关字段(从配置初始化)
|
||
private ObservableCollection<int> _availableFrameRates;
|
||
private int _selectedFrameRate;
|
||
private double _animationDuration;
|
||
private bool _canStartAnimation = true;
|
||
private bool _canPauseAnimation = false;
|
||
private bool _canStopAnimation = false;
|
||
|
||
// 碰撞检测相关字段
|
||
private CollisionReportResult _lastGeneratedReport; // 缓存最后生成的报告
|
||
|
||
// 碰撞检测参数字段(从配置初始化)
|
||
private double _detectionGap; // 检测间隙(米)
|
||
private int _animationFrameRate; // 动画帧率(FPS)
|
||
|
||
|
||
// 当前选中路径
|
||
private PathRouteViewModel _currentPathRoute;
|
||
|
||
// 移动物体相关字段
|
||
private ModelItem _selectedAnimatedObject;
|
||
private string _selectedAnimatedObjectName;
|
||
private bool _hasSelectedAnimatedObject = false;
|
||
private bool _canGenerateAnimation = false;
|
||
|
||
// 虚拟车辆相关字段
|
||
private bool _useVirtualVehicle = false; // 使用虚拟车辆
|
||
private double _virtualVehicleLength; // 虚拟车辆长度(米)
|
||
private double _virtualVehicleWidth; // 虚拟车辆宽度(米)
|
||
private double _virtualVehicleHeight; // 虚拟车辆高度(米)
|
||
|
||
// 手工碰撞对象相关字段
|
||
private bool _isManualCollisionTargetEnabled = false;
|
||
private ObservableCollection<ManualCollisionTargetViewModel> _manualCollisionTargets;
|
||
private string _manualCollisionTargetSummary = "未指定碰撞检测对象";
|
||
private DateTime? _manualTargetsLastSyncTime;
|
||
private const int ManualCollisionTargetLimit = 60;
|
||
private const string ManualTargetsHighlightCategory = ModelHighlightHelper.ManualTargetsCategory;
|
||
private const string CollisionResultsHighlightCategory = ModelHighlightHelper.CollisionResultsCategory;
|
||
|
||
#endregion
|
||
|
||
#region 公共属性
|
||
|
||
|
||
/// <summary>
|
||
/// 可用帧率集合
|
||
/// </summary>
|
||
public ObservableCollection<int> AvailableFrameRates
|
||
{
|
||
get => _availableFrameRates;
|
||
set => SetProperty(ref _availableFrameRates, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选中的帧率
|
||
/// </summary>
|
||
public int SelectedFrameRate
|
||
{
|
||
get => _selectedFrameRate;
|
||
set
|
||
{
|
||
if (SetProperty(ref _selectedFrameRate, value))
|
||
{
|
||
// 同步更新AnimationFrameRate字段
|
||
_animationFrameRate = value;
|
||
|
||
// 帧率变化时,步长需要重新计算(步长=路径长度/(帧率*时长))
|
||
OnPropertyChanged(nameof(CollisionDetectionAccuracy));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动画持续时间(秒)
|
||
/// </summary>
|
||
public double AnimationDuration
|
||
{
|
||
get => _animationDuration;
|
||
set
|
||
{
|
||
if (SetProperty(ref _animationDuration, value))
|
||
{
|
||
// 动画时长变化时,步长和速度需要重新计算
|
||
OnPropertyChanged(nameof(CollisionDetectionAccuracy));
|
||
OnPropertyChanged(nameof(MovementSpeed));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 是否可以开始动画
|
||
/// </summary>
|
||
public bool CanStartAnimation
|
||
{
|
||
get => _canStartAnimation;
|
||
set => SetProperty(ref _canStartAnimation, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否可以暂停动画
|
||
/// </summary>
|
||
public bool CanPauseAnimation
|
||
{
|
||
get => _canPauseAnimation;
|
||
set => SetProperty(ref _canPauseAnimation, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否可以停止动画
|
||
/// </summary>
|
||
public bool CanStopAnimation
|
||
{
|
||
get => _canStopAnimation;
|
||
set => SetProperty(ref _canStopAnimation, value);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 是否有碰撞检测结果
|
||
/// </summary>
|
||
private bool _hasClashDetectiveResults;
|
||
public bool HasClashDetectiveResults
|
||
{
|
||
get => _hasClashDetectiveResults;
|
||
set
|
||
{
|
||
if (SetProperty(ref _hasClashDetectiveResults, value))
|
||
{
|
||
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 碰撞检测精度(米) - 步长,根据路径长度、帧率和时长计算得出
|
||
/// 如果无法计算则返回0,表示数据不完整
|
||
/// </summary>
|
||
public double CollisionDetectionAccuracy
|
||
{
|
||
get
|
||
{
|
||
var pathLength = GetPathLength();
|
||
if (pathLength > 0 && _animationFrameRate > 0 && _animationDuration > 0)
|
||
{
|
||
// 步长 = 路径长度 / (帧率 * 时长)
|
||
return Math.Round(pathLength / (_animationFrameRate * _animationDuration), 3);
|
||
}
|
||
|
||
// 无法计算时返回0
|
||
// 注意:如果路径为空(pathLength==0),这是正常的初始状态,不输出警告
|
||
// 只有在配置参数异常时才输出警告
|
||
if (pathLength > 0 && (_animationFrameRate <= 0 || _animationDuration <= 0))
|
||
{
|
||
LogManager.Warning($"[AnimationControlViewModel] 无法计算碰撞检测精度 - 路径长度:{pathLength}m, 帧率:{_animationFrameRate}fps, 时长:{_animationDuration}s");
|
||
}
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 运动速度(米/秒) - 根据路径长度和时长计算得出
|
||
/// 如果无法计算则返回0,表示数据不完整
|
||
/// </summary>
|
||
public double MovementSpeed
|
||
{
|
||
get
|
||
{
|
||
var pathLength = GetPathLength();
|
||
if (pathLength > 0 && _animationDuration > 0)
|
||
{
|
||
// 速度 = 路径长度 / 时长
|
||
return Math.Round(pathLength / _animationDuration, 2);
|
||
}
|
||
|
||
// 无法计算时返回0
|
||
// 注意:如果路径为空(pathLength==0),这是正常的初始状态,不输出警告
|
||
// 只有在配置参数异常时才输出警告
|
||
if (pathLength > 0 && _animationDuration <= 0)
|
||
{
|
||
LogManager.Warning($"[AnimationControlViewModel] 无法计算运动速度 - 路径长度:{pathLength}m, 时长:{_animationDuration}s");
|
||
}
|
||
return 0;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 路径长度(米) - 计算得出
|
||
/// </summary>
|
||
public double PathLength
|
||
{
|
||
get
|
||
{
|
||
return GetPathLength();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测间隙(米)
|
||
/// </summary>
|
||
public double DetectionGap
|
||
{
|
||
get => _detectionGap;
|
||
set
|
||
{
|
||
// 限制精度到2位小数
|
||
var roundedValue = Math.Round(value, 2);
|
||
SetProperty(ref _detectionGap, roundedValue);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 动画帧率(FPS)
|
||
/// </summary>
|
||
public int AnimationFrameRate
|
||
{
|
||
get => _animationFrameRate;
|
||
set
|
||
{
|
||
if (SetProperty(ref _animationFrameRate, value))
|
||
{
|
||
// 帧率变化时,步长需要重新计算
|
||
OnPropertyChanged(nameof(CollisionDetectionAccuracy));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 当前选中的路径
|
||
/// </summary>
|
||
public PathRouteViewModel CurrentPathRoute
|
||
{
|
||
get => _currentPathRoute;
|
||
set
|
||
{
|
||
if (SetProperty(ref _currentPathRoute, value))
|
||
{
|
||
// 路径改变时,步长、速度和路径长度需要重新计算
|
||
OnPropertyChanged(nameof(CollisionDetectionAccuracy));
|
||
OnPropertyChanged(nameof(MovementSpeed));
|
||
OnPropertyChanged(nameof(PathLength));
|
||
UpdateCanGenerateAnimation();
|
||
|
||
// 路径改变时,刷新ClashDetective结果列表
|
||
RefreshClashDetectiveResultsList();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选中的移动物体
|
||
/// </summary>
|
||
public ModelItem SelectedAnimatedObject
|
||
{
|
||
get => _selectedAnimatedObject;
|
||
set
|
||
{
|
||
if (SetProperty(ref _selectedAnimatedObject, value))
|
||
{
|
||
UpdateAnimatedObjectInfo();
|
||
UpdateCanGenerateAnimation();
|
||
|
||
// 预计算排除列表(异步)
|
||
_ = PrecomputeCollisionExclusionsAsync(value);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 选中的移动物体名称
|
||
/// </summary>
|
||
public string SelectedAnimatedObjectName
|
||
{
|
||
get => _selectedAnimatedObjectName;
|
||
set => SetProperty(ref _selectedAnimatedObjectName, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否已选择移动物体
|
||
/// </summary>
|
||
public bool HasSelectedAnimatedObject
|
||
{
|
||
get => _hasSelectedAnimatedObject;
|
||
set => SetProperty(ref _hasSelectedAnimatedObject, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否可以生成动画
|
||
/// </summary>
|
||
public bool CanGenerateAnimation
|
||
{
|
||
get => _canGenerateAnimation;
|
||
set => SetProperty(ref _canGenerateAnimation, value);
|
||
}
|
||
|
||
#region 虚拟车辆属性
|
||
|
||
/// <summary>
|
||
/// 是否使用选择的模型物体
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 是否使用虚拟车辆
|
||
/// </summary>
|
||
public bool UseVirtualVehicle
|
||
{
|
||
get => _useVirtualVehicle;
|
||
set
|
||
{
|
||
if (SetProperty(ref _useVirtualVehicle, value))
|
||
{
|
||
// ✨ 模式切换清理:切换到虚拟车辆模式时,移除选择物体的动画数据
|
||
if (value)
|
||
{
|
||
try
|
||
{
|
||
// 只有在当前动画对象不是虚拟车辆时才执行重置
|
||
if (_pathAnimationManager != null &&
|
||
_pathAnimationManager.AnimatedObject != null &&
|
||
!VirtualVehicleManager.Instance.IsVirtualVehicleActive)
|
||
{
|
||
_pathAnimationManager.RestoreObjectToCADPosition();
|
||
_pathAnimationManager.ClearAnimationResults(); // 清理手动选择物体留下的动画数据
|
||
LogManager.Info("已切换到虚拟车辆模式,将之前的手动选择物体归位并清理动画数据");
|
||
}
|
||
|
||
// 显示虚拟车辆,并同步到起点
|
||
LogManager.Info("正在显示虚拟车辆到起点...");
|
||
VirtualVehicleManager.Instance.ShowVirtualVehicle(
|
||
VirtualVehicleLength, VirtualVehicleWidth, VirtualVehicleHeight);
|
||
|
||
var vModel = VirtualVehicleManager.Instance.CurrentVirtualVehicle;
|
||
if (vModel != null && CurrentPathRoute != null && CurrentPathRoute.Points != null)
|
||
{
|
||
var points = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
|
||
_pathAnimationManager?.MoveVehicleToPathStart(vModel, points);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"切换到虚拟车辆模式失败: {ex.Message}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// ✨ 模式切换清理:切换到手动选择模式时,隐藏虚拟车辆并清理数据
|
||
try
|
||
{
|
||
// 不删除虚拟车辆,只隐藏它
|
||
VirtualVehicleManager.Instance.HideVirtualVehicle();
|
||
_pathAnimationManager?.ClearAnimationResults(); // 清理虚拟车辆留下的动画数据
|
||
LogManager.Info("已切换到手动选择模式,自动隐藏虚拟车辆并清理动画数据");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"隐藏虚拟车辆失败: {ex.Message}");
|
||
}
|
||
}
|
||
UpdateCanGenerateAnimation();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 虚拟车辆长度(米)- 只读,从路径编辑同步
|
||
/// </summary>
|
||
public double VirtualVehicleLength
|
||
{
|
||
get => _virtualVehicleLength;
|
||
private set => SetProperty(ref _virtualVehicleLength, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 虚拟车辆宽度(米)- 只读,从路径编辑同步
|
||
/// </summary>
|
||
public double VirtualVehicleWidth
|
||
{
|
||
get => _virtualVehicleWidth;
|
||
private set => SetProperty(ref _virtualVehicleWidth, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 虚拟车辆高度(米)- 只读,从路径编辑同步
|
||
/// </summary>
|
||
public double VirtualVehicleHeight
|
||
{
|
||
get => _virtualVehicleHeight;
|
||
private set => SetProperty(ref _virtualVehicleHeight, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置虚拟车辆参数(由主ViewModel调用)
|
||
/// </summary>
|
||
public void SetVirtualVehicleParameters(double length, double width, double height)
|
||
{
|
||
VirtualVehicleLength = length;
|
||
VirtualVehicleWidth = width;
|
||
VirtualVehicleHeight = height;
|
||
|
||
LogManager.Info($"[AnimationControlViewModel] 虚拟车辆参数已更新: {length:F1}m × {width:F1}m × {height:F1}m");
|
||
|
||
// 如果当前使用虚拟车辆模式,更新生成动画的可用状态
|
||
if (UseVirtualVehicle)
|
||
{
|
||
UpdateCanGenerateAnimation();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 手工碰撞对象属性
|
||
|
||
public bool IsManualCollisionTargetEnabled
|
||
{
|
||
get => _isManualCollisionTargetEnabled;
|
||
set
|
||
{
|
||
if (SetProperty(ref _isManualCollisionTargetEnabled, value))
|
||
{
|
||
HandleManualCollisionModeChanged();
|
||
}
|
||
}
|
||
}
|
||
|
||
public ObservableCollection<ManualCollisionTargetViewModel> ManualCollisionTargets => _manualCollisionTargets;
|
||
|
||
public string ManualCollisionTargetSummary
|
||
{
|
||
get => _manualCollisionTargetSummary;
|
||
set => SetProperty(ref _manualCollisionTargetSummary, value);
|
||
}
|
||
|
||
public bool HasManualCollisionTargets => _manualCollisionTargets?.Count > 0;
|
||
|
||
public bool IsManualTargetModeActive => IsManualCollisionTargetEnabled && HasManualCollisionTargets;
|
||
|
||
#endregion
|
||
|
||
#region ClashDetective结果管理
|
||
|
||
private ObservableCollection<ClashDetectiveResultViewModel> _clashDetectiveResults = new ObservableCollection<ClashDetectiveResultViewModel>();
|
||
|
||
public ObservableCollection<ClashDetectiveResultViewModel> ClashDetectiveResults => _clashDetectiveResults;
|
||
|
||
public ICommand RefreshClashDetectiveResultsCommand { get; private set; }
|
||
|
||
#endregion
|
||
|
||
#region 媒体控制属性
|
||
|
||
/// <summary>
|
||
/// 时间轴位置(0-总帧数)
|
||
/// </summary>
|
||
public double TimelinePosition
|
||
{
|
||
get => _pathAnimationManager?.CurrentFrame ?? 0;
|
||
set
|
||
{
|
||
if (_pathAnimationManager != null)
|
||
{
|
||
_pathAnimationManager.SeekToFrame((int)Math.Round(value));
|
||
}
|
||
OnPropertyChanged();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 时间轴持续时间(总帧数)
|
||
/// </summary>
|
||
public double TimelineDuration => _pathAnimationManager?.TotalFrames - 1 ?? 0;
|
||
|
||
/// <summary>
|
||
/// 当前时间显示(格式化)
|
||
/// </summary>
|
||
public string CurrentTimeDisplay
|
||
{
|
||
get
|
||
{
|
||
if (_pathAnimationManager == null) return "00:00";
|
||
|
||
var currentSeconds = (_pathAnimationManager.CurrentFrame / (double)(_pathAnimationManager.TotalFrames - 1)) * _animationDuration;
|
||
return FormatTime(currentSeconds);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 总时长显示(格式化)
|
||
/// </summary>
|
||
public string TotalTimeDisplay => FormatTime(_animationDuration);
|
||
|
||
/// <summary>
|
||
/// 帧信息显示
|
||
/// </summary>
|
||
public string FrameInfoDisplay
|
||
{
|
||
get
|
||
{
|
||
if (_pathAnimationManager == null) return "0/0";
|
||
return $"{_pathAnimationManager.CurrentFrame + 1}/{_pathAnimationManager.TotalFrames}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否正在正向播放
|
||
/// </summary>
|
||
public bool IsPlayingForward => _pathAnimationManager?.PlaybackDirection == 1 &&
|
||
_pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;
|
||
|
||
/// <summary>
|
||
/// 是否正在反向播放
|
||
/// </summary>
|
||
public bool IsPlayingReverse => _pathAnimationManager?.PlaybackDirection == -1 &&
|
||
_pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;
|
||
|
||
#endregion
|
||
|
||
|
||
#endregion
|
||
|
||
#region 命令
|
||
|
||
// 原有命令
|
||
public ICommand StartAnimationCommand { get; private set; }
|
||
public ICommand PauseAnimationCommand { get; private set; }
|
||
public ICommand StopAnimationCommand { get; private set; }
|
||
public ICommand ViewCollisionReportCommand { get; private set; }
|
||
public ICommand SelectAnimatedObjectCommand { get; private set; }
|
||
public ICommand ClearAnimatedObjectCommand { get; private set; }
|
||
public ICommand GenerateAnimationCommand { get; private set; }
|
||
public ICommand ApplyManualTargetsFromSelectionCommand { get; private set; }
|
||
public ICommand ClearManualTargetsCommand { get; private set; }
|
||
public ICommand RemoveManualTargetCommand { get; private set; }
|
||
public ICommand HighlightManualTargetsCommand { get; private set; }
|
||
public ICommand ClearManualHighlightsCommand { get; private set; }
|
||
public ICommand HighlightPrecomputedCollisionResultsCommand { get; private set; }
|
||
public ICommand ClearPrecomputedCollisionHighlightsCommand { get; private set; }
|
||
public ICommand HighlightClashDetectiveResultsCommand { get; private set; }
|
||
public ICommand ClearClashDetectiveHighlightsCommand { get; private set; }
|
||
|
||
// 媒体控制命令
|
||
public ICommand PlayForwardCommand { get; private set; }
|
||
public ICommand PlayReverseCommand { get; private set; }
|
||
public ICommand StepForwardCommand { get; private set; }
|
||
public ICommand StepBackwardCommand { get; private set; }
|
||
public ICommand FastForwardCommand { get; private set; }
|
||
public ICommand FastBackwardCommand { get; private set; }
|
||
public ICommand SeekToStartCommand { get; private set; }
|
||
public ICommand SeekToEndCommand { get; private set; }
|
||
|
||
#endregion
|
||
|
||
#region 构造函数
|
||
|
||
public AnimationControlViewModel() : this(null)
|
||
{
|
||
// 调用带参数的构造函数,传入null
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带主ViewModel参数的构造函数,支持统一状态栏
|
||
/// </summary>
|
||
/// <param name="mainViewModel">主ViewModel,用于统一状态栏</param>
|
||
public AnimationControlViewModel(LogisticsControlViewModel mainViewModel) : base()
|
||
{
|
||
try
|
||
{
|
||
// 设置主ViewModel引用到基类
|
||
SetMainViewModel(mainViewModel);
|
||
|
||
// 初始化管理器 - 使用单例模式确保与GenerateCollisionReportCommand使用同一实例
|
||
_pathAnimationManager = NavisworksTransport.Core.Animation.PathAnimationManager.GetInstance();
|
||
_clashIntegration = ClashDetectiveIntegration.Instance;
|
||
_uiStateManager = UIStateManager.Instance;
|
||
|
||
// 订阅PathAnimationManager事件
|
||
_pathAnimationManager.ProgressChanged += OnAnimationProgressChanged;
|
||
_pathAnimationManager.StateChanged += OnAnimationStateChanged;
|
||
|
||
// 初始化集合
|
||
AvailableFrameRates = new ThreadSafeObservableCollection<int>();
|
||
_manualCollisionTargets = new ObservableCollection<ManualCollisionTargetViewModel>();
|
||
_manualCollisionTargets.CollectionChanged += OnManualCollisionTargetsChanged;
|
||
UpdateManualCollisionTargetSummary();
|
||
|
||
// 初始化设置
|
||
InitializeAnimationSettings();
|
||
|
||
// 初始化命令
|
||
InitializeCommands();
|
||
|
||
// 订阅碰撞检测事件
|
||
_clashIntegration.CollisionDetected += OnCollisionDetected;
|
||
_clashIntegration.ClashDetectiveResultSaved += OnClashDetectiveResultSaved;
|
||
|
||
// 订阅文档状态事件
|
||
DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated;
|
||
DocumentStateManager.Instance.DocumentReady += OnDocumentReady;
|
||
|
||
LogManager.Info("AnimationControlViewModel初始化完成 - 支持统一状态栏");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"AnimationControlViewModel初始化失败: {ex.Message}", ex);
|
||
UpdateMainStatus("初始化失败,请检查日志");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 初始化方法
|
||
|
||
/// <summary>
|
||
/// 初始化动画设置
|
||
/// </summary>
|
||
private void InitializeAnimationSettings()
|
||
{
|
||
// 从配置加载动画参数
|
||
var config = NavisworksTransport.Core.Config.ConfigManager.Instance.Current;
|
||
|
||
// 初始化可用帧率
|
||
AvailableFrameRates.Clear();
|
||
var frameRates = new[] { 15, 24, 30, 60 };
|
||
foreach (var rate in frameRates)
|
||
{
|
||
AvailableFrameRates.Add(rate);
|
||
}
|
||
|
||
// 从配置读取帧率
|
||
SelectedFrameRate = config.Animation.FrameRate;
|
||
|
||
// 从配置读取动画持续时间
|
||
AnimationDuration = config.Animation.DurationSeconds;
|
||
|
||
// 设置初始状态 - 默认状态未激活,等待有效动画
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
|
||
// 初始化碰撞检测状态
|
||
HasClashDetectiveResults = false;
|
||
UpdateMainStatus("碰撞检测就绪");
|
||
|
||
// 从配置读取碰撞检测参数
|
||
DetectionGap = config.Animation.DetectionGapMeters;
|
||
AnimationFrameRate = config.Animation.FrameRate;
|
||
|
||
// 🔥 从配置加载虚拟车辆尺寸(与路径编辑保持一致)
|
||
VirtualVehicleLength = config.PathEditing.VehicleLengthMeters;
|
||
VirtualVehicleWidth = config.PathEditing.VehicleWidthMeters;
|
||
VirtualVehicleHeight = config.PathEditing.VehicleHeightMeters;
|
||
|
||
// 设置动画按钮的初始状态
|
||
UpdateAnimationButtonStates();
|
||
|
||
LogManager.Info($"动画设置初始化完成 - 帧率:{SelectedFrameRate}fps, 持续时间:{AnimationDuration}秒, 检测间隙:{DetectionGap}米");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化命令
|
||
/// </summary>
|
||
private void InitializeCommands()
|
||
{
|
||
StartAnimationCommand = new RelayCommand(async () => await ExecuteStartAnimationAsync(), () => CanStartAnimation);
|
||
PauseAnimationCommand = new RelayCommand(async () => await ExecutePauseAnimationAsync(), () => CanPauseAnimation);
|
||
StopAnimationCommand = new RelayCommand(async () => await ExecuteStopAnimationAsync(), () => CanStopAnimation);
|
||
ViewCollisionReportCommand = new RelayCommand(async () => await ExecuteViewCollisionReport(), () => HasClashDetectiveResults);
|
||
SelectAnimatedObjectCommand = new RelayCommand(ExecuteSelectAnimatedObject);
|
||
ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
|
||
GenerateAnimationCommand = new RelayCommand(ExecuteGenerateAnimation, () => CanGenerateAnimation);
|
||
ApplyManualTargetsFromSelectionCommand = new RelayCommand(ExecuteApplyManualTargetsFromSelection);
|
||
ClearManualTargetsCommand = new RelayCommand(ExecuteClearManualTargets, () => HasManualCollisionTargets);
|
||
RemoveManualTargetCommand = new RelayCommand<ManualCollisionTargetViewModel>(ExecuteRemoveManualTarget, target => target != null);
|
||
HighlightManualTargetsCommand = new RelayCommand(ExecuteHighlightManualTargets, () => HasManualCollisionTargets);
|
||
ClearManualHighlightsCommand = new RelayCommand(ExecuteClearManualHighlights, () => HasManualCollisionTargets);
|
||
HighlightPrecomputedCollisionResultsCommand = new RelayCommand(ExecuteHighlightPrecomputedCollisionResults, () => HasClashDetectiveResults);
|
||
ClearPrecomputedCollisionHighlightsCommand = new RelayCommand(ExecuteClearPrecomputedCollisionHighlights, () => HasClashDetectiveResults);
|
||
HighlightClashDetectiveResultsCommand = new RelayCommand(ExecuteHighlightClashDetectiveResults, () => HasClashDetectiveResults);
|
||
ClearClashDetectiveHighlightsCommand = new RelayCommand(ExecuteClearClashDetectiveHighlights, () => HasClashDetectiveResults);
|
||
RefreshClashDetectiveResultsCommand = new RelayCommand(ExecuteRefreshClashDetectiveResults);
|
||
PlayForwardCommand = new RelayCommand(ExecutePlayForward, CanExecuteMediaCommands);
|
||
PlayReverseCommand = new RelayCommand(ExecutePlayReverse, CanExecuteMediaCommands);
|
||
StepForwardCommand = new RelayCommand(ExecuteStepForward, CanExecuteMediaCommands);
|
||
StepBackwardCommand = new RelayCommand(ExecuteStepBackward, CanExecuteMediaCommands);
|
||
FastForwardCommand = new RelayCommand(ExecuteFastForward, CanExecuteMediaCommands);
|
||
FastBackwardCommand = new RelayCommand(ExecuteFastBackward, CanExecuteMediaCommands);
|
||
SeekToStartCommand = new RelayCommand(ExecuteSeekToStart, CanExecuteMediaCommands);
|
||
SeekToEndCommand = new RelayCommand(ExecuteSeekToEnd, CanExecuteMediaCommands);
|
||
|
||
LogManager.Info("动画控制命令初始化完成");
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
#region 命令实现
|
||
|
||
/// <summary>
|
||
/// 开始动画命令
|
||
/// </summary>
|
||
private async Task ExecuteStartAnimationAsync()
|
||
{
|
||
if (!CanStartAnimation || CurrentPathRoute == null || CurrentPathRoute.Points.Count < 2)
|
||
{
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
UpdateMainStatus("请先选择包含至少2个点的路径");
|
||
});
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 根据PathAnimationManager的状态决定操作
|
||
if (_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Paused)
|
||
{
|
||
// 从暂停状态恢复
|
||
_pathAnimationManager.ResumeAnimation();
|
||
LogManager.Info("从暂停状态恢复动画播放");
|
||
return; // 恢复动画后直接返回,不再执行StartAnimation
|
||
}
|
||
else if (_pathAnimationManager.IsAnimating)
|
||
{
|
||
LogManager.Warning("动画已在播放中");
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
UpdateMainStatus("动画已在播放中");
|
||
CanStartAnimation = true;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 开始播放物体移动动画
|
||
// 清理之前的碰撞缓存
|
||
_pathAnimationManager?.ClearExclusionCache();
|
||
LogManager.Info("已清理UI层碰撞检测缓存");
|
||
|
||
// 清理之前的碰撞高亮
|
||
ModelHighlightHelper.ClearCollisionHighlights();
|
||
LogManager.Info("已清理之前的碰撞高亮");
|
||
|
||
// 首先重置进度(开始全新动画时)
|
||
|
||
_pathAnimationManager.StartAnimation();
|
||
|
||
LogManager.Info($"开始物体移动动画播放: {CurrentPathRoute.Name}, 帧率: {SelectedFrameRate}fps");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
UpdateMainStatus($"开始动画出错: {ex.Message}");
|
||
LogManager.Error($"开始动画异常: {ex.Message}", ex);
|
||
|
||
// 恢复按钮状态
|
||
CanStartAnimation = true;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 暂停动画命令
|
||
/// </summary>
|
||
private async Task ExecutePauseAnimationAsync()
|
||
{
|
||
try
|
||
{
|
||
// 调用PathAnimationManager暂停动画
|
||
// UI状态会通过OnAnimationStateChanged事件自动更新
|
||
_pathAnimationManager.PauseAnimation();
|
||
|
||
LogManager.Info("暂停动画播放");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"暂停动画异常: {ex.Message}", ex);
|
||
|
||
// 错误时手动恢复UI状态
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
UpdateMainStatus("暂停动画失败");
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止动画命令
|
||
/// </summary>
|
||
private async Task ExecuteStopAnimationAsync()
|
||
{
|
||
try
|
||
{
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
UpdateMainStatus("动画已停止");
|
||
|
||
CanStartAnimation = true;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
|
||
LogManager.Info("停止动画播放");
|
||
});
|
||
|
||
// 停止当前动画
|
||
_pathAnimationManager.CancelAnimation();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"停止动画异常: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 查看碰撞报告命令(显示已缓存的报告)
|
||
/// </summary>
|
||
private async Task ExecuteViewCollisionReport()
|
||
{
|
||
if (!HasClashDetectiveResults)
|
||
{
|
||
UpdateMainStatus("尚无碰撞检测结果可查看");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 检查是否有已缓存的报告
|
||
if (_lastGeneratedReport == null)
|
||
{
|
||
UpdateMainStatus("报告尚未生成,请等待碰撞检测完成");
|
||
LogManager.Warning("尝试查看报告但缓存为空");
|
||
return;
|
||
}
|
||
|
||
UpdateMainStatus("正在显示碰撞报告...", -1, true);
|
||
LogManager.Info("开始显示已缓存的碰撞报告");
|
||
|
||
// 在UI线程上显示缓存的报告
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
try
|
||
{
|
||
// 使用WPF碰撞报告对话框直接显示缓存的报告
|
||
var reportDialog = NavisworksTransport.UI.WPF.Views.CollisionReportDialog.ShowReport(_lastGeneratedReport);
|
||
|
||
if (reportDialog != null)
|
||
{
|
||
UpdateMainStatus("碰撞报告已显示");
|
||
LogManager.Info("成功显示缓存的碰撞报告");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("显示报告对话框失败");
|
||
LogManager.Error("CollisionReportDialog.ShowReport返回null");
|
||
}
|
||
}
|
||
catch (Exception dialogEx)
|
||
{
|
||
UpdateMainStatus($"显示报告失败: {dialogEx.Message}");
|
||
LogManager.Error($"显示WPF报告对话框失败: {dialogEx.Message}");
|
||
}
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateMainStatus("查看碰撞报告出错");
|
||
LogManager.Error($"查看碰撞报告异常: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
#region 媒体控制命令实现
|
||
|
||
/// <summary>
|
||
/// 检查媒体控制命令是否可执行
|
||
/// </summary>
|
||
private bool CanExecuteMediaCommands()
|
||
{
|
||
return _pathAnimationManager != null &&
|
||
_pathAnimationManager.CurrentState != NavisworksTransport.Core.Animation.AnimationState.Idle &&
|
||
_pathAnimationManager.TotalFrames > 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行正向播放命令
|
||
/// </summary>
|
||
private void ExecutePlayForward()
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager?.PlayForward();
|
||
UpdateMediaControlProperties();
|
||
LogManager.Info("执行正向播放命令");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"正向播放命令执行失败: {ex.Message}");
|
||
UpdateMainStatus("正向播放失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行反向播放命令
|
||
/// </summary>
|
||
private void ExecutePlayReverse()
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager?.PlayReverse();
|
||
UpdateMediaControlProperties();
|
||
LogManager.Info("执行反向播放命令");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"反向播放命令执行失败: {ex.Message}");
|
||
UpdateMainStatus("反向播放失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行单帧前进命令
|
||
/// </summary>
|
||
private void ExecuteStepForward()
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager?.StepForward();
|
||
UpdateMediaControlProperties();
|
||
LogManager.Debug("执行单帧前进命令");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"单帧前进命令执行失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行单帧后退命令
|
||
/// </summary>
|
||
private void ExecuteStepBackward()
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager?.StepBackward();
|
||
UpdateMediaControlProperties();
|
||
LogManager.Debug("执行单帧后退命令");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"单帧后退命令执行失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行快进10帧命令
|
||
/// </summary>
|
||
private void ExecuteFastForward()
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager?.FastForward();
|
||
UpdateMediaControlProperties();
|
||
LogManager.Debug("执行快进10帧命令");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"快进命令执行失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行快退10帧命令
|
||
/// </summary>
|
||
private void ExecuteFastBackward()
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager?.FastBackward();
|
||
UpdateMediaControlProperties();
|
||
LogManager.Debug("执行快退10帧命令");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"快退命令执行失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行跳转到开头命令
|
||
/// </summary>
|
||
private void ExecuteSeekToStart()
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager?.SeekToFrame(0);
|
||
UpdateMediaControlProperties();
|
||
LogManager.Info("跳转到动画开头");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"跳转到开头失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行跳转到结尾命令
|
||
/// </summary>
|
||
private void ExecuteSeekToEnd()
|
||
{
|
||
try
|
||
{
|
||
int lastFrame = (_pathAnimationManager?.TotalFrames ?? 1) - 1;
|
||
_pathAnimationManager?.SeekToFrame(lastFrame);
|
||
UpdateMediaControlProperties();
|
||
LogManager.Info("跳转到动画结尾");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"跳转到结尾失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行选择移动物体命令
|
||
/// </summary>
|
||
private void ExecuteSelectAnimatedObject()
|
||
{
|
||
try
|
||
{
|
||
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
|
||
if (doc == null || doc.CurrentSelection.IsEmpty)
|
||
{
|
||
LogManager.Warning("请先在场景中选择一个物体");
|
||
return;
|
||
}
|
||
|
||
var newObject = doc.CurrentSelection.SelectedItems.First;
|
||
if (newObject == null) return;
|
||
|
||
// 1. 如果有旧物体,先归位并清理旧动画
|
||
if (_selectedAnimatedObject != null && !ReferenceEquals(_selectedAnimatedObject, newObject))
|
||
{
|
||
_pathAnimationManager?.RestoreObjectToCADPosition();
|
||
}
|
||
_pathAnimationManager?.ClearAnimationResults();
|
||
|
||
// 2. 设置新物体
|
||
SelectedAnimatedObject = newObject;
|
||
LogManager.Info($"已选择移动物体: {SelectedAnimatedObject.DisplayName}");
|
||
|
||
// 3. 立即移动到起点(如果路径存在)
|
||
if (CurrentPathRoute != null && CurrentPathRoute.Points != null && _pathAnimationManager != null)
|
||
{
|
||
var points = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
|
||
_pathAnimationManager.MoveVehicleToPathStart(newObject, points);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"选择物体失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新媒体控制相关属性
|
||
/// </summary>
|
||
private void UpdateMediaControlProperties()
|
||
{
|
||
OnPropertyChanged(nameof(TimelinePosition));
|
||
OnPropertyChanged(nameof(TimelineDuration));
|
||
OnPropertyChanged(nameof(CurrentTimeDisplay));
|
||
OnPropertyChanged(nameof(FrameInfoDisplay));
|
||
OnPropertyChanged(nameof(IsPlayingForward));
|
||
OnPropertyChanged(nameof(IsPlayingReverse));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 格式化时间显示
|
||
/// </summary>
|
||
private string FormatTime(double seconds)
|
||
{
|
||
var timeSpan = TimeSpan.FromSeconds(Math.Max(0, seconds));
|
||
return $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
|
||
}
|
||
|
||
#endregion
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 设置当前路径
|
||
/// </summary>
|
||
public void SetCurrentPath(PathRouteViewModel pathRoute)
|
||
{
|
||
CurrentPathRoute = pathRoute;
|
||
|
||
// 同步路径到 PathAnimationManager
|
||
if (pathRoute != null && _pathAnimationManager != null)
|
||
{
|
||
// 从 PathPlanningManager 获取对应的 PathRoute 对象
|
||
var pathPlanningManager = PathPlanningManager.Instance;
|
||
var coreRoute = pathPlanningManager.GetAllRoutes().FirstOrDefault(r => r.Id == pathRoute.Id);
|
||
if (coreRoute != null)
|
||
{
|
||
_pathAnimationManager.SetRoute(coreRoute);
|
||
}
|
||
}
|
||
|
||
// 添加调试日志
|
||
if (pathRoute != null)
|
||
{
|
||
LogManager.Info($"[AnimationControlViewModel] 设置当前路径: 名称='{pathRoute.Name}', ID='{pathRoute.Id}', 点数={pathRoute.Points?.Count ?? 0}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Info("[AnimationControlViewModel] 清空当前路径");
|
||
}
|
||
|
||
// 路径改变时清除已生成的动画(如果有的话)
|
||
if (_pathAnimationManager != null &&
|
||
(_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Ready ||
|
||
_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Finished))
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager.CancelAnimation(); // 这会将状态设置为Stopped,需要重新生成动画
|
||
LogManager.Info("路径更改,已清除之前生成的动画");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"清除之前动画时出现警告: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 路径改变时,步长、速度和路径长度需要重新计算
|
||
OnPropertyChanged(nameof(CollisionDetectionAccuracy));
|
||
OnPropertyChanged(nameof(MovementSpeed));
|
||
OnPropertyChanged(nameof(PathLength));
|
||
|
||
// 按钮状态更新
|
||
UpdateAnimationButtonStates();
|
||
|
||
// 更新碰撞状态
|
||
if (pathRoute == null)
|
||
{
|
||
UpdateMainStatus("请选择路径");
|
||
}
|
||
else if (pathRoute.Points.Count < 2)
|
||
{
|
||
UpdateMainStatus("路径点数不足");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("碰撞检测就绪");
|
||
}
|
||
|
||
LogManager.Info($"AnimationControlViewModel当前路径更新: {pathRoute?.Name ?? "无"}, 点数: {pathRoute?.Points.Count ?? 0}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理动画进度变化事件
|
||
/// 使用Dispatcher更新UI
|
||
/// </summary>
|
||
private void OnAnimationProgressChanged(object sender, double progressPercent)
|
||
{
|
||
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
// 更新时间轴相关属性
|
||
OnPropertyChanged(nameof(TimelinePosition));
|
||
OnPropertyChanged(nameof(CurrentTimeDisplay));
|
||
OnPropertyChanged(nameof(FrameInfoDisplay));
|
||
}));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理动画状态变化事件
|
||
/// </summary>
|
||
private void OnAnimationStateChanged(object sender, Core.Animation.AnimationState state)
|
||
{
|
||
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
switch (state)
|
||
{
|
||
case Core.Animation.AnimationState.Playing:
|
||
CanStartAnimation = false;
|
||
CanPauseAnimation = true;
|
||
CanStopAnimation = true;
|
||
UpdateMainStatus("动画播放中");
|
||
UpdateMediaControlProperties(); // 更新媒体控制属性
|
||
break;
|
||
case Core.Animation.AnimationState.Paused:
|
||
CanStartAnimation = true; // 暂停状态下可以继续
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = true;
|
||
UpdateMainStatus("动画已暂停");
|
||
UpdateMediaControlProperties(); // 更新媒体控制属性
|
||
break;
|
||
case Core.Animation.AnimationState.Finished:
|
||
CanStartAnimation = true;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
UpdateMainStatus("动画已完成");
|
||
UpdateMediaControlProperties(); // 更新媒体控制属性
|
||
// 先清除所有碰撞高亮(包括预计算和ClashDetective)
|
||
ModelHighlightHelper.ClearCollisionHighlights();
|
||
// 检查并高亮ClashDetective结果
|
||
CheckAndHighlightClashDetectiveResults();
|
||
break;
|
||
case NavisworksTransport.Core.Animation.AnimationState.Stopped:
|
||
CanStartAnimation = true;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
UpdateMainStatus("动画已停止");
|
||
UpdateMediaControlProperties(); // 更新媒体控制属性
|
||
// 先清除所有碰撞高亮(包括预计算和ClashDetective)
|
||
ModelHighlightHelper.ClearCollisionHighlights();
|
||
// 检查并高亮ClashDetective结果
|
||
CheckAndHighlightClashDetectiveResults();
|
||
break;
|
||
default:
|
||
UpdateMainStatus($"动画状态: {state}");
|
||
UpdateMediaControlProperties(); // 更新媒体控制属性
|
||
break;
|
||
}
|
||
}));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查并高亮ClashDetective结果
|
||
/// </summary>
|
||
private void CheckAndHighlightClashDetectiveResults()
|
||
{
|
||
try
|
||
{
|
||
// 检查是否有当前测试的ClashDetective结果
|
||
var clashResults = _clashIntegration?.GetCurrentTestResults();
|
||
|
||
if (clashResults != null && clashResults.Count > 0)
|
||
{
|
||
// 有ClashDetective结果,高亮显示
|
||
ModelHighlightHelper.HighlightClashDetectiveResults(_clashIntegration.CurrentTestName, _clashIntegration.GetCurrentPathClashResults);
|
||
HasClashDetectiveResults = true;
|
||
LogManager.Info($"动画结束,已高亮ClashDetective检测结果:{clashResults.Count}个碰撞");
|
||
}
|
||
// 如果没有ClashDetective结果,不做处理(可能第一次运行会自动计算,或者没有碰撞)
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"检查并高亮ClashDetective结果失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理碰撞检测事件 - 自动生成报告并保存数据库
|
||
/// </summary>
|
||
private async void OnCollisionDetected(object sender, CollisionDetectedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 获取ClashDetective结果
|
||
var clashResults = _clashIntegration?.GetCurrentTestResults();
|
||
var collisionCount = clashResults?.Count ?? 0;
|
||
|
||
if (collisionCount > 0)
|
||
{
|
||
UpdateMainStatus($"发现 {collisionCount} 个碰撞点,正在生成报告...", -1, true);
|
||
LogManager.Info($"碰撞检测完成,发现 {collisionCount} 个碰撞,开始自动生成报告");
|
||
|
||
// 自动生成碰撞报告
|
||
var generateReportCommand = NavisworksTransport.Commands.GenerateCollisionReportCommand.CreateComprehensive();
|
||
var result = await generateReportCommand.ExecuteAsync();
|
||
|
||
if (result.IsSuccess && result is PathPlanningResult<CollisionReportResult> reportResult)
|
||
{
|
||
// 缓存报告结果,供后续查看使用
|
||
_lastGeneratedReport = reportResult.Data;
|
||
|
||
// 保存到数据库
|
||
await SaveCollisionReportToDatabase(reportResult.Data);
|
||
|
||
UpdateMainStatus($"✅ 碰撞报告已生成并保存:{collisionCount} 个碰撞");
|
||
LogManager.Info("碰撞报告已自动生成并保存到数据库");
|
||
|
||
// 高亮ClashDetective结果
|
||
ModelHighlightHelper.HighlightClashDetectiveResults(_clashIntegration.CurrentTestName, _clashIntegration.GetCurrentPathClashResults);
|
||
HasClashDetectiveResults = true;
|
||
LogManager.Info("已自动高亮ClashDetective检测结果");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus($"发现 {collisionCount} 个碰撞点(报告生成失败)");
|
||
LogManager.Error($"自动生成碰撞报告失败: {result.ErrorMessage}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("未发现碰撞");
|
||
_lastGeneratedReport = null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"处理碰撞检测事件失败: {ex.Message}", ex);
|
||
UpdateMainStatus("处理碰撞检测事件失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// ClashDetective结果保存事件处理
|
||
/// </summary>
|
||
private void OnClashDetectiveResultSaved(object sender, ClashDetectiveResultSavedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
// 刷新ClashDetective结果列表
|
||
RefreshClashDetectiveResultsList();
|
||
LogManager.Info($"ClashDetective结果已保存,列表已刷新: 路径={e.PathName}, 测试={e.TestName}, 碰撞数={e.CollisionCount}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"处理ClashDetective结果保存事件失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存碰撞报告到数据库
|
||
/// </summary>
|
||
private async Task SaveCollisionReportToDatabase(CollisionReportResult reportResult)
|
||
{
|
||
try
|
||
{
|
||
var pathPlanningManager = PathPlanningManager.Instance;
|
||
var pathDatabase = pathPlanningManager?.GetPathDatabase();
|
||
|
||
if (pathDatabase != null && reportResult != null)
|
||
{
|
||
// 获取路由ID
|
||
string routeId = CurrentPathRoute?.Id ?? "";
|
||
|
||
// 从报告中获取所有需要的数据
|
||
await Task.Run(() =>
|
||
{
|
||
pathDatabase.SaveCollisionReport(
|
||
routeId,
|
||
reportResult.PathName,
|
||
SelectedAnimatedObjectName ?? "未知对象",
|
||
reportResult.UniqueCollidedObjectsCount,
|
||
reportResult.FrameRate,
|
||
reportResult.Duration,
|
||
reportResult.DetectionGap,
|
||
reportResult.AnimationCollisions,
|
||
reportResult.TotalCollisions
|
||
);
|
||
});
|
||
|
||
LogManager.Info($"碰撞报告已保存到数据库 - 路径:{reportResult.PathName}, " +
|
||
$"碰撞构件:{reportResult.UniqueCollidedObjectsCount}, " +
|
||
$"动画碰撞:{reportResult.AnimationCollisions}, " +
|
||
$"ClashDetective:{reportResult.TotalCollisions}");
|
||
}
|
||
else
|
||
{
|
||
LogManager.Warning("无法获取PathDatabase实例或报告数据为空,碰撞报告未保存到数据库");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"保存碰撞报告到数据库失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行清除移动物体命令
|
||
/// 清除移动物体选择、停止当前动画并更新按钮状态
|
||
/// </summary>
|
||
private void ExecuteClearAnimatedObject()
|
||
{
|
||
try
|
||
{
|
||
if (_pathAnimationManager != null)
|
||
{
|
||
// 1. 归位并清理
|
||
_pathAnimationManager.RestoreObjectToCADPosition();
|
||
_pathAnimationManager.ClearAnimationResults();
|
||
LogManager.Info("已清除PathAnimationManager中的动画数据并归位物体");
|
||
}
|
||
|
||
// 2. 重置属性
|
||
SelectedAnimatedObject = null;
|
||
UpdateAnimatedObjectInfo();
|
||
UpdateCanGenerateAnimation();
|
||
|
||
// 3. 清理高亮
|
||
ModelHighlightHelper.ClearCollisionHighlights();
|
||
|
||
LogManager.Info("移动物体已完全清除并归位");
|
||
UpdateMainStatus("已清除物体选择并恢复位置");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"清除失败: {ex.Message}");
|
||
UpdateMainStatus($"清除失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#region 手工碰撞对象命令
|
||
|
||
private void ExecuteApplyManualTargetsFromSelection()
|
||
{
|
||
try
|
||
{
|
||
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
|
||
var selectedItems = doc?.CurrentSelection?.SelectedItems;
|
||
|
||
if (selectedItems == null || selectedItems.Count == 0)
|
||
{
|
||
UpdateMainStatus("请在Navisworks中选择需要检测的对象");
|
||
LogManager.Warning("[手工碰撞] 未选择任何对象");
|
||
return;
|
||
}
|
||
|
||
var toAdd = new List<ManualCollisionTargetViewModel>();
|
||
foreach (ModelItem item in selectedItems)
|
||
{
|
||
if (item == null)
|
||
continue;
|
||
|
||
if (!ModelItemAnalysisHelper.IsModelItemValid(item) || !HasGeometryRecursive(item))
|
||
continue;
|
||
|
||
if (ManualTargetExists(item))
|
||
continue;
|
||
|
||
var displayName = ModelItemAnalysisHelper.GetSafeDisplayName(item);
|
||
var modelPath = BuildModelPath(item);
|
||
toAdd.Add(new ManualCollisionTargetViewModel(item, displayName, modelPath));
|
||
}
|
||
|
||
if (toAdd.Count == 0)
|
||
{
|
||
if (_manualCollisionTargets.Count >= ManualCollisionTargetLimit)
|
||
{
|
||
UpdateMainStatus($"手工目标数量已达上限({ManualCollisionTargetLimit})");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("选中的对象已在列表中或不包含几何体");
|
||
}
|
||
return;
|
||
}
|
||
|
||
int availableSlots = ManualCollisionTargetLimit - _manualCollisionTargets.Count;
|
||
if (availableSlots <= 0)
|
||
{
|
||
UpdateMainStatus($"手工目标数量已达上限({ManualCollisionTargetLimit})");
|
||
return;
|
||
}
|
||
|
||
var addedCount = Math.Min(availableSlots, toAdd.Count);
|
||
foreach (var entry in toAdd.Take(addedCount))
|
||
{
|
||
_manualCollisionTargets.Add(entry);
|
||
}
|
||
|
||
_manualTargetsLastSyncTime = DateTime.Now;
|
||
UpdateManualCollisionTargetSummary();
|
||
RefreshManualTargetHighlights();
|
||
SyncManualCollisionTargetsToAnimationManager();
|
||
|
||
UpdateMainStatus($"已添加 {addedCount} 个碰撞检测对象");
|
||
LogManager.Info($"[手工碰撞] 添加 {addedCount} 个对象,目前共 {_manualCollisionTargets.Count} 个");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[手工碰撞] 同步选中对象失败: {ex.Message}");
|
||
UpdateMainStatus("同步手工对象失败");
|
||
}
|
||
}
|
||
|
||
private void ExecuteClearManualTargets()
|
||
{
|
||
if (_manualCollisionTargets.Count == 0)
|
||
return;
|
||
|
||
_manualCollisionTargets.Clear();
|
||
_manualTargetsLastSyncTime = null;
|
||
ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
|
||
SyncManualCollisionTargetsToAnimationManager();
|
||
UpdateManualCollisionTargetSummary();
|
||
UpdateMainStatus("已清空手工碰撞对象");
|
||
LogManager.Info("[手工碰撞] 手工对象列表已清空");
|
||
}
|
||
|
||
private void ExecuteRemoveManualTarget(ManualCollisionTargetViewModel target)
|
||
{
|
||
if (target == null)
|
||
return;
|
||
|
||
if (_manualCollisionTargets.Remove(target))
|
||
{
|
||
UpdateMainStatus($"已移除 {target.DisplayName}");
|
||
LogManager.Info($"[手工碰撞] 移除对象 {target.DisplayName}");
|
||
|
||
if (_manualCollisionTargets.Count == 0)
|
||
{
|
||
ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
|
||
}
|
||
else
|
||
{
|
||
RefreshManualTargetHighlights();
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ExecuteHighlightManualTargets()
|
||
{
|
||
if (_manualCollisionTargets.Count == 0)
|
||
return;
|
||
|
||
RefreshManualTargetHighlights(forceHighlight: true);
|
||
UpdateMainStatus("已高亮手工指定对象");
|
||
}
|
||
|
||
private void ExecuteClearManualHighlights()
|
||
{
|
||
ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
|
||
UpdateMainStatus("已清除手工对象高亮");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 碰撞结果高亮命令
|
||
|
||
private void ExecuteHighlightPrecomputedCollisionResults()
|
||
{
|
||
try
|
||
{
|
||
var deduplicatedResults = _clashIntegration?.GetDeduplicatedCollisionResults();
|
||
LogManager.Debug($"[预计算碰撞结果高亮] 获取到去重后的预计算结果数量: {deduplicatedResults?.Count ?? 0}");
|
||
|
||
if (deduplicatedResults == null || deduplicatedResults.Count == 0)
|
||
{
|
||
UpdateMainStatus("没有可高亮的去重预计算碰撞结果对象");
|
||
LogManager.Warning("[预计算碰撞结果高亮] 去重预计算缓存为空");
|
||
return;
|
||
}
|
||
|
||
// 输出前3个碰撞结果的信息
|
||
for (int i = 0; i < Math.Min(3, deduplicatedResults.Count); i++)
|
||
{
|
||
var collision = deduplicatedResults[i];
|
||
LogManager.Debug($"[预计算碰撞结果高亮] 碰撞{i+1}: {collision.DisplayName}, Item1={collision.Item1?.DisplayName}, Item2={collision.Item2?.DisplayName}");
|
||
}
|
||
|
||
ModelHighlightHelper.HighlightCollisionResults(deduplicatedResults, clearPrevious: false);
|
||
UpdateMainStatus($"已高亮 {deduplicatedResults.Count} 个去重预计算碰撞结果");
|
||
LogManager.Info($"[预计算碰撞结果高亮] 已高亮 {deduplicatedResults.Count} 个结果");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"高亮预计算碰撞结果失败: {ex.Message}", ex);
|
||
UpdateMainStatus("高亮预计算碰撞结果失败");
|
||
}
|
||
}
|
||
|
||
private void ExecuteClearPrecomputedCollisionHighlights()
|
||
{
|
||
ModelHighlightHelper.ClearCategory(CollisionResultsHighlightCategory);
|
||
UpdateMainStatus("已清除预计算碰撞结果高亮");
|
||
}
|
||
|
||
private void ExecuteHighlightClashDetectiveResults()
|
||
{
|
||
ModelHighlightHelper.HighlightClashDetectiveResults(_clashIntegration.CurrentTestName, _clashIntegration.GetCurrentPathClashResults);
|
||
UpdateMainStatus("已高亮ClashDetective检测结果");
|
||
}
|
||
|
||
private void ExecuteClearClashDetectiveHighlights()
|
||
{
|
||
ModelHighlightHelper.ClearClashDetectiveHighlights();
|
||
UpdateMainStatus("已清除ClashDetective高亮");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新ClashDetective结果列表
|
||
/// </summary>
|
||
private void RefreshClashDetectiveResultsList()
|
||
{
|
||
try
|
||
{
|
||
var pathName = CurrentPathRoute?.Name;
|
||
if (string.IsNullOrEmpty(pathName))
|
||
{
|
||
_clashDetectiveResults.Clear();
|
||
HasClashDetectiveResults = false;
|
||
return;
|
||
}
|
||
|
||
var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
|
||
if (pathDatabase == null)
|
||
{
|
||
_clashDetectiveResults.Clear();
|
||
HasClashDetectiveResults = false;
|
||
return;
|
||
}
|
||
|
||
var records = pathDatabase.GetClashDetectiveResultsByPath(pathName);
|
||
_clashDetectiveResults.Clear();
|
||
|
||
foreach (var record in records)
|
||
{
|
||
_clashDetectiveResults.Add(new ClashDetectiveResultViewModel(record, RefreshClashDetectiveResultsList));
|
||
}
|
||
|
||
// 更新HasClashDetectiveResults属性
|
||
HasClashDetectiveResults = _clashDetectiveResults.Count > 0;
|
||
|
||
UpdateMainStatus($"已刷新ClashDetective结果列表:{_clashDetectiveResults.Count}条记录");
|
||
LogManager.Info($"ClashDetective结果列表已刷新:{_clashDetectiveResults.Count}条记录");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"刷新ClashDetective结果列表失败: {ex.Message}");
|
||
_clashDetectiveResults.Clear();
|
||
HasClashDetectiveResults = false;
|
||
}
|
||
}
|
||
|
||
private void ExecuteRefreshClashDetectiveResults()
|
||
{
|
||
RefreshClashDetectiveResultsList();
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 异步预计算碰撞排除列表
|
||
/// </summary>
|
||
/// <param name="animationObject">动画对象</param>
|
||
private async Task PrecomputeCollisionExclusionsAsync(ModelItem animationObject)
|
||
{
|
||
try
|
||
{
|
||
if (animationObject == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
LogManager.Info($"[缓存预计算] 开始为新选择的动画对象预计算: {animationObject.DisplayName}");
|
||
|
||
// 在UI线程中更新开始状态
|
||
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
UpdateMainStatus("正在分析动画对象...", -1, true);
|
||
}));
|
||
|
||
// 在UI线程中执行Navisworks API操作(线程安全)
|
||
bool success;
|
||
try
|
||
{
|
||
success = _pathAnimationManager.PrecomputeCollisionExclusions(animationObject);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[缓存预计算] 预计算失败: {ex.Message}", ex);
|
||
success = false;
|
||
}
|
||
|
||
// 异步更新UI状态
|
||
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
if (success)
|
||
{
|
||
var cacheStats = _pathAnimationManager.GetCacheStats();
|
||
UpdateMainStatus("动画对象分析完成");
|
||
LogManager.Info($"[缓存预计算] 预计算成功 - {cacheStats}");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("动画对象分析失败,无法生成动画");
|
||
LogManager.Error("[缓存预计算] 预计算失败: 无法构建碰撞排除列表");
|
||
}
|
||
}));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[缓存预计算] 异步预计算过程异常: {ex.Message}", ex);
|
||
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
UpdateMainStatus("动画对象分析异常");
|
||
}));
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 手工碰撞对象辅助方法
|
||
|
||
private void OnManualCollisionTargetsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||
{
|
||
UpdateManualCollisionTargetSummary();
|
||
OnPropertyChanged(nameof(HasManualCollisionTargets));
|
||
OnPropertyChanged(nameof(IsManualTargetModeActive));
|
||
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
|
||
RefreshManualTargetHighlights();
|
||
SyncManualCollisionTargetsToAnimationManager();
|
||
}
|
||
|
||
private void UpdateManualCollisionTargetSummary()
|
||
{
|
||
if (_manualCollisionTargets == null || _manualCollisionTargets.Count == 0)
|
||
{
|
||
ManualCollisionTargetSummary = "未指定碰撞检测对象";
|
||
return;
|
||
}
|
||
|
||
var syncPart = _manualTargetsLastSyncTime.HasValue
|
||
? $" • 最近同步 {_manualTargetsLastSyncTime.Value:HH:mm:ss}"
|
||
: string.Empty;
|
||
ManualCollisionTargetSummary = $"{_manualCollisionTargets.Count} 个对象{syncPart}";
|
||
}
|
||
|
||
private string BuildModelPath(ModelItem item)
|
||
{
|
||
try
|
||
{
|
||
var segments = new List<string>();
|
||
var current = item;
|
||
int guard = 0;
|
||
|
||
while (current != null && guard < 15)
|
||
{
|
||
var name = ModelItemAnalysisHelper.GetSafeDisplayName(current);
|
||
if (!string.IsNullOrWhiteSpace(name))
|
||
{
|
||
segments.Add(name.Trim());
|
||
}
|
||
current = current.Parent;
|
||
guard++;
|
||
}
|
||
|
||
segments.Reverse();
|
||
return string.Join(" / ", segments);
|
||
}
|
||
catch
|
||
{
|
||
return "路径获取失败";
|
||
}
|
||
}
|
||
|
||
private List<ModelItem> GetValidManualCollisionTargets(bool pruneInvalidEntries = false)
|
||
{
|
||
var validItems = new List<ModelItem>();
|
||
if (_manualCollisionTargets == null)
|
||
{
|
||
return validItems;
|
||
}
|
||
|
||
var invalidItems = new List<ManualCollisionTargetViewModel>();
|
||
foreach (var entry in _manualCollisionTargets)
|
||
{
|
||
if (entry?.ModelItem != null &&
|
||
ModelItemAnalysisHelper.IsModelItemValid(entry.ModelItem) &&
|
||
HasGeometryRecursive(entry.ModelItem))
|
||
{
|
||
validItems.Add(entry.ModelItem);
|
||
}
|
||
else if (pruneInvalidEntries && entry != null)
|
||
{
|
||
invalidItems.Add(entry);
|
||
}
|
||
}
|
||
|
||
if (pruneInvalidEntries && invalidItems.Count > 0)
|
||
{
|
||
foreach (var invalid in invalidItems)
|
||
{
|
||
_manualCollisionTargets.Remove(invalid);
|
||
}
|
||
|
||
if (invalidItems.Count > 0)
|
||
{
|
||
UpdateMainStatus("部分手工碰撞对象已失效,已自动清理");
|
||
}
|
||
}
|
||
|
||
return validItems;
|
||
}
|
||
|
||
private void SyncManualCollisionTargetsToAnimationManager(bool pruneInvalidEntries = false)
|
||
{
|
||
if (_pathAnimationManager == null)
|
||
return;
|
||
|
||
var targets = GetValidManualCollisionTargets(pruneInvalidEntries);
|
||
if (IsManualCollisionTargetEnabled && targets.Count > 0)
|
||
{
|
||
_pathAnimationManager.SetManualCollisionTargets(targets, true);
|
||
}
|
||
else
|
||
{
|
||
_pathAnimationManager.SetManualCollisionTargets(null, false);
|
||
}
|
||
}
|
||
|
||
private bool ManualTargetExists(ModelItem item)
|
||
{
|
||
if (item == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (_manualCollisionTargets == null || _manualCollisionTargets.Count == 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
Guid itemGuid = item.InstanceGuid;
|
||
foreach (var target in _manualCollisionTargets)
|
||
{
|
||
if (target == null)
|
||
continue;
|
||
|
||
if (itemGuid != Guid.Empty && target.InstanceGuid != Guid.Empty)
|
||
{
|
||
if (target.InstanceGuid == itemGuid)
|
||
return true;
|
||
}
|
||
else if (ReferenceEquals(target.ModelItem, item))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void RefreshManualTargetHighlights(bool forceHighlight = false)
|
||
{
|
||
try
|
||
{
|
||
if (_clashIntegration == null)
|
||
return;
|
||
|
||
var validItems = GetValidManualCollisionTargets();
|
||
if (validItems.Count == 0)
|
||
{
|
||
ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
|
||
return;
|
||
}
|
||
|
||
if (IsManualTargetModeActive || forceHighlight)
|
||
{
|
||
ModelHighlightHelper.HighlightItems(ManualTargetsHighlightCategory, validItems);
|
||
}
|
||
else
|
||
{
|
||
ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[手工碰撞] 更新高亮失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void HandleManualCollisionModeChanged()
|
||
{
|
||
RefreshManualTargetHighlights();
|
||
SyncManualCollisionTargetsToAnimationManager(true);
|
||
|
||
if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets)
|
||
{
|
||
UpdateMainStatus("手工模式已启用,请先选择碰撞检测对象");
|
||
}
|
||
else if (!IsManualCollisionTargetEnabled)
|
||
{
|
||
UpdateMainStatus("已切换回默认碰撞检测对象范围");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 动画辅助方法
|
||
|
||
/// <summary>
|
||
/// 执行生成动画命令(同步执行,因为Navisworks API需要STA线程)
|
||
/// </summary>
|
||
private void ExecuteGenerateAnimation()
|
||
{
|
||
try
|
||
{
|
||
if (!CanGenerateAnimation)
|
||
{
|
||
UpdateMainStatus("无法生成动画:缺少必要条件");
|
||
LogManager.Warning("尝试生成动画但条件不满足");
|
||
return;
|
||
}
|
||
|
||
UpdateMainStatus("正在生成动画...", -1, true);
|
||
LogManager.Info($"开始生成动画 - 模式: {(UseVirtualVehicle ? "虚拟车辆" : "选择物体")}");
|
||
|
||
var cacheStartTime = DateTime.Now;
|
||
|
||
var manualModeEnabled = IsManualCollisionTargetEnabled;
|
||
List<ModelItem> manualTargets = null;
|
||
|
||
if (manualModeEnabled)
|
||
{
|
||
manualTargets = GetValidManualCollisionTargets(pruneInvalidEntries: true);
|
||
if (manualTargets.Count == 0)
|
||
{
|
||
UpdateMainStatus("手工模式已启用,但没有可用的碰撞对象");
|
||
LogManager.Warning("[动画生成] 手工模式启用但没有有效对象");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 构建碰撞检测缓存(两种模式都需要几何对象缓存)
|
||
UpdateMainStatus("正在构建碰撞检测缓存...", -1, true);
|
||
LogManager.Info("[动画生成] 开始构建碰撞检测缓存");
|
||
|
||
try
|
||
{
|
||
if (!manualModeEnabled)
|
||
{
|
||
ClashDetectiveIntegration.ClearAllCaches();
|
||
|
||
UpdateMainStatus("正在缓存几何对象列表...", -1, true);
|
||
ClashDetectiveIntegration.BuildNonHidddenGeometryItemsCache();
|
||
|
||
UpdateMainStatus("正在缓存通道对象...", -1, true);
|
||
var clashIntegration = ClashDetectiveIntegration.Instance;
|
||
clashIntegration.BuildChannelObjectsCache();
|
||
|
||
var cacheElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
|
||
LogManager.Info($"[动画生成] 碰撞检测缓存构建完成,耗时: {cacheElapsed:F1}ms");
|
||
|
||
if (!UseVirtualVehicle)
|
||
{
|
||
UpdateMainStatus("正在分析动画对象...", -1, true);
|
||
var success = _pathAnimationManager.PrecomputeCollisionExclusions(SelectedAnimatedObject);
|
||
|
||
if (!success)
|
||
{
|
||
var msg = "[动画生成] 动画对象分析失败,无法继续生成动画";
|
||
LogManager.Error(msg);
|
||
UpdateMainStatus("生成失败:对象分析错误");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("使用手工碰撞对象,跳过几何缓存构建", -1, true);
|
||
LogManager.Info("[动画生成] 手工碰撞对象模式,已跳过几何体/通道缓存和空间索引构建");
|
||
}
|
||
}
|
||
catch (Exception cacheEx)
|
||
{
|
||
LogManager.Error($"[动画生成] 缓存构建失败: {cacheEx.Message}");
|
||
UpdateMainStatus($"生成失败:{cacheEx.Message}");
|
||
return;
|
||
}
|
||
|
||
UpdateMainStatus("正在生成路径动画...", -1, true);
|
||
|
||
// 设置动画参数到PathAnimationManager
|
||
_pathAnimationManager.SetAnimationFrameRate(_animationFrameRate);
|
||
_pathAnimationManager.SetCollisionDetectionAccuracy(CollisionDetectionAccuracy);
|
||
_pathAnimationManager.SetMovementSpeed(MovementSpeed);
|
||
_pathAnimationManager.SetDetectionGap(_detectionGap);
|
||
|
||
// 将PathRouteViewModel的点转换为Point3D列表
|
||
var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
|
||
|
||
ModelItem animatedObject;
|
||
|
||
if (UseVirtualVehicle)
|
||
{
|
||
// 使用虚拟车辆:优先获取已存在的实例
|
||
animatedObject = VirtualVehicleManager.Instance.CurrentVirtualVehicle;
|
||
|
||
if (animatedObject == null)
|
||
{
|
||
LogManager.Info("[ExecuteGenerateAnimation] 虚拟车辆不存在,正在显示...");
|
||
UpdateMainStatus("正在显示虚拟车辆...", -1, true);
|
||
|
||
VirtualVehicleManager.Instance.ShowVirtualVehicle(
|
||
VirtualVehicleLength,
|
||
VirtualVehicleWidth,
|
||
VirtualVehicleHeight
|
||
);
|
||
animatedObject = VirtualVehicleManager.Instance.CurrentVirtualVehicle;
|
||
}
|
||
|
||
if (animatedObject == null)
|
||
{
|
||
LogManager.Error("[动画生成] 虚拟车辆显示失败");
|
||
UpdateMainStatus("生成失败:无法显示虚拟车辆");
|
||
return;
|
||
}
|
||
|
||
LogManager.Info($"使用虚拟车辆: {animatedObject.DisplayName}");
|
||
}
|
||
else
|
||
{
|
||
animatedObject = SelectedAnimatedObject;
|
||
}
|
||
|
||
if (manualModeEnabled)
|
||
{
|
||
_pathAnimationManager.SetManualCollisionTargets(manualTargets, true);
|
||
}
|
||
else
|
||
{
|
||
_pathAnimationManager.SetManualCollisionTargets(null, false);
|
||
}
|
||
|
||
// 【统一使用CreateAnimation,完全复用现有逻辑】
|
||
LogManager.Info($"[ExecuteGenerateAnimation] 准备创建动画: 路径名称='{CurrentPathRoute.Name}', ID='{CurrentPathRoute.Id}', " +
|
||
$"动画对象='{animatedObject.DisplayName}'");
|
||
|
||
// 从PathPlanningManager获取PathRoute对象
|
||
var pathPlanningManager = PathPlanningManager.Instance;
|
||
var pathRoute = pathPlanningManager.GetAllRoutes().FirstOrDefault(r => r.Id == CurrentPathRoute.Id);
|
||
|
||
if (pathRoute == null)
|
||
{
|
||
LogManager.Error($"无法找到路径对象,ID: {CurrentPathRoute.Id}");
|
||
UpdateMainStatus("生成失败:找不到路径数据");
|
||
return;
|
||
}
|
||
|
||
// 先设置路径到动画管理器
|
||
_pathAnimationManager.SetRoute(pathRoute);
|
||
|
||
// 准备车辆尺寸参数
|
||
double vLength = UseVirtualVehicle ? VirtualVehicleLength : 0;
|
||
double vWidth = UseVirtualVehicle ? VirtualVehicleWidth : 0;
|
||
double vHeight = UseVirtualVehicle ? VirtualVehicleHeight : 0;
|
||
|
||
LogManager.Info($"[ExecuteGenerateAnimation] 准备调用CreateAnimation: UseVirtualVehicle={UseVirtualVehicle}, 车辆尺寸: {vLength:F2}×{vWidth:F2}×{vHeight:F2}m");
|
||
|
||
_pathAnimationManager.CreateAnimation(animatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id, UseVirtualVehicle,
|
||
vLength, vWidth, vHeight);
|
||
|
||
var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
|
||
LogManager.Info($"[动画生成] 动画生成完成,总耗时: {totalElapsed:F1}ms");
|
||
LogManager.Info($"动画生成成功: 物体={animatedObject.DisplayName}, 路径={CurrentPathRoute.Name}");
|
||
|
||
if (UseVirtualVehicle)
|
||
{
|
||
LogManager.Info($"虚拟车辆尺寸: {VirtualVehicleLength:F1}×{VirtualVehicleWidth:F1}×{VirtualVehicleHeight:F1}m");
|
||
}
|
||
|
||
if (IsManualCollisionTargetEnabled)
|
||
{
|
||
RefreshManualTargetHighlights(forceHighlight: true);
|
||
}
|
||
|
||
// 更新状态
|
||
UpdateAnimationButtonStates();
|
||
UpdateMainStatus("动画生成成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateMainStatus("动画生成失败");
|
||
LogManager.Error($"生成动画时发生错误: {ex.Message}");
|
||
|
||
LogManager.Error($"动画生成失败,详细错误信息:{ex}");
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 更新移动物体信息
|
||
/// </summary>
|
||
private void UpdateAnimatedObjectInfo()
|
||
{
|
||
if (SelectedAnimatedObject != null)
|
||
{
|
||
SelectedAnimatedObjectName = SelectedAnimatedObject.DisplayName ?? "未命名物体";
|
||
HasSelectedAnimatedObject = true;
|
||
}
|
||
else
|
||
{
|
||
SelectedAnimatedObjectName = "未选择移动物体";
|
||
HasSelectedAnimatedObject = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归检查ModelItem是否包含几何体
|
||
/// </summary>
|
||
private bool HasGeometryRecursive(ModelItem item)
|
||
{
|
||
if (item == null) return false;
|
||
|
||
// 如果当前项有几何体,直接返回true
|
||
if (item.HasGeometry) return true;
|
||
|
||
// 递归检查子项
|
||
foreach (ModelItem child in item.Children)
|
||
{
|
||
if (HasGeometryRecursive(child))
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新是否可以生成动画的状态
|
||
/// </summary>
|
||
private void UpdateCanGenerateAnimation()
|
||
{
|
||
var hasPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
|
||
|
||
bool hasValidObject;
|
||
if (UseVirtualVehicle)
|
||
{
|
||
// 使用虚拟车辆时,只需要有效的车辆尺寸
|
||
hasValidObject = VirtualVehicleLength > 0 &&
|
||
VirtualVehicleWidth > 0 &&
|
||
VirtualVehicleHeight > 0;
|
||
}
|
||
else
|
||
{
|
||
// 使用选择物体时,需要已选择物体
|
||
hasValidObject = SelectedAnimatedObject != null;
|
||
}
|
||
|
||
CanGenerateAnimation = hasValidObject && hasPath;
|
||
|
||
// 更新状态提示
|
||
if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets)
|
||
{
|
||
UpdateMainStatus("手工模式已启用,请先选择碰撞检测对象");
|
||
}
|
||
else if (!hasPath && !hasValidObject)
|
||
{
|
||
UpdateMainStatus(UseVirtualVehicle ? "请选择动画路径" : "请选择移动物体和动画路径");
|
||
}
|
||
else if (!hasValidObject)
|
||
{
|
||
UpdateMainStatus(UseVirtualVehicle ? "虚拟车辆参数无效" : "请选择移动物体");
|
||
}
|
||
else if (!hasPath)
|
||
{
|
||
UpdateMainStatus("请选择有效的动画路径");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("可以生成动画");
|
||
}
|
||
|
||
// 同时更新动画按钮状态,因为对象或路径的变化会影响"开始动画"按钮的可用性
|
||
UpdateAnimationButtonStates();
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 计算当前路径的总长度
|
||
/// </summary>
|
||
private double GetPathLength()
|
||
{
|
||
if (CurrentPathRoute == null || CurrentPathRoute.Points.Count < 2)
|
||
{
|
||
return 0.0;
|
||
}
|
||
|
||
double totalLength = 0.0;
|
||
var points = CurrentPathRoute.Points;
|
||
|
||
for (int i = 0; i < points.Count - 1; i++)
|
||
{
|
||
var p1 = points[i];
|
||
var p2 = points[i + 1];
|
||
|
||
// 计算两点之间的欧几里得距离(模型单位)
|
||
double dx = p2.X - p1.X;
|
||
double dy = p2.Y - p1.Y;
|
||
double dz = p2.Z - p1.Z;
|
||
|
||
double segmentLengthInModelUnits = Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||
|
||
// 转换为米单位
|
||
double segmentLengthInMeters = NavisworksTransport.Utils.UnitsConverter.ConvertToMeters(segmentLengthInModelUnits);
|
||
totalLength += segmentLengthInMeters;
|
||
}
|
||
|
||
return totalLength;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统一更新动画按钮状态逻辑
|
||
/// 根据当前的路径、动画对象和动画管理器状态来决定按钮的可用性
|
||
/// </summary>
|
||
private void UpdateAnimationButtonStates()
|
||
{
|
||
try
|
||
{
|
||
// 检查是否有有效的动画可以播放
|
||
var hasValidPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
|
||
var hasValidAnimatedObject = SelectedAnimatedObject != null;
|
||
var hasValidAnimation = hasValidPath && hasValidAnimatedObject;
|
||
|
||
// 检查动画管理器当前状态
|
||
var animationState = _pathAnimationManager?.CurrentState ?? NavisworksTransport.Core.Animation.AnimationState.Stopped;
|
||
var isAnimating = _pathAnimationManager?.IsAnimating ?? false;
|
||
|
||
// 检查动画是否已经生成(Ready状态表示动画已生成但未播放)
|
||
var isAnimationReady = animationState == NavisworksTransport.Core.Animation.AnimationState.Ready ||
|
||
animationState == NavisworksTransport.Core.Animation.AnimationState.Finished;
|
||
|
||
// 根据动画状态和条件更新按钮状态
|
||
switch (animationState)
|
||
{
|
||
case NavisworksTransport.Core.Animation.AnimationState.Playing:
|
||
// 播放中:开始按钮禁用,暂停和停止按钮可用
|
||
CanStartAnimation = false;
|
||
CanPauseAnimation = true;
|
||
CanStopAnimation = true;
|
||
UpdateMainStatus("动画播放中");
|
||
break;
|
||
|
||
case NavisworksTransport.Core.Animation.AnimationState.Paused:
|
||
// 暂停中:开始按钮可用(显示为继续),暂停按钮禁用,停止按钮可用
|
||
CanStartAnimation = hasValidAnimation && isAnimationReady;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = true;
|
||
UpdateMainStatus("动画已暂停");
|
||
break;
|
||
|
||
default:
|
||
// 停止或其他状态:需要同时满足有效动画条件和动画已生成条件
|
||
CanStartAnimation = hasValidAnimation && isAnimationReady;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
|
||
// 更新状态文本
|
||
if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets)
|
||
{
|
||
UpdateMainStatus("手工模式已启用,请先选择碰撞检测对象");
|
||
}
|
||
else if (!hasValidPath && !hasValidAnimatedObject)
|
||
{
|
||
UpdateMainStatus("请选择路径和移动物体");
|
||
}
|
||
else if (!hasValidPath)
|
||
{
|
||
UpdateMainStatus("请选择有效路径(至少2个点)");
|
||
}
|
||
else if (!hasValidAnimatedObject)
|
||
{
|
||
UpdateMainStatus("请选择移动物体");
|
||
}
|
||
else if (!isAnimationReady)
|
||
{
|
||
UpdateMainStatus("请点击'生成动画'");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus("动画就绪");
|
||
}
|
||
break;
|
||
}
|
||
|
||
LogManager.Debug($"按钮状态已更新: CanStart={CanStartAnimation}, CanPause={CanPauseAnimation}, CanStop={CanStopAnimation}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"更新动画按钮状态时发生错误: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 生成碰撞检测报告数据
|
||
/// </summary>
|
||
private CollisionReportData GenerateCollisionReport()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("开始从Clash Detective获取碰撞检测结果");
|
||
|
||
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
|
||
var documentClash = doc.GetClash();
|
||
|
||
if (documentClash == null)
|
||
{
|
||
LogManager.Warning("无法获取Clash Detective文档");
|
||
return new CollisionReportData { IsValid = false, ErrorMessage = "Clash Detective不可用" };
|
||
}
|
||
|
||
var reportData = new CollisionReportData
|
||
{
|
||
IsValid = true,
|
||
GeneratedTime = DateTime.Now,
|
||
AnimatedObjectName = SelectedAnimatedObject?.DisplayName ?? "未知对象",
|
||
PathName = CurrentPathRoute?.Name ?? "未知路径"
|
||
};
|
||
|
||
// 获取所有动画相关的碰撞测试(包括新的分组测试)
|
||
var animationTests = documentClash.TestsData.Tests.Cast<ClashTest>()
|
||
.Where(t => t.DisplayName.Contains("碰撞检测") )
|
||
.ToList();
|
||
|
||
LogManager.Info($"找到 {animationTests.Count} 个碰撞检测");
|
||
|
||
foreach (var test in animationTests)
|
||
{
|
||
// 递归统计测试中的碰撞总数(包括分组内的)
|
||
var totalCollisionCount = GetTotalClashResultCountForReport(test);
|
||
|
||
var testInfo = new CollisionTestInfo
|
||
{
|
||
TestName = test.DisplayName,
|
||
TestType = test.TestType.ToString(),
|
||
Tolerance = test.Tolerance,
|
||
CollisionCount = totalCollisionCount,
|
||
Status = test.Status.ToString()
|
||
};
|
||
|
||
// 递归获取每个碰撞的详细信息(包括分组内的)
|
||
var allClashResults = GetAllClashResultsFromTest(test);
|
||
foreach (var clashResult in allClashResults)
|
||
{
|
||
var collision = new CollisionDetailInfo
|
||
{
|
||
CollisionId = clashResult.Guid.ToString(),
|
||
Distance = clashResult.Distance,
|
||
Status = clashResult.Status.ToString()
|
||
};
|
||
|
||
// 获取碰撞对象信息
|
||
if (clashResult.CompositeItem1 != null)
|
||
{
|
||
collision.Object1Name = clashResult.CompositeItem1.DisplayName;
|
||
var point = clashResult.CompositeItem1.BoundingBox();
|
||
collision.Object1Position = $"({point.Center.X:F2}, {point.Center.Y:F2}, {point.Center.Z:F2})";
|
||
}
|
||
|
||
if (clashResult.CompositeItem2 != null)
|
||
{
|
||
collision.Object2Name = clashResult.CompositeItem2.DisplayName;
|
||
var point = clashResult.CompositeItem2.BoundingBox();
|
||
collision.Object2Position = $"({point.Center.X:F2}, {point.Center.Y:F2}, {point.Center.Z:F2})";
|
||
}
|
||
|
||
// 获取碰撞中心点
|
||
if (clashResult.Center != null)
|
||
{
|
||
collision.CollisionCenter = $"({clashResult.Center.X:F2}, {clashResult.Center.Y:F2}, {clashResult.Center.Z:F2})";
|
||
}
|
||
|
||
testInfo.Collisions.Add(collision);
|
||
}
|
||
|
||
reportData.Tests.Add(testInfo);
|
||
}
|
||
|
||
// 统计总体信息
|
||
reportData.TotalTests = reportData.Tests.Count;
|
||
reportData.TotalCollisions = reportData.Tests.Sum(t => t.CollisionCount);
|
||
|
||
LogManager.Info($"碰撞报告生成完成:{reportData.TotalTests}个测试,{reportData.TotalCollisions}个碰撞");
|
||
return reportData;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"生成碰撞报告异常: {ex.Message}");
|
||
return new CollisionReportData { IsValid = false, ErrorMessage = ex.Message };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示碰撞报告
|
||
/// </summary>
|
||
private async Task ShowCollisionReport(CollisionReportData reportData)
|
||
{
|
||
try
|
||
{
|
||
// 更新UI摘要信息
|
||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||
{
|
||
if (reportData.IsValid)
|
||
{
|
||
// 🔧 修复:显示动画过程缓存的碰撞数量,而非测试数量
|
||
var animationCollisionCount = _clashIntegration?.AnimationCollisionCount ?? 0;
|
||
var summary = $"动画碰撞: {animationCollisionCount}个 | Clash Detective权威结果: {reportData.TotalCollisions}个";
|
||
UpdateMainStatus("碰撞报告已生成");
|
||
}
|
||
else
|
||
{
|
||
UpdateMainStatus($"报告生成失败:{reportData.ErrorMessage}");
|
||
}
|
||
});
|
||
|
||
// 如果报告有效且有碰撞数据,用户可通过"查看碰撞报告"按钮查看详细信息
|
||
if (reportData.IsValid && reportData.TotalCollisions > 0)
|
||
{
|
||
LogManager.Info($"碰撞检测完成,发现 {reportData.TotalCollisions} 个碰撞问题。用户可通过报告按钮查看详细信息");
|
||
}
|
||
else if (reportData.IsValid && reportData.TotalCollisions == 0)
|
||
{
|
||
// 没有碰撞时显示简单提示
|
||
LogManager.Info("未发现碰撞问题,路径规划良好");
|
||
System.Windows.MessageBox.Show(
|
||
"未发现碰撞问题,路径规划良好!",
|
||
"碰撞检测结果",
|
||
System.Windows.MessageBoxButton.OK,
|
||
System.Windows.MessageBoxImage.Information);
|
||
}
|
||
else
|
||
{
|
||
// 报告无效时的错误处理
|
||
LogManager.Error($"碰撞报告数据无效: {reportData.ErrorMessage}");
|
||
System.Windows.MessageBox.Show(
|
||
$"碰撞报告生成失败:{reportData.ErrorMessage}",
|
||
"错误",
|
||
System.Windows.MessageBoxButton.OK,
|
||
System.Windows.MessageBoxImage.Error);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"显示碰撞报告异常: {ex.Message}");
|
||
System.Windows.MessageBox.Show(
|
||
$"显示碰撞报告时发生异常:{ex.Message}",
|
||
"错误",
|
||
System.Windows.MessageBoxButton.OK,
|
||
System.Windows.MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
#region 文档管理
|
||
|
||
/// <summary>
|
||
/// 获取单例实例
|
||
/// </summary>
|
||
private static AnimationControlViewModel _instance;
|
||
public static AnimationControlViewModel Instance
|
||
{
|
||
get { return _instance; }
|
||
set { _instance = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 文档失效时的处理
|
||
/// </summary>
|
||
private void OnDocumentInvalidated(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[AnimationControlViewModel] 文档失效,重置状态");
|
||
Reset();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[AnimationControlViewModel] 处理文档失效失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 文档就绪时的处理
|
||
/// </summary>
|
||
private void OnDocumentReady(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[AnimationControlViewModel] 文档就绪,启用功能");
|
||
|
||
// 重新启用UI
|
||
CanStartAnimation = true;
|
||
CanGenerateAnimation = true;
|
||
|
||
UpdateMainStatus("文档已加载,就绪");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[AnimationControlViewModel] 处理文档就绪失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置视图模型状态
|
||
/// </summary>
|
||
public void Reset()
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[AnimationControlViewModel] 重置状态");
|
||
|
||
// 停止动画
|
||
if (_pathAnimationManager != null && _pathAnimationManager.IsAnimating)
|
||
{
|
||
_pathAnimationManager.CancelAnimation();
|
||
}
|
||
|
||
// 清空选中对象
|
||
SelectedAnimatedObject = null;
|
||
SelectedAnimatedObjectName = string.Empty;
|
||
CurrentPathRoute = null;
|
||
|
||
// 禁用按钮
|
||
CanStartAnimation = false;
|
||
CanPauseAnimation = false;
|
||
CanStopAnimation = false;
|
||
CanGenerateAnimation = false;
|
||
HasSelectedAnimatedObject = false;
|
||
|
||
// 清空碰撞结果
|
||
HasClashDetectiveResults = false;
|
||
ModelHighlightHelper.ClearCollisionHighlights();
|
||
|
||
UpdateMainStatus("已重置");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[AnimationControlViewModel] 重置失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 资源清理
|
||
|
||
private bool _disposed = false;
|
||
|
||
/// <summary>
|
||
/// 清理资源
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
Dispose(true);
|
||
GC.SuppressFinalize(this);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理资源
|
||
/// </summary>
|
||
protected virtual void Dispose(bool disposing)
|
||
{
|
||
if (!_disposed)
|
||
{
|
||
if (disposing)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("开始清理AnimationControlViewModel资源");
|
||
|
||
// 1. 取消事件订阅(在停止动画之前,避免事件处理中访问已释放的对象)
|
||
|
||
// 取消DocumentStateManager事件订阅
|
||
try
|
||
{
|
||
DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated;
|
||
DocumentStateManager.Instance.DocumentReady -= OnDocumentReady;
|
||
LogManager.Debug("DocumentStateManager事件订阅取消完成");
|
||
}
|
||
catch (Exception docEx)
|
||
{
|
||
LogManager.Warning($"取消DocumentStateManager事件订阅时出现警告: {docEx.Message}");
|
||
}
|
||
|
||
if (_pathAnimationManager != null)
|
||
{
|
||
try
|
||
{
|
||
_pathAnimationManager.ProgressChanged -= OnAnimationProgressChanged;
|
||
_pathAnimationManager.StateChanged -= OnAnimationStateChanged;
|
||
LogManager.Debug("PathAnimationManager事件订阅取消完成");
|
||
}
|
||
catch (Exception eventEx)
|
||
{
|
||
LogManager.Warning($"取消PathAnimationManager事件订阅时出现警告: {eventEx.Message}");
|
||
}
|
||
}
|
||
|
||
if (_clashIntegration != null)
|
||
{
|
||
try
|
||
{
|
||
_clashIntegration.CollisionDetected -= OnCollisionDetected;
|
||
_clashIntegration.ClashDetectiveResultSaved -= OnClashDetectiveResultSaved;
|
||
LogManager.Debug("碰撞检测事件订阅取消完成");
|
||
}
|
||
catch (Exception eventEx)
|
||
{
|
||
LogManager.Warning($"取消碰撞检测事件订阅时出现警告: {eventEx.Message}");
|
||
}
|
||
}
|
||
|
||
if (_manualCollisionTargets != null)
|
||
{
|
||
_manualCollisionTargets.CollectionChanged -= OnManualCollisionTargetsChanged;
|
||
_manualCollisionTargets.Clear();
|
||
}
|
||
|
||
|
||
// 2. 不再清理PathAnimationManager - 现在使用单例模式,由应用程序生命周期管理
|
||
// 注意:PathAnimationManager.GetInstance()返回的是单例实例,
|
||
// 不应该在这里Dispose,否则会影响其他使用该单例的地方
|
||
if (_pathAnimationManager != null)
|
||
{
|
||
try
|
||
{
|
||
// 只清理事件订阅和停止动画,不调用Dispose,也不重置位置(避免访问已释放对象)
|
||
_pathAnimationManager.ShutdownAnimation();
|
||
LogManager.Debug("PathAnimationManager已执行关闭清理");
|
||
}
|
||
catch (Exception resetEx)
|
||
{
|
||
LogManager.Warning($"执行PathAnimationManager关闭清理时出现警告: {resetEx.Message}");
|
||
}
|
||
}
|
||
|
||
// 3. 清理PathAnimationManager缓存(缓存功能已迁移到PathAnimationManager)
|
||
// PathAnimationManager的Dispose已经包含了ClearExclusionCache的调用
|
||
|
||
LogManager.Info("AnimationControlViewModel资源清理完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"AnimationControlViewModel资源清理过程中发生异常: {ex.Message}");
|
||
// 即使清理过程中发生异常,也不应该抛出,因为这会干扰应用程序的正常关闭
|
||
}
|
||
}
|
||
|
||
_disposed = true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归统计测试中的碰撞结果总数(包括分组内的结果)- 用于报告
|
||
/// </summary>
|
||
private int GetTotalClashResultCountForReport(ClashTest test)
|
||
{
|
||
if (test == null) return 0;
|
||
|
||
int totalCount = 0;
|
||
foreach (var child in test.Children)
|
||
{
|
||
if (child is ClashResult)
|
||
{
|
||
totalCount++;
|
||
}
|
||
else if (child is ClashResultGroup group)
|
||
{
|
||
// 递归统计分组内的结果
|
||
totalCount += CountClashResultsInGroupForReport(group);
|
||
}
|
||
}
|
||
|
||
return totalCount;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归统计分组内的碰撞结果数量 - 用于报告
|
||
/// </summary>
|
||
private int CountClashResultsInGroupForReport(ClashResultGroup group)
|
||
{
|
||
if (group == null) return 0;
|
||
|
||
int count = 0;
|
||
foreach (var child in group.Children)
|
||
{
|
||
if (child is ClashResult)
|
||
{
|
||
count++;
|
||
}
|
||
else if (child is ClashResultGroup nestedGroup)
|
||
{
|
||
// 递归处理嵌套分组
|
||
count += CountClashResultsInGroupForReport(nestedGroup);
|
||
}
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归获取测试中的所有碰撞结果(包括分组内的)- 用于报告
|
||
/// </summary>
|
||
private List<ClashResult> GetAllClashResultsFromTest(ClashTest test)
|
||
{
|
||
var results = new List<ClashResult>();
|
||
if (test == null) return results;
|
||
|
||
foreach (var child in test.Children)
|
||
{
|
||
if (child is ClashResult result)
|
||
{
|
||
results.Add(result);
|
||
}
|
||
else if (child is ClashResultGroup group)
|
||
{
|
||
// 递归获取分组内的结果
|
||
results.AddRange(GetAllClashResultsFromGroup(group));
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归获取分组内的所有碰撞结果 - 用于报告
|
||
/// </summary>
|
||
private List<ClashResult> GetAllClashResultsFromGroup(ClashResultGroup group)
|
||
{
|
||
var results = new List<ClashResult>();
|
||
if (group == null) return results;
|
||
|
||
foreach (var child in group.Children)
|
||
{
|
||
if (child is ClashResult result)
|
||
{
|
||
results.Add(result);
|
||
}
|
||
else if (child is ClashResultGroup nestedGroup)
|
||
{
|
||
// 递归处理嵌套分组
|
||
results.AddRange(GetAllClashResultsFromGroup(nestedGroup));
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |