using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api.Clash; using NavisworksTransport.Core; using NavisworksTransport.Core.Models; using NavisworksTransport.Core.Config; using NavisworksTransport.Commands; using NavisworksTransport.UI.WPF.Collections; using NavisworksTransport.UI.WPF.Views; using NavisworksTransport.Utils; using NavisworksTransport.Utils.CoordinateSystem; namespace NavisworksTransport.UI.WPF.ViewModels { /// /// 碰撞热点信息 - 用于分析高频碰撞物体 /// public class CollisionHotspotInfo { public ModelItem Object { get; set; } public string ObjectName { get; set; } public int CollisionCount { get; set; } public double Percentage { get; set; } public string Reason { get; set; } public string RecommendedAction { get; set; } } /// /// 碰撞报告数据 /// 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 Tests { get; set; } = new List(); } /// /// 碰撞测试信息 /// 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 Collisions { get; set; } = new List(); } /// /// 碰撞详细信息 /// 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; } /// /// 手工指定的碰撞检测对象 /// public class ManualCollisionTargetViewModel : INotifyPropertyChanged { private int _index; public ManualCollisionTargetViewModel(ModelItem modelItem, string displayName, string modelPath, int index = 0) { ModelItem = modelItem ?? throw new ArgumentNullException(nameof(modelItem)); DisplayName = displayName; ModelPath = modelPath; InstanceGuid = modelItem.InstanceGuid; _index = index; } public ModelItem ModelItem { get; } public string DisplayName { get; } public string ModelPath { get; } public Guid InstanceGuid { get; } public int Index { get => _index; set { if (_index != value) { _index = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } /// /// 排除对象ViewModel - 用于检测排除列表 /// public class ExcludedObjectViewModel : INotifyPropertyChanged { private int _index; public ExcludedObjectViewModel(ModelItem modelItem, string displayName, string modelPath, int index = 0) { ModelItem = modelItem ?? throw new ArgumentNullException(nameof(modelItem)); DisplayName = displayName; ModelPath = modelPath; InstanceGuid = modelItem.InstanceGuid; _index = index; } public ModelItem ModelItem { get; } public string DisplayName { get; } public string ModelPath { get; } public Guid InstanceGuid { get; } public int Index { get => _index; set { if (_index != value) { _index = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } /// /// 碰撞构件ViewModel /// public class CollisionObjectViewModel { public int Index { get; set; } public string DisplayName { get; set; } public string ObjectName { get; set; } public string ModelPath { get; set; } public int ResultId { get; set; } public string TestName { get; set; } public int? ModelIndex { get; set; } public string PathId { get; set; } public ModelItem ModelItem { get; set; } public ICommand HighlightCommand { get; set; } public ICommand ClearHighlightCommand { get; set; } // 动画跟踪点位置信息(用于还原碰撞场景) public Point3D AnimatedObjectTrackedPosition { get; set; } public double AnimatedObjectTrackedYawRadians { get; set; } public Rotation3D AnimatedObjectTrackedRotation { get; set; } public bool AnimatedObjectHasTrackedRotation { get; set; } public bool HasPositionInfo { get; set; } } /// /// ClashDetective碰撞检测结果视图模型 /// public class ClashDetectiveResultViewModel { private readonly Action _refreshCallback; public ClashDetectiveResultViewModel(CollisionDetectionRecord 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()); CollisionObjects = new ObservableCollection(); } public CollisionDetectionRecord Record { get; } // 运动物体(从碰撞历史数据库中加载) public ModelItem AnimatedObject { get; set; } // 虚拟物体尺寸(当IsVirtualObject=true时使用) public double VirtualObjectLengthInMeters { get; set; } public double VirtualObjectWidthInMeters { get; set; } public double VirtualObjectHeightInMeters { get; set; } public string TestTimeDisplay => Record.TestTime?.ToString("MM-dd HH:mm:ss") ?? Record.CreatedTime.ToString("MM-dd HH:mm:ss"); public string CollisionCountDisplay => $"{Record.CollisionCount}个"; public ICommand ViewCommand { get; } public ICommand ReportCommand { get; } public ICommand DeleteCommand { get; } public ObservableCollection CollisionObjects { get; } public void ExecuteHighlightCollisionObject(CollisionObjectViewModel collisionObject) { if (collisionObject == null) return; try { LogManager.Info($"[碰撞构件] 高亮显示构件: {collisionObject.DisplayName}"); // 清除之前的高亮 ModelHighlightHelper.ClearAllHighlights(); // 直接使用ModelItem高亮 if (collisionObject.ModelItem != null && ModelItemAnalysisHelper.IsModelItemValid(collisionObject.ModelItem)) { ModelHighlightHelper.HighlightItems( ModelHighlightHelper.ClashDetectiveResultsCategory, new[] { collisionObject.ModelItem }); LogManager.Info($"[碰撞构件] 已高亮构件: {collisionObject.DisplayName}"); } else { LogManager.Warning($"[碰撞构件] ModelItem无效或为空: {collisionObject.DisplayName}"); } } catch (Exception ex) { LogManager.Error($"[碰撞构件] 高亮显示失败: {ex.Message}", ex); } } private void ExecuteView() { // 高亮显示该次检测的碰撞对象 var clashIntegration = ClashDetectiveIntegration.Instance; ModelHighlightHelper.HighlightClashDetectiveResults(Record.TestName, clashIntegration.GetCurrentPathClashResults); } private void ExecuteReport() { // 打开该次检测的详细报告 try { var testName = Record.TestName; LogManager.Info($"[历史报告] 开始生成历史碰撞报告: {testName}"); // 直接使用现有的报告生成命令,但指定测试名称 // 常规检测已经高亮了,所以 hasHighlighted=true var command = GenerateCollisionReportCommand.CreateComprehensive(hasHighlighted: true); 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.DeleteDetectionResult(Record.Id); // 通过回调刷新列表 _refreshCallback?.Invoke(); } } } /// /// 动画控制视图模型 /// 专门处理路径动画播放和碰撞检测功能 /// public class AnimationControlViewModel : ViewModelBase, IDisposable { #region 私有字段 private readonly Core.Animation.PathAnimationManager _pathAnimationManager; private readonly ClashDetectiveIntegration _clashIntegration; private readonly UIStateManager _uiStateManager; private PathPlanningManager _pathPlanningManager; // 记录最近复用的历史检测记录ID(用于判断是否需要跳过动画生成) private int? _lastReusedRecordId; // 碰撞构件查看时保存的动画物体状态(用于恢复) private ModelItem _savedAnimatedObjectForCollision; #endregion #region 事件 /// /// 请求滚动到指定碰撞历史记录的事件(用于代码选中后UI自动滚动到该项) /// public event EventHandler RequestScrollToClashDetectiveResult; private ModelItemTransformHelper.ObjectStateSnapshot _savedAnimatedObjectStateForCollision; // 动画参数相关字段(从配置初始化) private ObservableCollection _availableFrameRates; private int _selectedFrameRate; private double _animationDuration; private bool _canStartAnimation = true; private bool _canPauseAnimation = false; private bool _canStopAnimation = false; // 碰撞检测相关字段 private CollisionReportResult _lastGeneratedReport; // 缓存最后生成的报告 // 碰撞检测参数字段(从配置初始化) private double _detectionTolerance; // 检测容差(米) private int _animationFrameRate; // 动画帧率(FPS) // 当前选中路径 private PathRouteViewModel _currentPathRoute; // 移动物体相关字段 private ModelItem _selectedAnimatedObject; private string _selectedAnimatedObjectName; private bool _hasSelectedAnimatedObject = false; private bool _canGenerateAnimation = false; // 虚拟物体相关字段 private bool _useVirtualObject = false; // 使用虚拟物体 private double _virtualObjectLengthInMeters; // 虚拟物体长度(米) private double _virtualObjectWidthInMeters; // 虚拟物体宽度(米) private double _virtualObjectHeightInMeters; // 虚拟物体高度(米) private double _safetyMarginInMeters; // 检测间隙(米),从路径编辑同步 // 角度修正相关字段 private LocalEulerRotationCorrection _objectRotationCorrection = LocalEulerRotationCorrection.Zero; // 物体绕宿主 X/Y/Z 轴的角度修正 // 移动物体原始尺寸(米,在选择物体时保存) private double _objectOriginalLength; // 物体原始长度(X方向) private double _objectOriginalWidth; // 物体原始宽度(Y方向) private double _objectOriginalHeight; // 物体原始高度(Z方向) /// /// 物体原始长度(X方向,米)- 选择物体时保存,不受旋转影响 /// public double ObjectOriginalLength => _objectOriginalLength; /// /// 物体原始宽度(Y方向,米)- 选择物体时保存,不受旋转影响 /// public double ObjectOriginalWidth => _objectOriginalWidth; /// /// 物体原始高度(Z方向,米)- 选择物体时保存,不受旋转影响 /// public double ObjectOriginalHeight => _objectOriginalHeight; // 手工碰撞对象相关字段 private bool _isManualCollisionTargetEnabled = true; private ObservableCollection _manualCollisionTargets; private string _manualCollisionTargetSummary = "未指定碰撞检测对象"; private DateTime? _manualTargetsLastSyncTime; private const int ManualCollisionTargetLimit = 60; private const string ManualTargetsHighlightCategory = ModelHighlightHelper.ManualTargetsCategory; private const string CollisionResultsHighlightCategory = ModelHighlightHelper.PrecomputeCollisionResultsCategory; // 检测排除对象相关字段 private ObservableCollection _excludedObjects; private string _excludedObjectSummary = "未指定排除对象"; private const string ExcludedObjectsHighlightCategory = "excludedObjects"; // 与ModelHighlightHelper中定义的绿色类别一致 #endregion #region 公共属性 /// /// 可用帧率集合 /// public ObservableCollection AvailableFrameRates { get => _availableFrameRates; set => SetProperty(ref _availableFrameRates, value); } /// /// 选中的帧率 /// public int SelectedFrameRate { get => _selectedFrameRate; set { if (SetProperty(ref _selectedFrameRate, value)) { // 同步更新AnimationFrameRate字段 _animationFrameRate = value; // 帧率变化时,步长需要重新计算(步长=路径长度/(帧率*时长)) OnPropertyChanged(nameof(CollisionDetectionAccuracy)); } } } /// /// 动画持续时间(秒) /// public double AnimationDuration { get => _animationDuration; set { if (SetProperty(ref _animationDuration, value)) { // 动画时长变化时,步长和速度需要重新计算 OnPropertyChanged(nameof(CollisionDetectionAccuracy)); OnPropertyChanged(nameof(MovementSpeed)); } } } /// /// 是否可以开始动画 /// public bool CanStartAnimation { get => _canStartAnimation; set => SetProperty(ref _canStartAnimation, value); } /// /// 是否可以暂停动画 /// public bool CanPauseAnimation { get => _canPauseAnimation; set => SetProperty(ref _canPauseAnimation, value); } /// /// 是否可以停止动画 /// public bool CanStopAnimation { get => _canStopAnimation; set => SetProperty(ref _canStopAnimation, value); } /// /// 是否有碰撞检测结果 /// private bool _hasClashDetectiveResults; public bool HasClashDetectiveResults { get => _hasClashDetectiveResults; set { if (SetProperty(ref _hasClashDetectiveResults, value)) { System.Windows.Input.CommandManager.InvalidateRequerySuggested(); } } } /// /// 碰撞检测精度(米) - 步长,根据路径长度、帧率和时长计算得出 /// 如果无法计算则返回0,表示数据不完整 /// 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; } } /// /// 运动速度(米/秒) - 根据路径长度和时长计算得出 /// 如果无法计算则返回0,表示数据不完整 /// 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; } } /// /// 路径长度(米) - 计算得出 /// public double PathLength { get { return GetPathLength(); } } /// /// 动画帧率(FPS) /// public int AnimationFrameRate { get => _animationFrameRate; set { if (SetProperty(ref _animationFrameRate, value)) { // 帧率变化时,步长需要重新计算 OnPropertyChanged(nameof(CollisionDetectionAccuracy)); } } } /// /// 检测容差(米) /// public double DetectionTolerance { get => _detectionTolerance; set { if (SetProperty(ref _detectionTolerance, value)) { // 容差变化时,碰撞检测精度需要重新计算 OnPropertyChanged(nameof(CollisionDetectionAccuracy)); } } } /// /// 当前选中的路径 /// public PathRouteViewModel CurrentPathRoute { get => _currentPathRoute; set { var oldRoute = _currentPathRoute; if (SetProperty(ref _currentPathRoute, value)) { // 路径改变时,步长、速度和路径长度需要重新计算 OnPropertyChanged(nameof(CollisionDetectionAccuracy)); OnPropertyChanged(nameof(MovementSpeed)); OnPropertyChanged(nameof(PathLength)); UpdateCanGenerateAnimation(); // 路径改变时,刷新ClashDetective结果列表 RefreshClashDetectiveResultsList(); // 🔥 路径改变时,如果已选择物体或虚拟物体,立即移动到新路径起点 // 注意:只有路径真正改变时才移动(避免初始设置null时的不必要移动) if (value != null && oldRoute != value) { MoveAnimatedObjectToPathStart(); } } } } /// /// 选中的移动物体 /// public ModelItem SelectedAnimatedObject { get => _selectedAnimatedObject; set { if (SetProperty(ref _selectedAnimatedObject, value)) { UpdateAnimatedObjectInfo(); UpdateCanGenerateAnimation(); // 预计算排除列表(异步) _ = PrecomputeCollisionExclusionsAsync(value); // 当选择了物体时,根据路径类型打开通行空间可视化 if (value != null) { UpdatePassageSpaceVisualization(); // 🔥 立即将物体移动到路径起点(如果路径已选择) MoveAnimatedObjectToPathStart(); } } } } /// /// 选中的移动物体名称 /// public string SelectedAnimatedObjectName { get => _selectedAnimatedObjectName; set => SetProperty(ref _selectedAnimatedObjectName, value); } /// /// 是否已选择移动物体 /// public bool HasSelectedAnimatedObject { get => _hasSelectedAnimatedObject; set => SetProperty(ref _hasSelectedAnimatedObject, value); } /// /// 是否可以生成动画 /// public bool CanGenerateAnimation { get => _canGenerateAnimation; set => SetProperty(ref _canGenerateAnimation, value); } #region 虚拟物体属性 /// /// 是否使用选择的模型物体 /// /// /// 是否使用虚拟物体 /// public bool UseVirtualObject { get => _useVirtualObject; set { if (SetProperty(ref _useVirtualObject, value)) { // ✨ 模式切换清理:切换到虚拟物体模式时,移除选择物体的动画数据 if (value) { try { // 🔥 将之前选择的实体物体归位(如果存在) // 注意:使用 SelectedAnimatedObject 而不是 _pathAnimationManager.AnimatedObject // 因为用户可能只选择了物体但还没有生成动画 if (_pathAnimationManager != null && SelectedAnimatedObject != null && !VirtualObjectManager.Instance.IsVirtualObjectActive) { // 先将实体物体设置到动画管理器,然后归位 _pathAnimationManager.SetAnimatedObject(SelectedAnimatedObject); _pathAnimationManager.RestoreObjectToCADPosition(); _pathAnimationManager.ClearAnimationResults(); // 清理手动选择物体留下的动画数据 LogManager.Info("已切换到虚拟物体模式,将之前的手动选择物体归位并清理动画数据"); } // 显示虚拟物体 LogManager.Info("正在显示虚拟物体..."); VirtualObjectManager.Instance.ShowVirtualObject( VirtualObjectLengthInMeters, VirtualObjectWidthInMeters, VirtualObjectHeightInMeters); // 重置角度修正值(虚拟物体重新选择时重置) ObjectRotationCorrection = LocalEulerRotationCorrection.Zero; LogManager.Debug("[切换虚拟物体] 已重置角度修正值为0"); // 当切换到虚拟物体模式时,根据路径类型打开通行空间可视化 UpdatePassageSpaceVisualization(); // 设置已选择物体状态(虚拟物体也算已选择) HasSelectedAnimatedObject = true; // 🔥 立即将虚拟物体移动到路径起点(如果路径已选择) MoveAnimatedObjectToPathStart(); } catch (Exception ex) { LogManager.Warning($"切换到虚拟物体模式失败: {ex.Message}"); } } else { // ✨ 模式切换清理:切换到手动选择模式时,隐藏虚拟物体并清理数据 try { // 🔥 先将虚拟物体归位(如果之前被移动过) if (_pathAnimationManager != null && VirtualObjectManager.Instance.IsVirtualObjectActive) { var metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); _pathAnimationManager.SetVirtualObjectParameters(true, VirtualObjectLengthInMeters * metersToUnits, VirtualObjectWidthInMeters * metersToUnits, VirtualObjectHeightInMeters * metersToUnits); _pathAnimationManager.RestoreObjectToCADPosition(); LogManager.Info("已切换到手动选择模式,虚拟物体已归位"); } // 隐藏虚拟物体 VirtualObjectManager.Instance.HideVirtualObject(); _pathAnimationManager?.ClearAnimationResults(); // 清理虚拟物体留下的动画数据 LogManager.Info("已切换到手动选择模式,自动隐藏虚拟物体并清理动画数据"); // 🔥 如果已选择实体物体且路径已选择,将实体物体移动到路径起点 if (SelectedAnimatedObject != null && CurrentPathRoute != null) { MoveAnimatedObjectToPathStart(); } } catch (Exception ex) { LogManager.Warning($"隐藏虚拟物体失败: {ex.Message}"); } } UpdateCanGenerateAnimation(); } } } /// /// 虚拟物体长度(米)- 只读,从路径编辑同步 /// public double VirtualObjectLengthInMeters { get => _virtualObjectLengthInMeters; private set => SetProperty(ref _virtualObjectLengthInMeters, value); } /// /// 虚拟物体宽度(米)- 只读,从路径编辑同步 /// public double VirtualObjectWidthInMeters { get => _virtualObjectWidthInMeters; private set => SetProperty(ref _virtualObjectWidthInMeters, value); } /// /// 虚拟物体高度(米)- 只读,从路径编辑同步 /// public double VirtualObjectHeightInMeters { get => _virtualObjectHeightInMeters; private set => SetProperty(ref _virtualObjectHeightInMeters, value); } /// /// 物体绕宿主 X/Y/Z 轴的角度修正。 /// public LocalEulerRotationCorrection ObjectRotationCorrection { get => _objectRotationCorrection; set { if (SetProperty(ref _objectRotationCorrection, value)) { // 角度修正值改变时,更新物体朝向和通行空间 UpdateObjectRotation(); } } } /// /// 设置虚拟物体参数(由主ViewModel调用) /// 参数单位:米 /// public void SetVirtualObjectParameters(double length, double width, double height, double safetyMarginInMeters) { LogManager.Info($"[AnimationControlViewModel] SetVirtualObjectParameters被调用: length={length:F4}, width={width:F4}, height={height:F4}, safetyMarginInMeters={safetyMarginInMeters:F4}"); VirtualObjectLengthInMeters = length; VirtualObjectWidthInMeters = width; VirtualObjectHeightInMeters = height; _safetyMarginInMeters = safetyMarginInMeters; LogManager.Info($"[AnimationControlViewModel] 虚拟物体参数已更新: {length:F1}米 × {width:F1}米 × {height:F1}米, 检测间隙: {safetyMarginInMeters:F2}米, _safetyMarginInMeters={_safetyMarginInMeters:F4}"); // 如果当前使用虚拟物体模式,更新生成动画的可用状态 if (UseVirtualObject) { UpdateCanGenerateAnimation(); // 更新通行空间可视化 UpdatePassageSpaceVisualization(); } } #endregion #region 手工碰撞对象属性 public bool IsManualCollisionTargetEnabled { get => _isManualCollisionTargetEnabled; set { if (SetProperty(ref _isManualCollisionTargetEnabled, value)) { HandleManualCollisionModeChanged(); } } } public ObservableCollection ManualCollisionTargets => _manualCollisionTargets; public string ManualCollisionTargetSummary { get => _manualCollisionTargetSummary; set => SetProperty(ref _manualCollisionTargetSummary, value); } public bool HasManualCollisionTargets => _manualCollisionTargets?.Count > 0; public bool IsManualTargetModeActive => IsManualCollisionTargetEnabled && HasManualCollisionTargets; // 检测排除对象公共属性 public ObservableCollection ExcludedObjects => _excludedObjects; public string ExcludedObjectSummary { get => _excludedObjectSummary; set => SetProperty(ref _excludedObjectSummary, value); } public bool HasExcludedObjects => _excludedObjects?.Count > 0; #endregion #region ClashDetective结果管理 private ObservableCollection _clashDetectiveResults = new ObservableCollection(); private ClashDetectiveResultViewModel _selectedClashDetectiveResult; public ObservableCollection ClashDetectiveResults => _clashDetectiveResults; public ClashDetectiveResultViewModel SelectedClashDetectiveResult { get => _selectedClashDetectiveResult; set { if (_selectedClashDetectiveResult != value) { // 切换前恢复动画物体状态 if (_selectedClashDetectiveResult != null) { RestoreAnimatedObjectFromCollisionView(); } _selectedClashDetectiveResult = value; OnPropertyChanged(); // 触发滚动事件,让View滚动到选中的项 if (_selectedClashDetectiveResult != null) { RequestScrollToClashDetectiveResult?.Invoke(this, _selectedClashDetectiveResult); } } } } public ICommand RefreshClashDetectiveResultsCommand { get; private set; } #endregion #region 媒体控制属性 /// /// 时间轴位置(0-总帧数) /// public double TimelinePosition { get => _pathAnimationManager?.CurrentFrame ?? 0; set { if (_pathAnimationManager != null) { _pathAnimationManager.SeekToFrame((int)Math.Round(value)); } OnPropertyChanged(); } } /// /// 时间轴持续时间(总帧数) /// public double TimelineDuration => _pathAnimationManager?.TotalFrames - 1 ?? 0; /// /// 当前时间显示(格式化) /// public string CurrentTimeDisplay { get { if (_pathAnimationManager == null) return "00:00"; var currentSeconds = (_pathAnimationManager.CurrentFrame / (double)(_pathAnimationManager.TotalFrames - 1)) * _animationDuration; return FormatTime(currentSeconds); } } /// /// 总时长显示(格式化) /// public string TotalTimeDisplay => FormatTime(_animationDuration); /// /// 帧信息显示 /// public string FrameInfoDisplay { get { if (_pathAnimationManager == null) return "0/0"; return $"{_pathAnimationManager.CurrentFrame + 1}/{_pathAnimationManager.TotalFrames}"; } } /// /// 是否正在正向播放 /// public bool IsPlayingForward => _pathAnimationManager?.PlaybackDirection == 1 && _pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing; /// /// 是否正在反向播放 /// 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 EditObjectRotationCommand { get; private set; } public ICommand GenerateAnimationCommand { get; private set; } public ICommand AddToBatchQueueCommand { 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 AddExcludedObjectsFromSelectionCommand { get; private set; } public ICommand ClearExcludedObjectsCommand { get; private set; } public ICommand RemoveExcludedObjectCommand { get; private set; } public ICommand HighlightAllExcludedObjectsCommand { get; private set; } public ICommand ClearExcludedHighlightCommand { get; private set; } public ICommand HighlightPrecomputedCollisionResultsCommand { get; private set; } public ICommand ClearPrecomputedCollisionHighlightsCommand { get; private set; } public ICommand ToggleWireframeModeCommand { 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 } /// /// 带主ViewModel参数的构造函数,支持统一状态栏 /// /// 主ViewModel,用于统一状态栏 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(); _manualCollisionTargets = new ObservableCollection(); _manualCollisionTargets.CollectionChanged += OnManualCollisionTargetsChanged; UpdateManualCollisionTargetSummary(); // 初始化检测排除对象集合 _excludedObjects = new ObservableCollection(); _excludedObjects.CollectionChanged += OnExcludedObjectsChanged; UpdateExcludedObjectSummary(); // 初始化设置 InitializeAnimationSettings(); // 初始化生成动画按钮状态(启用手工指定模式时检查列表) UpdateCanGenerateAnimation(); // 初始化命令 InitializeCommands(); // 订阅碰撞检测事件 _clashIntegration.CollisionDetected += OnCollisionDetected; _clashIntegration.ClashDetectiveResultSaved += OnClashDetectiveResultSaved; // 订阅文档状态事件 DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated; DocumentStateManager.Instance.DocumentReady += OnDocumentReady; // 🔥 如果文档已经有效,立即刷新碰撞检测历史列表 if (DocumentStateManager.Instance.IsDocumentValid) { RefreshClashDetectiveResultsList(); LogManager.Info("[AnimationControlViewModel] 文档已有效,初始化时加载碰撞检测历史列表"); } // 设置静态实例 _instance = this; LogManager.Info("AnimationControlViewModel初始化完成 - 支持统一状态栏"); } catch (Exception ex) { LogManager.Error($"AnimationControlViewModel初始化失败: {ex.Message}", ex); UpdateMainStatus("初始化失败,请检查日志"); throw; } } #endregion #region 公共方法 /// /// 设置PathPlanningManager(用于批处理队列功能) /// public void SetPathPlanningManager(PathPlanningManager pathPlanningManager) { _pathPlanningManager = pathPlanningManager; LogManager.Info("AnimationControlViewModel已设置PathPlanningManager"); } #endregion #region 初始化方法 /// /// 初始化动画设置 /// 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("碰撞检测就绪"); // 从配置读取安全间隙(用于预计算)- 使用米单位接口 _safetyMarginInMeters = config.PathEditing.SafetyMarginMeters; AnimationFrameRate = config.Animation.FrameRate; // 从配置读取检测容差(用于ClashDetective)- 使用米单位接口用于显示 _detectionTolerance = config.Animation.DetectionToleranceMeters; // 🔥 从配置加载虚拟物体尺寸(与路径编辑保持一致,使用米单位接口) VirtualObjectLengthInMeters = config.PathEditing.ObjectLengthMeters; VirtualObjectWidthInMeters = config.PathEditing.ObjectWidthMeters; VirtualObjectHeightInMeters = config.PathEditing.ObjectHeightMeters; // 设置动画按钮的初始状态 UpdateAnimationButtonStates(); LogManager.Info($"动画设置初始化完成 - 帧率:{SelectedFrameRate}fps, 持续时间:{AnimationDuration}秒, 安全间隙:{_safetyMarginInMeters}米"); // 订阅配置变更事件 SubscribeToConfigChanges(); } #region 配置变更处理 /// /// 订阅配置变更事件 /// private void SubscribeToConfigChanges() { try { ConfigManager.Instance.CategoryConfigurationChanged += OnCategoryConfigurationChanged; LogManager.Info("AnimationControlViewModel 已订阅配置变更事件"); } catch (Exception ex) { LogManager.Error($"订阅配置变更事件失败: {ex.Message}", ex); } } /// /// 取消订阅配置变更事件 /// private void UnsubscribeFromConfigChanges() { try { ConfigManager.Instance.CategoryConfigurationChanged -= OnCategoryConfigurationChanged; LogManager.Info("AnimationControlViewModel 已取消订阅配置变更事件"); } catch (Exception ex) { LogManager.Warning($"取消订阅配置变更事件时发生异常: {ex.Message}"); } } /// /// 配置变更事件处理 /// private void OnCategoryConfigurationChanged(object sender, Tuple args) { try { var category = args.Item1; var eventArgs = args.Item2; // 处理 Animation 类别变更 if (category == ConfigCategory.Animation || category == ConfigCategory.All) { LogManager.Info("收到 Animation 配置变更通知,正在更新参数..."); var config = ConfigManager.Instance.Current; // 更新动画参数 SelectedFrameRate = config.Animation.FrameRate; AnimationDuration = config.Animation.DurationSeconds; AnimationFrameRate = config.Animation.FrameRate; _detectionTolerance = config.Animation.DetectionToleranceMeters; LogManager.Info($"Animation 配置已更新 - 帧率:{SelectedFrameRate}fps, 持续时间:{AnimationDuration}秒"); } // 处理 PathEditing 类别变更(物体参数和安全间隙) if (category == ConfigCategory.PathEditing || category == ConfigCategory.All) { LogManager.Info("收到 PathEditing 配置变更通知,正在更新物体参数..."); var config = ConfigManager.Instance.Current; // 更新物体参数 VirtualObjectLengthInMeters = config.PathEditing.ObjectLengthMeters; VirtualObjectWidthInMeters = config.PathEditing.ObjectWidthMeters; VirtualObjectHeightInMeters = config.PathEditing.ObjectHeightMeters; _safetyMarginInMeters = config.PathEditing.SafetyMarginMeters; LogManager.Info($"物体参数已更新 - 尺寸:{VirtualObjectLengthInMeters:F1}x{VirtualObjectWidthInMeters:F1}x{VirtualObjectHeightInMeters:F1}米, 安全间隙:{_safetyMarginInMeters:F2}米"); } } catch (Exception ex) { LogManager.Error($"处理配置变更事件失败: {ex.Message}", ex); } } #endregion /// /// 初始化命令 /// 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(() => { // 重置角度修正值(每个物体的旋转独立) ObjectRotationCorrection = LocalEulerRotationCorrection.Zero; LogManager.Debug("[选择物体] 已重置角度修正值为0"); ExecuteSelectAnimatedObject(); }); ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject); EditObjectRotationCommand = new RelayCommand(ExecuteEditObjectRotation, () => HasSelectedAnimatedObject); GenerateAnimationCommand = new RelayCommand(ExecuteGenerateAnimation, () => CanGenerateAnimation); AddToBatchQueueCommand = new RelayCommand(ExecuteAddToBatchQueue, () => CanGenerateAnimation); ApplyManualTargetsFromSelectionCommand = new RelayCommand(ExecuteApplyManualTargetsFromSelection); ClearManualTargetsCommand = new RelayCommand(ExecuteClearManualTargets, () => HasManualCollisionTargets); RemoveManualTargetCommand = new RelayCommand(ExecuteRemoveManualTarget, target => target != null); HighlightManualTargetsCommand = new RelayCommand(ExecuteHighlightManualTargets, () => HasManualCollisionTargets); ClearManualHighlightsCommand = new RelayCommand(ExecuteClearManualHighlights, () => HasManualCollisionTargets); // 检测排除对象命令 AddExcludedObjectsFromSelectionCommand = new RelayCommand(ExecuteAddExcludedObjectsFromSelection); ClearExcludedObjectsCommand = new RelayCommand(ExecuteClearExcludedObjects, () => HasExcludedObjects); RemoveExcludedObjectCommand = new RelayCommand(ExecuteRemoveExcludedObject, obj => obj != null); HighlightAllExcludedObjectsCommand = new RelayCommand(ExecuteHighlightAllExcludedObjects, () => HasExcludedObjects); ClearExcludedHighlightCommand = new RelayCommand(ExecuteClearExcludedHighlight, () => HasExcludedObjects); HighlightPrecomputedCollisionResultsCommand = new RelayCommand(ExecuteHighlightPrecomputedCollisionResults, () => HasClashDetectiveResults); ClearPrecomputedCollisionHighlightsCommand = new RelayCommand(ExecuteClearPrecomputedCollisionHighlights, () => HasClashDetectiveResults); ToggleWireframeModeCommand = new RelayCommand(ExecuteToggleWireframeMode); 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 命令实现 /// /// 开始动画命令 /// 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; }); } } /// /// 暂停动画命令 /// 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("暂停动画失败"); }); } } /// /// 停止动画命令 /// 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); } } /// /// 查看碰撞报告命令(显示已缓存的报告) /// 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 媒体控制命令实现 /// /// 检查媒体控制命令是否可执行 /// private bool CanExecuteMediaCommands() { return _pathAnimationManager != null && _pathAnimationManager.CurrentState != NavisworksTransport.Core.Animation.AnimationState.Idle && _pathAnimationManager.TotalFrames > 0; } /// /// 执行正向播放命令 /// private void ExecutePlayForward() { try { // 自动调整视角到路径中心 try { var coreRoute = CurrentPathRoute?.Route; if (coreRoute != null) { ViewpointHelper.AdjustViewpointToPathCenter(coreRoute); LogManager.Info($"正向播放前:已自动调整视角到路径中心: {CurrentPathRoute.Name}"); } else { LogManager.Warning($"正向播放前:CurrentPathRoute.Route 为 null"); } } catch (Exception ex) { LogManager.Error($"正向播放前:调整视角失败: {ex.Message}"); } _pathAnimationManager?.PlayForward(); UpdateMediaControlProperties(); LogManager.Info("执行正向播放命令"); } catch (Exception ex) { LogManager.Error($"正向播放命令执行失败: {ex.Message}"); UpdateMainStatus("正向播放失败"); } } /// /// 执行反向播放命令 /// private void ExecutePlayReverse() { try { // 自动调整视角到路径中心 try { var coreRoute = CurrentPathRoute?.Route; if (coreRoute != null) { ViewpointHelper.AdjustViewpointToPathCenter(coreRoute); LogManager.Info($"反向播放前:已自动调整视角到路径中心: {CurrentPathRoute.Name}"); } else { LogManager.Warning($"反向播放前:CurrentPathRoute.Route 为 null"); } } catch (Exception ex) { LogManager.Error($"反向播放前:调整视角失败: {ex.Message}"); } _pathAnimationManager?.PlayReverse(); UpdateMediaControlProperties(); LogManager.Info("执行反向播放命令"); } catch (Exception ex) { LogManager.Error($"反向播放命令执行失败: {ex.Message}"); UpdateMainStatus("反向播放失败"); } } /// /// 执行单帧前进命令 /// private void ExecuteStepForward() { try { _pathAnimationManager?.StepForward(); UpdateMediaControlProperties(); LogManager.Debug("执行单帧前进命令"); } catch (Exception ex) { LogManager.Error($"单帧前进命令执行失败: {ex.Message}"); } } /// /// 执行单帧后退命令 /// private void ExecuteStepBackward() { try { _pathAnimationManager?.StepBackward(); UpdateMediaControlProperties(); LogManager.Debug("执行单帧后退命令"); } catch (Exception ex) { LogManager.Error($"单帧后退命令执行失败: {ex.Message}"); } } /// /// 执行快进10帧命令 /// private void ExecuteFastForward() { try { _pathAnimationManager?.FastForward(); UpdateMediaControlProperties(); LogManager.Debug("执行快进10帧命令"); } catch (Exception ex) { LogManager.Error($"快进命令执行失败: {ex.Message}"); } } /// /// 执行快退10帧命令 /// private void ExecuteFastBackward() { try { _pathAnimationManager?.FastBackward(); UpdateMediaControlProperties(); LogManager.Debug("执行快退10帧命令"); } catch (Exception ex) { LogManager.Error($"快退命令执行失败: {ex.Message}"); } } /// /// 执行跳转到开头命令 /// private void ExecuteSeekToStart() { try { _pathAnimationManager?.SeekToFrame(0); UpdateMediaControlProperties(); LogManager.Info("跳转到动画开头"); } catch (Exception ex) { LogManager.Error($"跳转到开头失败: {ex.Message}"); } } /// /// 执行跳转到结尾命令 /// private void ExecuteSeekToEnd() { try { int lastFrame = (_pathAnimationManager?.TotalFrames ?? 1) - 1; _pathAnimationManager?.SeekToFrame(lastFrame); UpdateMediaControlProperties(); LogManager.Info("跳转到动画结尾"); } catch (Exception ex) { LogManager.Error($"跳转到结尾失败: {ex.Message}"); } } /// /// 执行选择移动物体命令 /// 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. 保存旧物体引用,在设置新物体之前 var oldObject = _selectedAnimatedObject; bool isSameObject = oldObject != null && ReferenceEquals(oldObject, newObject); // 2. 如果有旧物体且与新物体不同,先归位 if (!isSameObject && oldObject != null) { _pathAnimationManager?.RestoreObjectToCADPosition(); LogManager.Info($"[选择物体] 已归位旧物体: {oldObject.DisplayName}"); } _pathAnimationManager?.ClearAnimationResults(); // 2. 先保存物体原始尺寸(在设置SelectedAnimatedObject之前) var bbox = newObject.BoundingBox(); double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); var adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); var axisConvention = ModelAxisConvention.CreateDefaultForHost(adapter.HostType); double objectLength = axisConvention.GetForwardSize(bbox); double objectWidth = axisConvention.GetSideSize(bbox); double objectHeight = axisConvention.GetUpSize(bbox); _objectOriginalLength = objectLength / metersToModelUnits; _objectOriginalWidth = objectWidth / metersToModelUnits; _objectOriginalHeight = objectHeight / metersToModelUnits; _pathAnimationManager?.SetRealObjectDimensions(objectLength, objectWidth, objectHeight); LogManager.Debug($"[选择物体] 保存原始尺寸: 长度={_objectOriginalLength:F2}m, 宽度={_objectOriginalWidth:F2}m, 高度={_objectOriginalHeight:F2}m"); // 3. 先注册到动画管理器,再设置 SelectedAnimatedObject。 // SelectedAnimatedObject 的 setter 会立即触发 MoveAnimatedObjectToPathStart(); // 如果先移动、后 SetAnimatedObject,会把“已摆到起点后的当前姿态”再次当成参考姿态缓存。 _pathAnimationManager?.SetAnimatedObject(newObject); SelectedAnimatedObject = newObject; LogManager.Info($"已选择移动物体: {SelectedAnimatedObject.DisplayName}"); // 选择实体物体意味着切换到实体模式,避免后续起点同步仍走虚拟物体分支。 if (UseVirtualObject) { UseVirtualObject = false; LogManager.Debug("[选择物体] 已切换到实体物体模式"); } // 只有选择不同的物体时,才重置角度修正值 if (!isSameObject) { // 重置 ViewModel 中的角度修正值(会自动同步到 PathAnimationManager) ObjectRotationCorrection = LocalEulerRotationCorrection.Zero; LogManager.Debug("[选择物体] 已重置角度修正值为0(新物体)"); } else { LogManager.Debug($"[选择物体] 保持当前角度修正值(同一物体)"); } } catch (Exception ex) { LogManager.Error($"选择物体失败: {ex.Message}"); } } /// /// 更新媒体控制相关属性 /// private void UpdateMediaControlProperties() { OnPropertyChanged(nameof(TimelinePosition)); OnPropertyChanged(nameof(TimelineDuration)); OnPropertyChanged(nameof(CurrentTimeDisplay)); OnPropertyChanged(nameof(FrameInfoDisplay)); OnPropertyChanged(nameof(IsPlayingForward)); OnPropertyChanged(nameof(IsPlayingReverse)); } /// /// 格式化时间显示 /// private string FormatTime(double seconds) { var timeSpan = TimeSpan.FromSeconds(Math.Max(0, seconds)); return $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}"; } #endregion #endregion #region 辅助方法 /// /// 设置当前路径 /// public void SetCurrentPath(PathRouteViewModel pathRoute) { // 🔥 先同步路径到 PathAnimationManager(在设置 CurrentPathRoute 之前) // 避免 CurrentPathRoute setter 中调用 MoveAnimatedObjectToPathStart 时 _route 还未设置 if (pathRoute != null && _pathAnimationManager != null) { var coreRoute = pathRoute.Route; if (coreRoute != null) { _pathAnimationManager.SetRoute(coreRoute); LogManager.Info($"[AnimationControlViewModel] 已同步路径到 PathAnimationManager: {coreRoute.Name}"); } } CurrentPathRoute = pathRoute; // 添加调试日志 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}"); } /// /// 处理动画进度变化事件 /// 使用Dispatcher更新UI /// private void OnAnimationProgressChanged(object sender, double progressPercent) { System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { // 更新时间轴相关属性 OnPropertyChanged(nameof(TimelinePosition)); OnPropertyChanged(nameof(CurrentTimeDisplay)); OnPropertyChanged(nameof(FrameInfoDisplay)); })); } /// /// 处理动画状态变化事件 /// private void OnAnimationStateChanged(object sender, Core.Animation.AnimationState state) { System.Windows.Application.Current.Dispatcher.BeginInvoke(new Func(async () => { 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(); // 更新媒体控制属性 // 检查检测是否被取消 if (_clashIntegration?.WasLastTestCanceled == true) { LogManager.Info("[动画完成] 检测已被取消,跳过报告生成和高亮"); // 重置取消标志 _clashIntegration.ClearWasLastTestCanceled(); break; } ModelHighlightHelper.ClearCollisionHighlights(); await GenerateAndSaveCollisionReport(); HighlightClashDetectiveResults(); break; case NavisworksTransport.Core.Animation.AnimationState.Stopped: CanStartAnimation = true; CanPauseAnimation = false; CanStopAnimation = false; UpdateMainStatus("动画已停止"); UpdateMediaControlProperties(); // 更新媒体控制属性 // 先清除所有碰撞高亮(包括预计算和ClashDetective) ModelHighlightHelper.ClearCollisionHighlights(); // 注意:Stopped状态不进行碰撞检测(例如切换路径时) break; default: UpdateMainStatus($"动画状态: {state}"); UpdateMediaControlProperties(); // 更新媒体控制属性 break; } })); } /// /// 生成并保存碰撞报告(无论有无碰撞都生成) /// private async Task GenerateAndSaveCollisionReport() { try { var animationManager = Core.Animation.PathAnimationManager.GetInstance(); var hasAnimationCollisions = animationManager?.AllCollisionResults?.Count > 0; // 统一使用GenerateCollisionReportCommand生成报告(无论有无碰撞) UpdateMainStatus($"正在生成碰撞报告...", -1, true); var generateReportCommand = NavisworksTransport.Commands.GenerateCollisionReportCommand.CreateComprehensive(); // 设置检测记录ID(优先使用ID查询,更可靠) var detectionRecordId = _pathAnimationManager?.CurrentDetectionRecordId; generateReportCommand.SetDetectionRecordId(detectionRecordId); LogManager.Info($"生成碰撞报告,使用检测记录ID: {detectionRecordId?.ToString() ?? "null"}"); var result = await generateReportCommand.ExecuteAsync(); if (result.IsSuccess && result is PathPlanningResult reportResult) { // 缓存报告结果,供后续查看使用 _lastGeneratedReport = reportResult.Data; // 保存到数据库 await SaveCollisionReportToDatabase(reportResult.Data); var collisionCount = reportResult.Data.TotalCollisions; if (collisionCount > 0) { UpdateMainStatus($"✅ 碰撞报告已生成并保存:{collisionCount} 个碰撞"); LogManager.Info("碰撞报告已自动生成并保存到数据库"); } else { UpdateMainStatus($"✅ 碰撞报告已生成并保存:0 个碰撞"); LogManager.Info("碰撞报告已自动生成并保存到数据库(无碰撞)"); } } else { UpdateMainStatus($"碰撞报告生成失败: {result.ErrorMessage}"); LogManager.Error($"自动生成碰撞报告失败: {result.ErrorMessage}"); } } catch (Exception ex) { LogManager.Error($"生成碰撞报告失败: {ex.Message}"); } } /// /// 高亮ClashDetective结果 /// private void HighlightClashDetectiveResults() { try { // 检查测试名称是否有效,如果为空可能是检测被跳过(配置相同) if (string.IsNullOrEmpty(_clashIntegration?.CurrentTestName)) { LogManager.Info("当前没有ClashDetective测试(可能配置相同已跳过检测),从历史记录加载"); // 使用当前检测记录ID查询数据库获取TestName var detectionRecordId = _pathAnimationManager?.CurrentDetectionRecordId; if (detectionRecordId.HasValue) { var pathDatabase = _pathPlanningManager?.GetPathDatabase(); var record = pathDatabase?.GetDetectionResultById(detectionRecordId.Value); if (record != null && !string.IsNullOrEmpty(record.TestName)) { // 设置到ClashDetectiveIntegration以便后续使用 _clashIntegration.SetCurrentTestName(record.TestName); LogManager.Info($"已从数据库加载TestName: {record.TestName}"); } } } // 再次检查测试名称 if (string.IsNullOrEmpty(_clashIntegration?.CurrentTestName)) { LogManager.Info("无法获取ClashDetective测试名称,跳过高亮"); HasClashDetectiveResults = false; return; } // 检查是否有当前测试的ClashDetective结果 var clashResults = _clashIntegration.GetCurrentTestResults(); // 统一处理:无论有无碰撞都高亮(有碰撞时高亮碰撞对象,无碰撞时无操作) if (clashResults != null && clashResults.Count > 0) { ModelHighlightHelper.HighlightClashDetectiveResults(_clashIntegration.CurrentTestName, _clashIntegration.GetCurrentPathClashResults); HasClashDetectiveResults = true; LogManager.Info($"动画结束,已高亮ClashDetective检测结果:{clashResults.Count}个碰撞"); } else { HasClashDetectiveResults = false; } // 插入步骤:如果没有碰撞,显示祝贺窗口 if (clashResults == null || clashResults.Count == 0) { LogManager.Info("🎉 恭喜!本次动画未检测到任何碰撞,路径规划非常安全!"); System.Windows.MessageBox.Show( "🎉 恭喜!本次动画仿真过程中未发现任何碰撞!\n\n您的物流路径规划非常成功,物体可以安全通行。", "仿真完成 - 无碰撞", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information); } // 统一流程:刷新历史列表并显示报告(无论有无碰撞) LogManager.Info($"[HighlightClashDetectiveResults] 刷新历史列表,当前路径: {CurrentPathRoute?.Name}"); RefreshClashDetectiveResultsList(); if (_lastGeneratedReport != null) { NavisworksTransport.UI.WPF.Views.CollisionReportDialog.ShowReport(_lastGeneratedReport); } } catch (Exception ex) { LogManager.Error($"高亮ClashDetective结果失败: {ex.Message}"); } } /// /// 处理ClashDetective的碰撞检测事件 - 已废弃,报告生成统一在CheckAndHighlightClashDetectiveResults中处理 /// private void OnCollisionDetected(object sender, CollisionDetectedEventArgs e) { // 报告生成已统一在CheckAndHighlightClashDetectiveResults中处理 // 此事件保留用于日志记录和未来扩展 try { var clashResults = _clashIntegration?.GetCurrentTestResults(); var collisionCount = clashResults?.Count ?? 0; LogManager.Info($"[OnCollisionDetected] 碰撞检测事件触发,碰撞数量: {collisionCount}"); } catch (Exception ex) { LogManager.Error($"处理碰撞检测事件失败: {ex.Message}", ex); } } /// /// ClashDetective结果保存事件处理 /// 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); } } /// /// 保存碰撞报告到数据库 /// 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 ?? ""; // 注意:检测记录(包括排除列表)已在生成动画时保存到 CollisionDetectionRecords 表 // 这里只保存碰撞报告,不重复保存排除列表 // 从报告中获取所有需要的数据 await Task.Run(() => { // 保存碰撞报告 pathDatabase.SaveCollisionReport( routeId, reportResult.PathName, SelectedAnimatedObjectName ?? "未知对象", reportResult.UniqueCollidedObjectsCount, reportResult.FrameRate, reportResult.Duration, reportResult.DetectionTolerance, reportResult.AnimationCollisions, reportResult.TotalCollisions ); // 关联排除对象到检测记录 if (reportResult.ResultId > 0 && _excludedObjects != null && _excludedObjects.Count > 0) { // 获取排除对象的记录ID var excludedRecords = pathDatabase.GetExcludedObjects(routeId, includeGlobal: true); var excludedObjectIds = excludedRecords.Select(r => r.Id).ToList(); pathDatabase.LinkExcludedObjectsToDetectionRecord(reportResult.ResultId, excludedObjectIds); LogManager.Info($"[排除列表] 已关联 {excludedObjectIds.Count} 个排除对象到检测记录 (DetectionRecordId={reportResult.ResultId})"); } }); LogManager.Info($"碰撞报告已保存到数据库 - 路径:{reportResult.PathName}, " + $"碰撞构件:{reportResult.UniqueCollidedObjectsCount}, " + $"动画碰撞:{reportResult.AnimationCollisions}, " + $"ClashDetective:{reportResult.TotalCollisions}, " + $"排除对象:{_excludedObjects?.Count ?? 0}"); } else { LogManager.Warning("无法获取PathDatabase实例或报告数据为空,碰撞报告未保存到数据库"); } } catch (Exception ex) { LogManager.Error($"保存碰撞报告到数据库失败: {ex.Message}", ex); } } /// /// 执行清除移动物体命令 /// 清除移动物体选择、停止当前动画并更新按钮状态 /// private void ExecuteClearAnimatedObject() { try { if (_pathAnimationManager != null) { // 1. 归位并清理 _pathAnimationManager.RestoreObjectToCADPosition(); _pathAnimationManager.ClearAnimationResults(); LogManager.Info("已清除PathAnimationManager中的动画数据并归位物体"); } // 2. 先清空选择,避免角度修正归零时又触发“移动到起点”链路。 SelectedAnimatedObject = null; // 3. 重置角度修正值为0 ObjectRotationCorrection = LocalEulerRotationCorrection.Zero; LogManager.Debug("[清除物体] 已重置角度修正值为0"); // 4. 重置属性 UpdateAnimatedObjectInfo(); UpdateCanGenerateAnimation(); // 5. 清理高亮 ModelHighlightHelper.ClearCollisionHighlights(); LogManager.Info("移动物体已完全清除并归位"); UpdateMainStatus("已清除物体选择并恢复位置"); } catch (Exception ex) { LogManager.Error($"清除失败: {ex.Message}"); UpdateMainStatus($"清除失败: {ex.Message}"); } } /// /// 执行编辑物体角度修正 /// private void ExecuteEditObjectRotation() { try { var dialog = new Views.EditRotationWindow(_objectRotationCorrection); if (dialog.ShowDialog() == true) { var placementRequest = dialog.AdjustmentRequest; if (placementRequest.PreserveInitialPose) { ApplyTranslationOnlyObjectPlacement(); return; } _pathAnimationManager?.SetObjectStartPlacementMode(ObjectStartPlacementMode.AlignToPathPose); ObjectRotationCorrection = placementRequest.RotationCorrection; LogManager.Info($"物体角度修正已更新: {_objectRotationCorrection}"); UpdateMainStatus($"物体角度修正: {_objectRotationCorrection}"); // 应用角度修正到物体 UpdateObjectRotation(); // 更新通行空间可视化(考虑旋转后的尺寸) UpdatePassageSpaceVisualization(); } } catch (Exception ex) { LogManager.Error($"编辑物体角度失败: {ex.Message}"); UpdateMainStatus($"编辑物体角度失败: {ex.Message}"); } } private void ApplyTranslationOnlyObjectPlacement() { try { if (_pathAnimationManager == null || !HasSelectedAnimatedObject) { return; } _objectRotationCorrection = LocalEulerRotationCorrection.Zero; _pathAnimationManager.SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection.Zero); _pathAnimationManager.SetObjectStartPlacementMode(ObjectStartPlacementMode.PreserveInitialPose); OnPropertyChanged(nameof(ObjectRotationCorrection)); if (_pathAnimationManager.MoveObjectToPathStartPreservingInitialPose()) { LogManager.Info("[角度修正] 已按平移模式将物体移动到路径起点,保持初始位姿"); UpdateMainStatus("物体已平移到路径起点并保持初始位姿"); } else { LogManager.Warning("[角度修正] 平移模式未能将物体移动到路径起点"); UpdateMainStatus("平移模式执行失败"); } UpdatePassageSpaceVisualization(); } catch (Exception ex) { LogManager.Error($"平移到路径起点失败: {ex.Message}"); UpdateMainStatus($"平移到路径起点失败: {ex.Message}"); } } /// /// 更新物体旋转(应用角度修正) /// private void UpdateObjectRotation() { try { if (_pathAnimationManager != null && HasSelectedAnimatedObject) { // 将角度修正传递给动画管理器 _pathAnimationManager.SetObjectRotationCorrection(_objectRotationCorrection); LogManager.Debug($"[角度修正] 已更新物体旋转: {_objectRotationCorrection}"); } } catch (Exception ex) { LogManager.Error($"更新物体旋转失败: {ex.Message}"); } } /// /// 将动画对象(物体或虚拟物体)移动到当前路径的起点 /// 用于在选择物体/虚拟物体或切换路径时立即定位到起点,方便用户预览和调整角度 /// private void MoveAnimatedObjectToPathStart() { try { // 检查是否有路径 if (CurrentPathRoute == null || CurrentPathRoute.Points == null || CurrentPathRoute.Points.Count < 2) { LogManager.Debug("[移动到起点] 路径未选择或路径点不足,跳过移动"); return; } // 检查是否有动画对象(物体或虚拟物体) if (!HasSelectedAnimatedObject) { LogManager.Debug("[移动到起点] 未选择动画对象,跳过移动"); return; } // 获取路径点 var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList(); // 根据当前模式确定动画对象 ModelItem animatedObject = null; if (UseVirtualObject) { // 虚拟物体模式:获取当前虚拟物体 animatedObject = VirtualObjectManager.Instance.CurrentVirtualObject; if (animatedObject == null) { LogManager.Debug("[移动到起点] 虚拟物体未创建,跳过移动"); return; } // 设置虚拟物体参数到动画管理器(转换为模型单位) var metersToUnitsMove = UnitsConverter.GetMetersToUnitsConversionFactor(); _pathAnimationManager?.SetVirtualObjectParameters(true, VirtualObjectLengthInMeters * metersToUnitsMove, VirtualObjectWidthInMeters * metersToUnitsMove, VirtualObjectHeightInMeters * metersToUnitsMove); } else { // 实体物体模式:使用选中的物体 animatedObject = SelectedAnimatedObject; if (animatedObject == null) { LogManager.Debug("[移动到起点] 选中物体为空,跳过移动"); return; } } // 执行移动 if (_pathAnimationManager != null && animatedObject != null) { bool moved = _pathAnimationManager.MoveObjectToPathStart(animatedObject, pathPoints); if (moved) { LogManager.Info($"[移动到起点] {(UseVirtualObject ? "虚拟物体" : "物体")} 已移动到路径起点"); } else { LogManager.Warning($"[移动到起点] {(UseVirtualObject ? "虚拟物体" : "物体")} 移动失败(可能路径未设置)"); } } } catch (Exception ex) { LogManager.Warning($"[移动到起点] 移动动画对象到路径起点失败: {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(); 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); } // 更新序号 UpdateManualCollisionTargetIndices(); _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("已清除手工对象高亮"); } /// /// 聚焦到手工指定的对象 /// public void FocusOnManualTarget(ManualCollisionTargetViewModel target) { if (target?.ModelItem == null) return; try { // 先高亮该对象 var items = new List { target.ModelItem }; ModelHighlightHelper.HighlightItems(ManualTargetsHighlightCategory, items); // 聚焦到对象(斜上方45度视角) ViewpointHelper.FocusOnModelItem(target.ModelItem, viewAngleDegrees: 60.0, targetViewRatio: 0.25); UpdateMainStatus($"已聚焦到: {target.DisplayName}"); LogManager.Info($"聚焦到手工指定对象: {target.DisplayName}"); } catch (Exception ex) { LogManager.Error($"聚焦到手工指定对象失败: {ex.Message}", ex); UpdateMainStatus($"聚焦失败: {ex.Message}"); } } /// /// 聚焦到排除的对象 /// public void FocusOnExcludedObject(ExcludedObjectViewModel excludedObject) { if (excludedObject?.ModelItem == null) return; try { // 先高亮该对象(使用排除对象专用颜色) var items = new List { excludedObject.ModelItem }; ModelHighlightHelper.HighlightItems(ExcludedObjectsHighlightCategory, items); // 聚焦到对象(斜上方45度视角) ViewpointHelper.FocusOnModelItem(excludedObject.ModelItem, viewAngleDegrees: 60.0, targetViewRatio: 0.25); UpdateMainStatus($"已聚焦到排除对象: {excludedObject.DisplayName}"); LogManager.Info($"聚焦到排除对象: {excludedObject.DisplayName}"); } catch (Exception ex) { LogManager.Error($"聚焦到排除对象失败: {ex.Message}", ex); UpdateMainStatus($"聚焦失败: {ex.Message}"); } } /// /// 聚焦到碰撞构件,并移动动画物体到碰撞位置 /// public void FocusOnCollisionObject(CollisionObjectViewModel collisionObject) { if (collisionObject?.ModelItem == null) return; // 从历史记录中获取运动物体 var selectedResult = SelectedClashDetectiveResult; var animatedObject = selectedResult?.AnimatedObject; // 如果是虚拟物体且尚未创建,现在创建它 if (selectedResult?.Record?.IsVirtualObject == true && animatedObject == null) { try { double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(Autodesk.Navisworks.Api.Application.ActiveDocument.Units); // 创建并显示虚拟物体(尺寸从模型单位转换为米) animatedObject = VirtualObjectManager.Instance.CreateVirtualObject( selectedResult.VirtualObjectLengthInMeters * unitsToMeters, selectedResult.VirtualObjectWidthInMeters * unitsToMeters, selectedResult.VirtualObjectHeightInMeters * unitsToMeters); selectedResult.AnimatedObject = animatedObject; LogManager.Info($"[碰撞构件] 创建虚拟物体并聚焦: {selectedResult.VirtualObjectLengthInMeters:F2}x{selectedResult.VirtualObjectWidthInMeters:F2}x{selectedResult.VirtualObjectHeightInMeters:F2}"); } catch (Exception ex) { LogManager.Warning($"[碰撞构件] 创建虚拟物体失败: {ex.Message}"); } } if (animatedObject == null) { LogManager.Warning($"[碰撞构件] 未找到运动物体,仅聚焦到被撞对象: {collisionObject.DisplayName}"); // 仅聚焦,不移动物体 CollisionSceneHelper.MoveToCollisionAndFocus( collisionObject.ModelItem, null, collisionObject.AnimatedObjectTrackedPosition, collisionObject.AnimatedObjectTrackedYawRadians, collisionObject.AnimatedObjectTrackedRotation, collisionObject.AnimatedObjectHasTrackedRotation, false); return; } // 🔥 关键修复:如果运动物体是虚拟物体,确保它是可见的(可能被之前的切换隐藏了) if (selectedResult?.Record?.IsVirtualObject == true) { try { double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(Autodesk.Navisworks.Api.Application.ActiveDocument.Units); // 显示虚拟物体(如果已存在且尺寸相同,会显示;如果尺寸不同,会更新尺寸) VirtualObjectManager.Instance.ShowVirtualObject( selectedResult.VirtualObjectLengthInMeters * unitsToMeters, selectedResult.VirtualObjectWidthInMeters * unitsToMeters, selectedResult.VirtualObjectHeightInMeters * unitsToMeters); LogManager.Debug("[碰撞构件] 虚拟物体已确保可见"); } catch (Exception ex) { LogManager.Warning($"[碰撞构件] 显示虚拟物体失败: {ex.Message}"); } } try { // 首次查看时保存动画物体状态 if (_savedAnimatedObjectStateForCollision == null || _savedAnimatedObjectForCollision != animatedObject) { _savedAnimatedObjectForCollision = animatedObject; _savedAnimatedObjectStateForCollision = CollisionSceneHelper.SaveAnimatedObjectState(animatedObject); } // 移动动画物体到碰撞位置并聚焦 CollisionSceneHelper.MoveToCollisionAndFocus( collisionObject.ModelItem, animatedObject, collisionObject.AnimatedObjectTrackedPosition, collisionObject.AnimatedObjectTrackedYawRadians, collisionObject.AnimatedObjectTrackedRotation, collisionObject.AnimatedObjectHasTrackedRotation, collisionObject.HasPositionInfo); // 🔥 虚拟物体缩放已通过MoveItemToPositionAndYawWithCurrentScale保留 UpdateMainStatus($"已聚焦到碰撞构件: {collisionObject.DisplayName}"); LogManager.Info($"聚焦到碰撞构件: {collisionObject.DisplayName}"); } catch (Exception ex) { LogManager.Error($"聚焦到碰撞构件失败: {ex.Message}", ex); UpdateMainStatus($"聚焦失败: {ex.Message}"); } } /// /// 恢复动画物体到原始状态(失去焦点或切换历史记录时调用) /// public void RestoreAnimatedObjectFromCollisionView() { if (_savedAnimatedObjectForCollision != null && _savedAnimatedObjectStateForCollision != null) { CollisionSceneHelper.RestoreAnimatedObjectState(_savedAnimatedObjectForCollision, _savedAnimatedObjectStateForCollision); LogManager.Info($"[碰撞构件] 运动物体已恢复到原始状态: {_savedAnimatedObjectForCollision.DisplayName}"); } // 隐藏虚拟物体(如果之前显示的是虚拟物体) if (VirtualObjectManager.Instance.IsVirtualObjectActive) { VirtualObjectManager.Instance.HideVirtualObject(); LogManager.Info("[碰撞构件] 虚拟物体已隐藏"); } _savedAnimatedObjectStateForCollision = null; _savedAnimatedObjectForCollision = null; } #endregion #region 检测排除对象命令 /// /// 从当前选择添加排除对象 /// private void ExecuteAddExcludedObjectsFromSelection() { 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(); foreach (ModelItem item in selectedItems) { if (item == null) continue; if (!ModelItemAnalysisHelper.IsModelItemValid(item) || !HasGeometryRecursive(item)) continue; if (ExcludedObjectExists(item)) continue; var displayName = ModelItemAnalysisHelper.GetSafeDisplayName(item); var modelPath = BuildModelPath(item); toAdd.Add(new ExcludedObjectViewModel(item, displayName, modelPath)); } if (toAdd.Count == 0) { UpdateMainStatus("选中的对象已在排除列表中或不包含几何体"); return; } int startIndex = _excludedObjects.Count; foreach (var vm in toAdd) { vm.Index = ++startIndex; _excludedObjects.Add(vm); } UpdateMainStatus($"已添加 {toAdd.Count} 个排除对象"); LogManager.Info($"[排除对象] 添加 {toAdd.Count} 个对象到排除列表"); // 同步到PathAnimationManager SyncExcludedObjectsToAnimationManager(); } catch (Exception ex) { LogManager.Error($"添加排除对象失败: {ex.Message}"); UpdateMainStatus("添加排除对象失败"); } } /// /// 清除所有排除对象 /// private void ExecuteClearExcludedObjects() { try { int count = _excludedObjects.Count; _excludedObjects.Clear(); ModelHighlightHelper.ClearCategory(ExcludedObjectsHighlightCategory); // 同步到PathAnimationManager _pathAnimationManager?.ClearExcludedObjects(); UpdateMainStatus($"已清除 {count} 个排除对象"); LogManager.Info($"[排除对象] 清除所有 {count} 个排除对象"); } catch (Exception ex) { LogManager.Error($"清除排除对象失败: {ex.Message}"); } } /// /// 移除单个排除对象 /// private void ExecuteRemoveExcludedObject(ExcludedObjectViewModel obj) { if (obj == null) return; if (_excludedObjects.Remove(obj)) { // 重新编号 for (int i = 0; i < _excludedObjects.Count; i++) { _excludedObjects[i].Index = i + 1; } UpdateMainStatus($"已移除排除对象 {obj.DisplayName}"); LogManager.Info($"[排除对象] 移除 {obj.DisplayName}"); // 同步到PathAnimationManager(会清除缓存) SyncExcludedObjectsToAnimationManager(); // 清除该对象的高亮显示 ModelHighlightHelper.ClearCategory(ExcludedObjectsHighlightCategory); } } /// /// 高亮显示所有排除对象(全部高亮) /// private void ExecuteHighlightAllExcludedObjects() { try { if (_excludedObjects.Count == 0) return; var itemsToHighlight = _excludedObjects.Select(e => e.ModelItem).Where(m => m != null).ToList(); ModelHighlightHelper.HighlightItems(ExcludedObjectsHighlightCategory, itemsToHighlight); UpdateMainStatus($"已高亮全部 {itemsToHighlight.Count} 个排除对象"); LogManager.Info($"[排除对象] 高亮显示全部 {itemsToHighlight.Count} 个对象"); } catch (Exception ex) { LogManager.Error($"高亮排除对象失败: {ex.Message}"); } } /// /// 清除排除对象高亮 /// private void ExecuteClearExcludedHighlight() { try { ModelHighlightHelper.ClearCategory(ExcludedObjectsHighlightCategory); UpdateMainStatus("已清除排除对象高亮"); LogManager.Info("[排除对象] 清除高亮"); } catch (Exception ex) { LogManager.Error($"清除排除对象高亮失败: {ex.Message}"); } } /// /// 高亮显示单个排除对象(点选时) /// public void HighlightSingleExcludedObject(ExcludedObjectViewModel obj) { try { if (obj?.ModelItem == null) return; var items = new List { obj.ModelItem }; ModelHighlightHelper.HighlightItems(ExcludedObjectsHighlightCategory, items); LogManager.Debug($"[排除对象] 高亮显示单个对象: {obj.DisplayName}"); } catch (Exception ex) { LogManager.Error($"高亮单个排除对象失败: {ex.Message}"); } } /// /// 检查排除对象是否已存在 /// private bool ExcludedObjectExists(ModelItem item) { if (item == null) return false; // 使用ModelItemEquals比较底层原生对象(项目中推荐的比较方式) bool exists = _excludedObjects.Any(e => ModelItemAnalysisHelper.ModelItemEquals(e.ModelItem, item)); if (exists) { LogManager.Debug($"[排除对象] 对象已存在: {ModelItemAnalysisHelper.GetSafeDisplayName(item)}"); } return exists; } /// /// 同步排除对象到PathAnimationManager /// private void SyncExcludedObjectsToAnimationManager() { var objectsToExclude = _excludedObjects.Select(e => e.ModelItem).Where(m => m != null).ToList(); // 使用 SetExcludedObjectsAndClearCache 方法,更新排除列表并清除缓存(确保预计算能实时反映变更) _pathAnimationManager?.SetExcludedObjectsAndClearCache(objectsToExclude); LogManager.Debug($"[排除对象] 已同步 {objectsToExclude.Count} 个对象到PathAnimationManager,并清除动画缓存"); } /// /// 从对话框结果添加排除对象到UI列表 /// private void AddExcludedObjectsToUIList(List objects) { if (objects == null || objects.Count == 0) return; int startIndex = _excludedObjects.Count; foreach (var item in objects) { if (item == null || ExcludedObjectExists(item)) continue; var displayName = ModelItemAnalysisHelper.GetSafeDisplayName(item); var modelPath = BuildModelPath(item); var vm = new ExcludedObjectViewModel(item, displayName, modelPath, ++startIndex); _excludedObjects.Add(vm); } UpdateExcludedObjectSummary(); LogManager.Info($"[排除对象] 从对话框添加 {_excludedObjects.Count} 个排除对象到UI列表"); } /// /// 排除对象集合变更事件处理 /// private void OnExcludedObjectsChanged(object sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(HasExcludedObjects)); UpdateExcludedObjectSummary(); // 刷新命令可用状态 System.Windows.Input.CommandManager.InvalidateRequerySuggested(); } /// /// 更新排除对象统计信息 /// private void UpdateExcludedObjectSummary() { if (_excludedObjects == null || _excludedObjects.Count == 0) { ExcludedObjectSummary = "未指定排除对象"; } else { ExcludedObjectSummary = $"已指定 {_excludedObjects.Count} 个排除对象"; } } #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.ManageCollisionHighlightsByCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory, deduplicatedResults, clearOtherCategories: 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 async Task AnalyzeAndHighlightPrecomputedCollisionsAsync() { try { // 1. 获取预计算碰撞结果(从PathAnimationManager,现在缓存帧中已包含碰撞结果) var allResults = _pathAnimationManager?.AllCollisionResults; if (allResults == null || allResults.Count == 0) { LogManager.Info("[碰撞分析] 没有预计算碰撞结果,跳过分析"); // 🔥 没有预计算碰撞,配置已确定,直接保存检测记录 var recordId = SaveCollisionDetectionRecord(); if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value)) { LogManager.Info("[碰撞分析] 用户选择使用历史记录,跳过动画生成"); return; } return; } // 去重:按碰撞对象对去重(取每对的第一次出现) var deduplicatedResults = allResults .GroupBy(r => new { Item1 = r.Item1, Item2 = r.Item2 }) .Select(g => g.First()) .ToList(); LogManager.Info($"[碰撞分析] 开始分析 {allResults.Count} 个碰撞结果(去重后 {deduplicatedResults.Count} 个)"); // 2. 自动高亮所有预计算碰撞结果 ModelHighlightHelper.ManageCollisionHighlightsByCategory( ModelHighlightHelper.PrecomputeCollisionResultsCategory, deduplicatedResults, clearOtherCategories: false); LogManager.Info($"[碰撞分析] 已高亮 {deduplicatedResults.Count} 个碰撞对象"); // 3. 分析碰撞热点(使用原始结果 - 按频率统计) var hotspots = AnalyzeCollisionHotspots(allResults); if (hotspots.Count > 0) { LogManager.Info($"[碰撞分析] 发现 {hotspots.Count} 个高频碰撞物体"); foreach (var hotspot in hotspots.Take(5)) { LogManager.Info($" - {hotspot.ObjectName}: {hotspot.CollisionCount} 次 ({hotspot.Percentage:F1}%) - {hotspot.Reason}"); } // 4. 显示分析对话框(在UI线程)- 传递原始总数用于百分比计算 await _uiStateManager.ExecuteUIUpdateAsync(() => { ShowCollisionAnalysisDialog(hotspots, allResults.Count); }); } else { LogManager.Info("[碰撞分析] 没有发现明显的高频碰撞物体"); // 🔥 有碰撞但无高频热点,配置已确定,保存检测记录 var recordId = SaveCollisionDetectionRecord(); if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value)) { // 用户选择使用历史记录,跳过后续流程 LogManager.Info("[碰撞分析] 用户选择使用历史记录,跳过动画生成"); return; } } } catch (Exception ex) { LogManager.Error($"[碰撞分析] 分析预计算碰撞结果失败: {ex.Message}", ex); } } /// /// 分析碰撞热点 - 找出高频碰撞的物体 /// private List AnalyzeCollisionHotspots(List results) { // 按被撞物体分组统计 var objectStats = results .Where(r => r.Item2 != null) .GroupBy(r => r.Item2) .Select(g => new { Object = g.Key, Count = g.Count(), Percentage = (double)g.Count() / results.Count * 100 }) .Where(x => x.Count >= 10 || x.Percentage > 10) // 只关注高频物体 .OrderByDescending(x => x.Count) .ToList(); var hotspots = new List(); foreach (var stat in objectStats) { var objectName = stat.Object.DisplayName ?? "未知对象"; var reason = GetHotspotReason(objectName, stat.Object); hotspots.Add(new CollisionHotspotInfo { Object = stat.Object, ObjectName = objectName, CollisionCount = stat.Count, Percentage = stat.Percentage, Reason = reason, RecommendedAction = reason.Contains("地面") || reason.Contains("楼板") || reason.Contains("墙体") ? "建议排除" : "需要检查" }); } return hotspots; } /// /// 获取热点原因分析 /// private string GetHotspotReason(string objectName, ModelItem item) { var name = objectName.ToLower(); // 检查名称特征 if (name.Contains("地面") || name.Contains("楼板") || name.Contains("floor") || name.Contains("slab")) return "地面/楼板 - 大面积平面物体,容易产生假阳性碰撞"; if (name.Contains("墙体") || name.Contains("wall") || name.Contains("外墙")) return "墙体 - 沿路径延伸,容易产生假阳性碰撞"; if (name.Contains("楼梯") || name.Contains("stair")) return "楼梯 - 结构复杂,可能产生多次碰撞检测"; if (name.Contains("屋顶") || name.Contains("roof") || name.Contains("天花板")) return "屋顶/天花板 - 位于上方,可能产生假阳性碰撞"; // 检查包围盒大小 try { var bbox = item.BoundingBox(); var volume = (bbox.Max.X - bbox.Min.X) * (bbox.Max.Y - bbox.Min.Y) * (bbox.Max.Z - bbox.Min.Z); if (volume > 1000) // 大于1000立方米 return "大型物体 - 包围盒很大,容易与路径相交"; } catch (Exception ex) { LogManager.Debug($"[碰撞分析] 获取物体包围盒失败: {ex.Message}"); } return "未知原因 - 建议手动检查"; } /// /// 显示碰撞分析对话框 /// private void ShowCollisionAnalysisDialog(List hotspots, int totalCollisions) { try { var dialog = new CollisionAnalysisDialog(hotspots, totalCollisions); DialogHelper.SetOwnerSafely(dialog); var result = dialog.ShowDialog(); if (result == null) { // 用户取消 UpdateMainStatus("预计算碰撞分析已取消"); return; } if (dialog.ExcludedObjects.Count > 0) { // 用户选择了排除某些物体并重新生成 LogManager.Info($"[碰撞分析] 用户选择排除 {dialog.ExcludedObjects.Count} 个物体并重新生成"); // 🔥 添加排除对象到UI列表(合并,自动去重) AddExcludedObjectsToUIList(dialog.ExcludedObjects); // 🔥 从UI列表获取所有排除对象(包括之前手工添加的),设置到Manager并重新生成 var allExcludedObjects = _excludedObjects.Select(e => e.ModelItem).Where(m => m != null).ToList(); _pathAnimationManager.SetExcludedObjectsAndClearCache(allExcludedObjects); // 触发重新生成(使用 Dispatcher 避免阻塞 UI) System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { ExecuteGenerateAnimation(); }), DispatcherPriority.Background); UpdateMainStatus($"已排除 {allExcludedObjects.Count} 个物体,正在重新生成..."); } else { // 用户选择直接继续(无排除对象) LogManager.Info("[碰撞分析] 用户选择直接继续生成"); UpdateMainStatus($"预计算碰撞分析完成,共 {totalCollisions} 个候选碰撞"); // 🔥 保存检测记录到数据库(保存测试配置) var recordId = SaveCollisionDetectionRecord(); if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value)) { // 用户选择使用历史记录,跳过后续流程 LogManager.Info("[碰撞分析] 用户选择使用历史记录,跳过动画生成"); return; } } } catch (Exception ex) { LogManager.Error($"[碰撞分析] 显示分析对话框失败: {ex.Message}", ex); } } /// /// 保存检测记录到数据库 /// 在生成动画完成后调用,创建检测记录并保存当时的完整配置 /// private int? SaveCollisionDetectionRecord() { try { var pathDatabase = _pathPlanningManager?.GetPathDatabase(); if (pathDatabase == null) { LogManager.Warning("[检测记录] 数据库未初始化,无法保存检测记录"); return null; } string routeId = CurrentPathRoute?.Id ?? ""; // 🔥 检查是否已有相同配置的检测记录 var existingRecordResult = CheckExistingDetectionRecord(); if (existingRecordResult.HasValue) { var existingId = existingRecordResult.Value.id; var userChoice = existingRecordResult.Value.userChoice; if (userChoice == UserChoice.UseExisting) { // 用户选择使用历史记录 LogManager.Info($"[检测记录] 用户选择使用历史记录 (Id={existingId})"); _pathAnimationManager.CurrentDetectionRecordId = existingId; _pathAnimationManager.IsUsingHistoryRecord = true; // 标记使用历史记录,动画完成后跳过ClashDetective // 在历史列表中选中该记录 SelectHistoryRecordById(existingId); return existingId; } // 用户选择重新生成,继续创建新记录 LogManager.Info($"[检测记录] 用户选择重新生成,创建新记录"); } int recordId = CreateCollisionDetectionRecordSnapshot("动画生成", bindToAnimationManager: true); // 🔥 注册动画配置哈希与检测记录ID的映射(用于后续检查重复配置) try { var animatedObject = UseVirtualObject ? VirtualObjectManager.Instance.CurrentVirtualObject : SelectedAnimatedObject; var route = CurrentPathRoute?.Route; if (animatedObject != null && route != null) { var animationHash = _pathAnimationManager.ComputeAnimationConfigHashFromRoute(animatedObject, route); _pathAnimationManager.RegisterAnimationHashToRecordId(animationHash, recordId); } } catch (Exception ex) { LogManager.Warning($"[检测记录] 注册动画哈希映射失败: {ex.Message}"); } return recordId; } catch (Exception ex) { LogManager.Error($"[检测记录] 保存失败: {ex.Message}"); return null; } } /// /// 创建碰撞检测记录快照。 /// 生成动画和批处理都应该创建自己的配置快照,不依赖旧检测结果。 /// private int CreateCollisionDetectionRecordSnapshot(string scenarioName, bool bindToAnimationManager) { var pathDatabase = _pathPlanningManager?.GetPathDatabase(); if (pathDatabase == null) { throw new InvalidOperationException("数据库未初始化,无法保存检测记录"); } string routeId = CurrentPathRoute?.Id ?? string.Empty; string testName = $"{scenarioName}_{DateTime.Now:MMdd_HHmmssfff}"; double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); var record = new CollisionDetectionRecord { RouteId = routeId, CreatedTime = DateTime.Now, FrameRate = _animationFrameRate, DurationSeconds = AnimationDuration, DetectionTolerance = _detectionTolerance * metersToUnits, IsVirtualObject = UseVirtualObject, AnimatedObjectName = UseVirtualObject ? "虚拟物体" : SelectedAnimatedObject?.DisplayName, DetectAllObjects = !IsManualCollisionTargetEnabled, ObjectRotationCorrection = _objectRotationCorrection.ZDegrees, Description = $"{scenarioName}于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}", TestName = testName, TestTime = DateTime.Now, CollisionCount = 0, AnimationCollisionCount = 0 }; if (UseVirtualObject) { record.VirtualObjectLength = VirtualObjectLengthInMeters * metersToUnits; record.VirtualObjectWidth = VirtualObjectWidthInMeters * metersToUnits; record.VirtualObjectHeight = VirtualObjectHeightInMeters * metersToUnits; } else if (SelectedAnimatedObject != null) { try { var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(SelectedAnimatedObject); record.ObjectModelIndex = pathId.ModelIndex; record.ObjectPathId = pathId.PathId; } catch (Exception ex) { LogManager.Warning($"[检测记录] 获取运动物体PathId失败: {ex.Message}"); } } int recordId = pathDatabase.SaveCollisionDetectionRecord(record); LogManager.Info($"[检测记录] 已创建{scenarioName}记录 (Id={recordId})"); if (bindToAnimationManager) { _pathAnimationManager.CurrentDetectionRecordId = recordId; _pathAnimationManager.IsUsingHistoryRecord = false; } var excludedObjects = _pathAnimationManager?.GetExcludedObjects(); if (excludedObjects != null && excludedObjects.Count > 0) { var excludedRecords = new List(); foreach (var obj in excludedObjects) { if (obj == null) continue; try { var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(obj); excludedRecords.Add(new CollisionDetectionExcludedObjectRecord { DetectionRecordId = recordId, ModelIndex = pathId.ModelIndex, PathId = pathId.PathId, DisplayName = obj.DisplayName, ObjectName = obj.DisplayName, Reason = "预计算碰撞分析排除" }); } catch (Exception ex) { LogManager.Warning($"[检测记录] 准备排除对象记录失败: {obj.DisplayName}, {ex.Message}"); } } if (excludedRecords.Count > 0) { pathDatabase.SaveCollisionDetectionExcludedObjects(recordId, excludedRecords); LogManager.Info($"[检测记录] 已保存 {excludedRecords.Count} 个排除对象"); } } if (IsManualCollisionTargetEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0) { var manualTargetRecords = new List(); foreach (var target in _manualCollisionTargets) { if (target?.ModelItem == null) continue; try { var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(target.ModelItem); manualTargetRecords.Add(new CollisionDetectionManualTargetRecord { DetectionRecordId = recordId, ModelIndex = pathId.ModelIndex, PathId = pathId.PathId, DisplayName = target.ModelItem.DisplayName, ObjectName = target.ModelItem.DisplayName }); } catch (Exception ex) { LogManager.Warning($"[检测记录] 准备手工目标记录失败: {target.DisplayName}, {ex.Message}"); } } if (manualTargetRecords.Count > 0) { pathDatabase.SaveCollisionDetectionManualTargets(recordId, manualTargetRecords); LogManager.Info($"[检测记录] 已保存 {manualTargetRecords.Count} 个手工目标"); } } return recordId; } /// /// 用户选择析析 /// private enum UserChoice { CreateNew, // 创建新记录 UseExisting // 使用历史记录 } /// /// 检查是否已有相同配置的检测记录 /// 策略:1.先检查内存缓存 2.SQL过滤基础参数 3.比较排除列表和手工目标 /// /// (记录ID, 用户选择),不存在或用户取消则返回null private (int id, UserChoice userChoice)? CheckExistingDetectionRecord() { try { var routeId = CurrentPathRoute?.Id; if (string.IsNullOrEmpty(routeId)) { return null; } var pathDatabase = _pathPlanningManager?.GetPathDatabase(); if (pathDatabase == null) { return null; } var animatedObject = UseVirtualObject ? VirtualObjectManager.Instance.CurrentVirtualObject : SelectedAnimatedObject; if (animatedObject == null) { return null; } // 1. 先检查内存缓存(程序运行期间已处理过的) var currentHash = _pathAnimationManager.ComputeAnimationConfigHashFromRoute(animatedObject, CurrentPathRoute.Route); var cachedRecordId = _pathAnimationManager.GetDetectionRecordIdByHash(currentHash); if (cachedRecordId.HasValue) { var cachedRecord = pathDatabase.GetDetectionResultById(cachedRecordId.Value); if (cachedRecord != null) { LogManager.Info($"[CheckExistingDetectionRecord] 内存缓存命中: RecordId={cachedRecordId}"); return ShowDuplicateConfigDialog(cachedRecord); } } // 2. SQL过滤基础参数匹配的记录 // 注意:数据库存储的是模型单位(不带Meters后缀),查询时传模型单位 var factor = UnitsConverter.GetMetersToUnitsConversionFactor(); var matchingRecords = pathDatabase.FindDetectionRecordsByBaseParams( routeId, _animationFrameRate, AnimationDuration, _detectionTolerance * factor, // 米 → 模型单位 UseVirtualObject, UseVirtualObject ? VirtualObjectLengthInMeters * factor : (double?)null, // 米 → 模型单位 UseVirtualObject ? VirtualObjectWidthInMeters * factor : (double?)null, UseVirtualObject ? VirtualObjectHeightInMeters * factor : (double?)null, _objectRotationCorrection.ZDegrees, IsManualCollisionTargetEnabled ); if (matchingRecords == null || matchingRecords.Count == 0) { LogManager.Info("[CheckExistingDetectionRecord] SQL基础参数过滤无匹配"); return null; } LogManager.Info($"[CheckExistingDetectionRecord] SQL找到 {matchingRecords.Count} 条记录,开始比较排除列表"); // 3. 逐个比较排除列表和手工目标 var currentExcluded = GetCurrentExcludedObjects(); var currentTargets = GetCurrentManualTargets(); foreach (var record in matchingRecords) { if (IsExcludedListMatch(record.Id, currentExcluded, pathDatabase) && IsManualTargetListMatch(record.Id, currentTargets, pathDatabase)) { LogManager.Info($"[CheckExistingDetectionRecord] 找到完全匹配: RecordId={record.Id}"); // 注册到内存缓存 _pathAnimationManager.RegisterAnimationHashToRecordId(currentHash, record.Id); return ShowDuplicateConfigDialog(record); } } LogManager.Info("[CheckExistingDetectionRecord] 未找到完全匹配的记录"); return null; } catch (Exception ex) { LogManager.Warning($"[CheckExistingDetectionRecord] 检查失败: {ex.Message}"); return null; } } /// /// 显示重复配置对话框 /// private (int id, UserChoice userChoice)? ShowDuplicateConfigDialog(CollisionDetectionRecord record) { var message = $"检测配置已存在!\n\n" + $"找到一条相同配置的历史检测记录:\n" + $"• 测试名称:{record.TestName ?? "未命名"}\n" + $"• 检测时间:{record.TestTime:yyyy-MM-dd HH:mm:ss}\n" + $"• 碰撞数量:{record.CollisionCount} 个\n\n" + $"您可以:\n" + $"• 【是】直接使用历史记录(跳过重复检测)\n" + $"• 【否】重新生成新记录"; var result = DialogHelper.ShowMessageBox( message, "检测配置已存在", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question); if (result == System.Windows.MessageBoxResult.Yes) { _lastReusedRecordId = record.Id; return (record.Id, UserChoice.UseExisting); } else { _lastReusedRecordId = null; return (record.Id, UserChoice.CreateNew); } } /// /// 获取当前排除对象列表(ModelIndex + PathId) /// private List<(int ModelIndex, string PathId)> GetCurrentExcludedObjects() { var result = new List<(int, string)>(); var excludedObjects = _pathAnimationManager?.GetExcludedObjects(); if (excludedObjects != null) { foreach (var obj in excludedObjects) { if (obj == null) continue; try { var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(obj); result.Add((pathId.ModelIndex, pathId.PathId)); } catch { } } } return result.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList(); } /// /// 获取当前手工碰撞目标列表(ModelIndex + PathId) /// private List<(int ModelIndex, string PathId)> GetCurrentManualTargets() { var result = new List<(int, string)>(); if (IsManualCollisionTargetEnabled && _manualCollisionTargets != null) { foreach (var target in _manualCollisionTargets) { if (target?.ModelItem == null) continue; try { var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(target.ModelItem); result.Add((pathId.ModelIndex, pathId.PathId)); } catch { } } } return result.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList(); } /// /// 比较排除列表是否匹配 /// private bool IsExcludedListMatch(int recordId, List<(int ModelIndex, string PathId)> currentList, PathDatabase db) { var dbList = db.GetDetectionExcludedObjects(recordId) .Select(e => (e.ModelIndex, e.PathId)) .OrderBy(x => x.ModelIndex) .ThenBy(x => x.PathId) .ToList(); return dbList.SequenceEqual(currentList); } /// /// 比较手工目标列表是否匹配 /// private bool IsManualTargetListMatch(int recordId, List<(int ModelIndex, string PathId)> currentList, PathDatabase db) { var dbList = db.GetDetectionManualTargets(recordId) .Select(t => (t.ModelIndex, t.PathId)) .OrderBy(x => x.ModelIndex) .ThenBy(x => x.PathId) .ToList(); return dbList.SequenceEqual(currentList); } private void ExecuteToggleWireframeMode() { try { var doc = Autodesk.Navisworks.Api.Application.ActiveDocument; if (doc == null || doc.IsClear) { LogManager.Warning("[线框模式] 没有活动的文档"); UpdateMainStatus("无法切换线框模式:没有活动文档"); return; } // 创建当前视角的副本 var newViewpoint = doc.CurrentViewpoint.Value.CreateCopy(); // 判断当前是否已经是线框模式 bool isWireframe = (newViewpoint.RenderStyle == ViewpointRenderStyle.Wireframe); if (!isWireframe) { // 切换到线框模式 newViewpoint.RenderStyle = ViewpointRenderStyle.Wireframe; LogManager.Info("[线框模式] 视图模式已切换为Wireframe"); UpdateMainStatus("已切换到线框模式"); } else { // 切换回着色模式 newViewpoint.RenderStyle = ViewpointRenderStyle.Shaded; LogManager.Info("[线框模式] 视图模式已切换回Shaded"); UpdateMainStatus("已切换回着色模式"); } // 应用修改后的视角 doc.CurrentViewpoint.CopyFrom(newViewpoint); } catch (Exception ex) { LogManager.Error($"切换线框模式失败: {ex.Message}", ex); UpdateMainStatus("切换线框模式失败"); } } private void ExecuteHighlightClashDetectiveResults() { if (string.IsNullOrEmpty(_clashIntegration?.CurrentTestName)) { LogManager.Warning("[ClashDetective高亮] 当前测试名称为空,无法高亮结果"); return; } ModelHighlightHelper.HighlightClashDetectiveResults(_clashIntegration.CurrentTestName, _clashIntegration.GetCurrentPathClashResults); UpdateMainStatus("已高亮ClashDetective检测结果"); } private void ExecuteClearClashDetectiveHighlights() { ModelHighlightHelper.ClearClashDetectiveHighlights(); UpdateMainStatus("已清除ClashDetective高亮"); } /// /// 刷新ClashDetective结果列表 /// private void RefreshClashDetectiveResultsList() { try { var pathName = CurrentPathRoute?.Name; LogManager.Debug($"[刷新碰撞历史] CurrentPathRoute={CurrentPathRoute?.Id}, Name={pathName}"); if (string.IsNullOrEmpty(pathName)) { LogManager.Warning("[刷新碰撞历史] 路径名称为空,清空列表"); _clashDetectiveResults.Clear(); HasClashDetectiveResults = false; return; } var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase(); if (pathDatabase == null) { _clashDetectiveResults.Clear(); HasClashDetectiveResults = false; return; } var records = pathDatabase.GetDetectionResultsByPath(pathName); LogManager.Info($"[刷新碰撞历史] 查询到 {records.Count} 条记录,路径={pathName}"); _clashDetectiveResults.Clear(); foreach (var record in records) { var resultViewModel = new ClashDetectiveResultViewModel(record, RefreshClashDetectiveResultsList); // 加载运动物体(Item1) // 虚拟物体:只记录尺寸(数据库中是模型单位),等用户点击时再创建 // 真实物体:尝试解析PathId获取引用 if (record.IsVirtualObject) { // 虚拟物体必须有尺寸 if (!record.VirtualObjectLength.HasValue || !record.VirtualObjectWidth.HasValue || !record.VirtualObjectHeight.HasValue) { LogManager.Error($"[碰撞历史] 虚拟物体记录缺少尺寸: TestName={record.TestName}"); } resultViewModel.VirtualObjectLengthInMeters = record.VirtualObjectLength.Value; resultViewModel.VirtualObjectWidthInMeters = record.VirtualObjectWidth.Value; resultViewModel.VirtualObjectHeightInMeters = record.VirtualObjectHeight.Value; } else if (record.ObjectModelIndex.HasValue && !string.IsNullOrEmpty(record.ObjectPathId)) { try { var animatedObjectPathId = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId { ModelIndex = record.ObjectModelIndex.Value, PathId = record.ObjectPathId }; resultViewModel.AnimatedObject = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.ResolvePathId(animatedObjectPathId); LogManager.Info($"[碰撞历史] 已加载运动物体: {resultViewModel.AnimatedObject?.DisplayName ?? "未找到"} for TestName={record.TestName}"); } catch (Exception ex) { LogManager.Warning($"[碰撞历史] 无法加载运动物体: ModelIndex={record.ObjectModelIndex}, PathId={record.ObjectPathId}, 错误: {ex.Message}"); } } // 同时加载该结果的碰撞构件清单 var collisionObjectRecords = pathDatabase.GetClashDetectiveCollisionObjects(record.Id); // record.Id 就是 DetectionRecordId if (collisionObjectRecords != null) { int index = 1; foreach (var objRecord in collisionObjectRecords) { CollisionObjectViewModel objViewModel = null; ModelItem modelItem = null; string modelPath = "路径获取失败"; // 通过 ModelIndex 和 PathId 重建 ModelItem if (objRecord.ModelIndex.HasValue && !string.IsNullOrEmpty(objRecord.PathId)) { try { var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId { ModelIndex = objRecord.ModelIndex.Value, PathId = objRecord.PathId }; modelItem = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.ResolvePathId(pathIdObj); // 获取完整的节点树路径 if (modelItem != null) { modelPath = BuildModelPath(modelItem); } } catch (Exception ex) { LogManager.Warning($"[碰撞构件] 无法通过 PathId 找到 ModelItem: ModelIndex={objRecord.ModelIndex}, PathId={objRecord.PathId}, 错误: {ex.Message}"); } } objViewModel = new CollisionObjectViewModel { Index = index++, DisplayName = objRecord.DisplayName, ObjectName = objRecord.ObjectName, ModelPath = modelPath, ResultId = objRecord.DetectionRecordId, TestName = record.TestName, ModelIndex = objRecord.ModelIndex, PathId = objRecord.PathId, ModelItem = modelItem, HighlightCommand = new RelayCommand(() => resultViewModel.ExecuteHighlightCollisionObject(objViewModel)), // 填充碰撞位置信息 AnimatedObjectTrackedPosition = objRecord.HasPositionInfo && objRecord.AnimatedObjectTrackedPosX.HasValue ? new Point3D(objRecord.AnimatedObjectTrackedPosX.Value, objRecord.AnimatedObjectTrackedPosY.Value, objRecord.AnimatedObjectTrackedPosZ.Value) : null, AnimatedObjectTrackedYawRadians = objRecord.AnimatedObjectTrackedYawRadians ?? 0, AnimatedObjectTrackedRotation = objRecord.AnimatedObjectHasTrackedRotation && objRecord.AnimatedObjectTrackedRotA.HasValue && objRecord.AnimatedObjectTrackedRotB.HasValue && objRecord.AnimatedObjectTrackedRotC.HasValue && objRecord.AnimatedObjectTrackedRotD.HasValue ? new Rotation3D( objRecord.AnimatedObjectTrackedRotA.Value, objRecord.AnimatedObjectTrackedRotB.Value, objRecord.AnimatedObjectTrackedRotC.Value, objRecord.AnimatedObjectTrackedRotD.Value) : Rotation3D.Identity, AnimatedObjectHasTrackedRotation = objRecord.AnimatedObjectHasTrackedRotation, HasPositionInfo = objRecord.HasPositionInfo }; resultViewModel.CollisionObjects.Add(objViewModel); } } _clashDetectiveResults.Add(resultViewModel); } // 更新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(); } /// /// 在历史列表中选中指定ID的记录,并打开碰撞报告 /// private void SelectHistoryRecordById(int recordId) { try { // 先刷新列表确保记录存在 RefreshClashDetectiveResultsList(); // 查找并选中记录 var targetRecord = _clashDetectiveResults.FirstOrDefault(r => r.Record?.Id == recordId); if (targetRecord != null) { // 先设置选中项,确保UI更新 SelectedClashDetectiveResult = targetRecord; LogManager.Info($"[SelectHistoryRecordById] 已在历史列表中选中记录 (Id={recordId})"); // 更新状态栏显示 if (targetRecord.Record?.CollisionCount > 0) { UpdateMainStatus($"已加载历史记录:{targetRecord.Record.TestName},{targetRecord.Record.CollisionCount} 个碰撞"); } else { UpdateMainStatus($"已加载历史记录:{targetRecord.Record.TestName},无碰撞"); } // 🔥 打开该历史记录的碰撞报告,让用户明确知道使用了哪条记录 if (!string.IsNullOrEmpty(targetRecord.Record?.TestName)) { LogManager.Info($"[SelectHistoryRecordById] 正在打开历史记录报告: {targetRecord.Record.TestName}"); OpenHistoryReportAsync(recordId); } } else { LogManager.Warning($"[SelectHistoryRecordById] 未在列表中找到记录 (Id={recordId})"); } } catch (Exception ex) { LogManager.Error($"[SelectHistoryRecordById] 选中记录失败: {ex.Message}"); } } /// /// 检查是否是复用的历史记录 /// private bool IsReusedHistoryRecord(int recordId) { return _lastReusedRecordId.HasValue && _lastReusedRecordId.Value == recordId; } /// /// 异步打开历史碰撞报告 /// private async void OpenHistoryReportAsync(int recordId) { try { // 使用检测记录ID查询生成报告,更可靠 var command = GenerateCollisionReportCommand.CreateComprehensive(hasHighlighted: true); command.SetDetectionRecordId(recordId); var result = await command.ExecuteAsync(); if (result.IsSuccess && result is PathPlanningResult reportResult) { NavisworksTransport.UI.WPF.Views.CollisionReportDialog.ShowReport(reportResult.Data); } else { LogManager.Warning($"[OpenHistoryReportAsync] 生成报告失败: {result.ErrorMessage}"); } } catch (Exception ex) { LogManager.Error($"[OpenHistoryReportAsync] 打开历史报告失败: {ex.Message}"); } } #endregion /// /// 异步预计算碰撞排除列表 /// /// 动画对象 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) { // 先更新序号 UpdateManualCollisionTargetIndices(); UpdateManualCollisionTargetSummary(); OnPropertyChanged(nameof(HasManualCollisionTargets)); OnPropertyChanged(nameof(IsManualTargetModeActive)); System.Windows.Input.CommandManager.InvalidateRequerySuggested(); RefreshManualTargetHighlights(); SyncManualCollisionTargetsToAnimationManager(); UpdateCanGenerateAnimation(); // 更新生成动画按钮状态 } 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 void UpdateManualCollisionTargetIndices() { if (_manualCollisionTargets == null) return; for (int i = 0; i < _manualCollisionTargets.Count; i++) { _manualCollisionTargets[i].Index = i + 1; // 序号从1开始 } } private string BuildModelPath(ModelItem item) { try { var segments = new List(); 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 "路径获取失败"; } } /// /// 获取运动物体的 PathId 信息 /// private (int ModelIndex, string PathId) GetObjectPathIdInfo() { try { if (SelectedAnimatedObject == null) { return (0, null); } var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(SelectedAnimatedObject); return (pathId.ModelIndex, pathId.PathId); } catch (Exception ex) { LogManager.Warning($"[批处理] 无法获取运动对象的 PathId: {SelectedAnimatedObject.DisplayName}, 错误: {ex.Message}"); return (0, null); } } private List GetValidManualCollisionTargets(bool pruneInvalidEntries = false) { var validItems = new List(); if (_manualCollisionTargets == null) { return validItems; } var invalidItems = new List(); 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; } foreach (var target in _manualCollisionTargets) { if (target == null) continue; // 使用正确的 ModelItem 比较方法 if (ModelItemAnalysisHelper.ModelItemEquals(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); UpdateCanGenerateAnimation(); // 更新生成动画按钮状态 if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets) { UpdateMainStatus("手工模式已启用,请先选择碰撞检测对象"); } else if (!IsManualCollisionTargetEnabled) { UpdateMainStatus("已切换回默认碰撞检测对象范围"); } } #endregion #region 动画辅助方法 /// /// 执行生成动画命令(同步执行,因为Navisworks API需要STA线程) /// private async void ExecuteGenerateAnimation() { // 保存当前光标 var oldCursor = Mouse.OverrideCursor; try { if (!CanGenerateAnimation) { UpdateMainStatus("无法生成动画:缺少必要条件"); LogManager.Warning("尝试生成动画但条件不满足"); return; } // 设置等待光标 Mouse.OverrideCursor = Cursors.Wait; UpdateMainStatus("正在生成动画...", -1, true); LogManager.Info($"开始生成动画 - 模式: {(UseVirtualObject ? "虚拟物体" : "选择物体")}"); var cacheStartTime = DateTime.Now; var manualModeEnabled = IsManualCollisionTargetEnabled; List manualTargets = null; if (manualModeEnabled) { manualTargets = GetValidManualCollisionTargets(pruneInvalidEntries: true); if (manualTargets.Count == 0) { UpdateMainStatus("手工模式已启用,但没有可用的碰撞对象"); LogManager.Warning("[动画生成] 手工模式启用但没有有效对象"); return; } } // 🔥 预计算动画对象的排除列表(仅在非虚拟物体、非手工模式下) if (!manualModeEnabled && !UseVirtualObject) { UpdateMainStatus("正在分析动画对象...", -1, true); // 设置检测容差(安全间隙通过CreateAnimation传入) _pathAnimationManager.SetDetectionTolerance(_detectionTolerance); var success = _pathAnimationManager.PrecomputeCollisionExclusions(SelectedAnimatedObject); if (!success) { var msg = "[动画生成] 动画对象分析失败,无法继续生成动画"; LogManager.Error(msg); UpdateMainStatus("生成失败:对象分析错误"); return; } } else if (manualModeEnabled) { UpdateMainStatus("使用手工碰撞对象,跳过预计算", -1, true); LogManager.Info("[动画生成] 手工碰撞对象模式,已跳过对象预计算"); } UpdateMainStatus("正在生成路径动画...", -1, true); // 设置动画参数到PathAnimationManager _pathAnimationManager.SetAnimationFrameRate(_animationFrameRate); _pathAnimationManager.SetCollisionDetectionAccuracy(CollisionDetectionAccuracy); _pathAnimationManager.SetMovementSpeed(MovementSpeed); _pathAnimationManager.SetDetectionTolerance(_detectionTolerance); // 将PathRouteViewModel的点转换为Point3D列表 var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList(); ModelItem animatedObject = null; if (UseVirtualObject) { // 使用虚拟物体:总是调用 ShowVirtualObject 来更新尺寸 LogManager.Info("[ExecuteGenerateAnimation] 显示并更新虚拟物体..."); UpdateMainStatus("正在显示虚拟物体...", -1, true); VirtualObjectManager.Instance.ShowVirtualObject( VirtualObjectLengthInMeters, VirtualObjectWidthInMeters, VirtualObjectHeightInMeters ); animatedObject = VirtualObjectManager.Instance.CurrentVirtualObject; if (animatedObject == null) { LogManager.Error("[动画生成] 虚拟物体显示失败"); UpdateMainStatus("生成失败:无法显示虚拟物体"); return; } // 注意:虚拟物体在选择时已经通过 MoveAnimatedObjectToPathStart 移动到路径起点 // 这里不需要重复移动,因为 CreateAnimation 内部还会再次调用 MoveObjectToPathStart LogManager.Info($"使用虚拟物体: {animatedObject.DisplayName}"); } else { animatedObject = SelectedAnimatedObject; } // 统一准备碰撞检测(根据模式自动决定是否构建全局缓存) ClashDetectiveIntegration.PrepareCollisionDetection(animatedObject, manualModeEnabled, manualTargets); // 设置碰撞检测目标 if (manualModeEnabled) { _pathAnimationManager.SetManualCollisionTargets(manualTargets, true); } else { _pathAnimationManager.SetManualCollisionTargets(null, false); } // 【统一使用CreateAnimation,完全复用现有逻辑】 LogManager.Info($"[ExecuteGenerateAnimation] 准备创建动画: 路径名称='{CurrentPathRoute.Name}', ID='{CurrentPathRoute.Id}', " + $"动画对象='{animatedObject.DisplayName}'"); // 直接使用 CurrentPathRoute.Route var pathRoute = CurrentPathRoute.Route; if (pathRoute == null) { LogManager.Error($"无法找到路径对象,ID: {CurrentPathRoute.Id}"); UpdateMainStatus("生成失败:找不到路径数据"); return; } // 先设置路径到动画管理器 _pathAnimationManager.SetRoute(pathRoute); // 准备物体尺寸参数(从米转换为模型单位) var metersToModelUnits = Utils.UnitsConverter.GetMetersToUnitsConversionFactor(); double vLength = UseVirtualObject ? VirtualObjectLengthInMeters * metersToModelUnits : 0; double vWidth = UseVirtualObject ? VirtualObjectWidthInMeters * metersToModelUnits : 0; double vHeight = UseVirtualObject ? VirtualObjectHeightInMeters * metersToModelUnits : 0; double safetyMarginModelUnits = _safetyMarginInMeters * metersToModelUnits; LogManager.Info($"[ExecuteGenerateAnimation] 准备调用CreateAnimation: UseVirtualObject={UseVirtualObject}, 物体尺寸: {vLength:F2}×{vWidth:F2}×{vHeight:F2}模型单位, 安全间隙: {safetyMarginModelUnits:F4}模型单位 (_safetyMarginInMeters={_safetyMarginInMeters:F4}米)"); _pathAnimationManager.CreateAnimation(animatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id, UseVirtualObject, vLength, vWidth, vHeight, safetyMarginModelUnits); var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds; LogManager.Info($"[动画生成] 动画生成完成,总耗时: {totalElapsed:F1}ms"); LogManager.Info($"动画生成成功: 物体={animatedObject.DisplayName}, 路径={CurrentPathRoute.Name}"); if (UseVirtualObject) { LogManager.Info($"虚拟物体尺寸: {VirtualObjectLengthInMeters:F1}×{VirtualObjectWidthInMeters:F1}×{VirtualObjectHeightInMeters:F1}m"); } if (IsManualCollisionTargetEnabled) { RefreshManualTargetHighlights(forceHighlight: true); } // 动画生成完成,先恢复光标再显示分析对话框 Mouse.OverrideCursor = oldCursor; oldCursor = null; // 标记已恢复,避免 finally 再次恢复 // 🔥 自动生成动画后,自动高亮预计算碰撞结果并显示分析对话框 await AnalyzeAndHighlightPrecomputedCollisionsAsync(); // 更新状态 UpdateAnimationButtonStates(); UpdateMainStatus("动画生成成功"); } catch (Exception ex) { UpdateMainStatus("动画生成失败"); LogManager.Error($"生成动画时发生错误: {ex.Message}"); LogManager.Error($"动画生成失败,详细错误信息:{ex}"); } finally { // 恢复光标(如果还没有恢复) if (oldCursor != null) { Mouse.OverrideCursor = oldCursor; } } } /// /// 更新移动物体信息 /// private void UpdateAnimatedObjectInfo() { if (SelectedAnimatedObject != null) { SelectedAnimatedObjectName = SelectedAnimatedObject.DisplayName ?? "未命名物体"; HasSelectedAnimatedObject = true; } else { SelectedAnimatedObjectName = "未选择移动物体"; HasSelectedAnimatedObject = false; } } /// /// 计算本地三轴预旋转后的有效尺寸 /// 返回值为模型单位(与Navisworks API一致) /// /// (沿路径, 垂直路径, 法线) - 模型单位 private (double effectiveAlongPath, double effectiveAcrossPath, double effectiveNormalToPath) CalculateRotatedDimensions() { double forwardSize; double sideSize; double upSize; ModelAxisConvention axisConvention; if (UseVirtualObject) { double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); forwardSize = VirtualObjectLengthInMeters * metersToUnits; sideSize = VirtualObjectWidthInMeters * metersToUnits; upSize = VirtualObjectHeightInMeters * metersToUnits; axisConvention = ModelAxisConvention.CreateVirtualObjectAssetConvention(); } else if (SelectedAnimatedObject != null) { double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); forwardSize = _objectOriginalLength * metersToUnits; sideSize = _objectOriginalWidth * metersToUnits; upSize = _objectOriginalHeight * metersToUnits; var adapter = CoordinateSystemManager.Instance.CreateHostAdapter(); if ((CurrentPathRoute?.PathType == NavisworksTransport.PathType.Ground || CurrentPathRoute?.PathType == NavisworksTransport.PathType.Hoisting) && _pathAnimationManager != null && _pathAnimationManager.TryGetCurrentRouteRealObjectPlanarProjectedExtents( out double resolvedPlanarForward, out double resolvedPlanarSide, out double resolvedPlanarUp)) { double unitsToMetersForPlanar = UnitsConverter.GetUnitsToMetersConversionFactor(); LogManager.Debug( $"[角度修正] 平面真实物体复用最终姿态尺寸: Forward={resolvedPlanarForward * unitsToMetersForPlanar:F2}m, " + $"Side={resolvedPlanarSide * unitsToMetersForPlanar:F2}m, Up={resolvedPlanarUp * unitsToMetersForPlanar:F2}m, 角度={_objectRotationCorrection}"); return (resolvedPlanarForward, resolvedPlanarSide, resolvedPlanarUp); } if (CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail && _pathAnimationManager != null && _pathAnimationManager.TryGetCurrentRouteRealObjectRailProjectedExtents( out double resolvedForward, out double resolvedSide, out double resolvedUp)) { double unitsToMetersForRail = UnitsConverter.GetUnitsToMetersConversionFactor(); LogManager.Debug( $"[角度修正] Rail真实物体复用路径姿态尺寸: Forward={resolvedForward * unitsToMetersForRail:F2}m, " + $"Side={resolvedSide * unitsToMetersForRail:F2}m, Up={resolvedUp * unitsToMetersForRail:F2}m, 角度={_objectRotationCorrection}"); return (resolvedForward, resolvedSide, resolvedUp); } axisConvention = CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail ? ModelAxisConvention.CreateRailAssetConvention() : ModelAxisConvention.CreateDefaultForHost(adapter.HostType); } else { return (0, 0, 0); } var hostAdapter = CoordinateSystemManager.Instance.CreateHostAdapter(); Quaternion correctionQuaternion = UseVirtualObject ? hostAdapter.CreateCanonicalRotationCorrection(_objectRotationCorrection) : hostAdapter.CreateHostRotationCorrection(_objectRotationCorrection); var result = RotatedObjectExtentHelper.CalculateProjectedSemanticExtents( axisConvention, forwardSize, sideSize, upSize, correctionQuaternion); double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(); LogManager.Debug( $"[角度修正] 旋转后尺寸: 原始({forwardSize * unitsToMeters:F2}m×{sideSize * unitsToMeters:F2}m×{upSize * unitsToMeters:F2}m) -> " + $"有效({result.forwardExtent * unitsToMeters:F2}m×{result.sideExtent * unitsToMeters:F2}m×{result.upExtent * unitsToMeters:F2}m), 角度={_objectRotationCorrection}"); return result; } /// /// 根据路径类型更新通行空间可视化 /// 空轨和吊装路径默认打开通行空间(物体通行空间模式),地面路径不打开 /// 根据选择的物体或虚拟物体尺寸更新通行空间渲染 /// public void UpdatePassageSpaceVisualization() { try { if (CurrentPathRoute == null) { LogManager.Debug("[通行空间可视化] 没有选择路径,跳过更新"); return; } // 计算旋转后的有效尺寸(考虑角度修正) (double effectiveLength, double effectiveWidth, double effectiveHeight) = CalculateRotatedDimensions(); var renderPlugin = PathPointRenderPlugin.Instance; if (renderPlugin == null) { throw new InvalidOperationException("[通行空间可视化] 无法获取PathPointRenderPlugin实例"); } // 计算通行空间尺寸(模型单位) double passageAcrossPath, passageNormalToPath, passageAlongPath; double passageNormalToPathVertical, passageNormalToPathHorizontal; // 将安全间隙从米转换为模型单位 var metersToUnitsPassage = UnitsConverter.GetMetersToUnitsConversionFactor(); double safetyMargin = _safetyMarginInMeters * metersToUnitsPassage; if (UseVirtualObject) { // 虚拟物体的xyz定义:X方向 = 长度(前进方向),Y方向 = 宽度(侧面),Z方向 = 高度 if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Rail || CurrentPathRoute.PathType == NavisworksTransport.PathType.Hoisting) { // 空中路径(空轨或吊装):高度上下都加间隙 passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向) passageNormalToPath = effectiveHeight + 2 * safetyMargin; // 法线方向高度 + 2*间隙 passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向) // 吊装路径分段参数 passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙 passageNormalToPathHorizontal = effectiveHeight + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙 LogManager.Debug($"[通行空间可视化] 空中路径使用虚拟物体尺寸: 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m"); } else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground) { // 地面路径:物体的长度方向(X轴,前进方向)朝向路径方向 // 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面) passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向) passageNormalToPath = effectiveHeight + safetyMargin; // 法线方向高度 + 间隙(法线方向,只有上方) passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向) // 地面路径不需要分段参数 passageNormalToPathVertical = passageNormalToPath; passageNormalToPathHorizontal = passageNormalToPath; LogManager.Debug($"[通行空间可视化] 地面路径使用虚拟物体尺寸: 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m"); } else // Rail { // 空轨路径:物体的长度方向(X轴,前进方向)朝向路径方向 // 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度) // 法线方向只在远离轨道的一侧留间隙: // - 轨下安装:顶面贴路径,底面留间隙 // - 轨上安装:底面贴路径,顶面留间隙 passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向) passageNormalToPath = effectiveHeight + safetyMargin; // 法线方向单侧留间隙 passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向) // 空轨路径不需要分段参数 passageNormalToPathVertical = passageNormalToPath; passageNormalToPathHorizontal = passageNormalToPath; LogManager.Debug($"[通行空间可视化] 空轨路径使用虚拟物体尺寸: 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m"); } } else if (SelectedAnimatedObject != null) { // 根据路径类型确定通行空间的尺寸 if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Hoisting) { // 吊装路径: // 高度上下都加间隙 passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向) passageNormalToPath = effectiveHeight + 2 * safetyMargin; // 局部up方向高度 + 2*间隙(法线方向) passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向) // 吊装路径分段参数 passageNormalToPathVertical = effectiveLength + 2 * safetyMargin; // 垂直段:物体长度 + 2*间隙 passageNormalToPathHorizontal = effectiveHeight + 2 * safetyMargin; // 水平段:物体高度 + 2*间隙 LogManager.Debug($"[通行空间可视化] 空中路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 有效高度={effectiveHeight / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m"); } else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Ground) { // 地面路径(可能有坡度): // 物体的长度方向(X轴,前进方向)朝向路径方向 // 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度),高度上方加间隙(下方不需要,因为有地面) passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向) passageNormalToPath = effectiveHeight + safetyMargin; // 局部up方向高度 + 间隙(法线方向,只有上方) passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向) // 地面路径不需要分段参数 passageNormalToPathVertical = passageNormalToPath; passageNormalToPathHorizontal = passageNormalToPath; LogManager.Debug($"[通行空间可视化] 地面路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 有效高度={effectiveHeight / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m"); } else if (CurrentPathRoute.PathType == NavisworksTransport.PathType.Rail) { // 空轨路径(可能有坡度): // 物体的长度方向(X轴,前进方向)朝向路径方向 // 通行空间沿路径方向 = X方向尺寸(长度),垂直于路径方向 = Y方向尺寸(宽度) // 法线方向只在远离轨道的一侧留间隙: // - 轨下安装:顶面贴路径,底面留间隙 // - 轨上安装:底面贴路径,顶面留间隙 passageAcrossPath = effectiveWidth + 2 * safetyMargin; // 旋转后宽度 + 2*间隙(垂直于路径方向) passageNormalToPath = effectiveHeight + safetyMargin; // 局部up方向高度 + 单侧间隙(法线方向) passageAlongPath = effectiveLength; // 旋转后长度(沿路径方向) passageNormalToPathVertical = passageNormalToPath; passageNormalToPathHorizontal = passageNormalToPath; LogManager.Debug($"[通行空间可视化] 空轨路径使用物体尺寸 ({SelectedAnimatedObject.DisplayName}): 有效长度={effectiveLength / metersToUnitsPassage:F2}m, 有效宽度={effectiveWidth / metersToUnitsPassage:F2}m, 有效高度={effectiveHeight / metersToUnitsPassage:F2}m, 通行空间沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m"); } else { passageAcrossPath = effectiveWidth + 2 * safetyMargin; passageNormalToPath = effectiveHeight + safetyMargin; passageAlongPath = effectiveLength; passageNormalToPathVertical = passageNormalToPath; passageNormalToPathHorizontal = passageNormalToPath; } } else { // 默认就是使用虚拟物体,如果既不使用虚拟物体也没选择物体,说明配置错误 throw new InvalidOperationException("[通行空间可视化] 配置错误:既不使用虚拟物体也没有选择物体"); } // 设置通行空间参数到渲染插件(传递模型单位) renderPlugin.SetPassageSpaceParameters( passageAcrossPath, passageNormalToPath, passageAlongPath, passageNormalToPathVertical, passageNormalToPathHorizontal); LogManager.Debug($"[通行空间可视化] 已设置通行空间参数: 沿路径={passageAlongPath / metersToUnitsPassage:F2}m, 垂直路径={passageAcrossPath / metersToUnitsPassage:F2}m, 法线={passageNormalToPath / metersToUnitsPassage:F2}m, 安全间隙={_safetyMarginInMeters:F2}m"); // 根据路径类型决定是否切换到物体通行空间模式 bool enableObjectSpace = false; switch (CurrentPathRoute.PathType) { case NavisworksTransport.PathType.Rail: case NavisworksTransport.PathType.Hoisting: enableObjectSpace = true; LogManager.Debug($"[通行空间可视化] 路径类型={CurrentPathRoute.PathType}(空中路径),切换到物体通行空间模式"); break; case NavisworksTransport.PathType.Ground: enableObjectSpace = false; LogManager.Debug($"[通行空间可视化] 路径类型={CurrentPathRoute.PathType},不切换到物体通行空间模式"); break; default: LogManager.Warning($"[通行空间可视化] 未知的路径类型: {CurrentPathRoute.PathType}"); return; } // 切换到物体通行空间模式(如果需要) if (enableObjectSpace) { renderPlugin.VisualizationMode = PathVisualizationMode.ObjectSpace; LogManager.Debug("[通行空间可视化] 已切换到物体通行空间模式"); } // 刷新路径可视化以应用新的尺寸 var coreRoute = CurrentPathRoute.Route; if (coreRoute != null) { PathPlanningManager.Instance.DrawRouteVisualization(coreRoute, isAutoPath: false); LogManager.Debug("[通行空间可视化] 已刷新路径可视化"); } } catch (Exception ex) { LogManager.Error($"[通行空间可视化] 更新失败: {ex.Message}", ex); } } /// /// 递归检查ModelItem是否包含几何体 /// 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; } /// /// 更新是否可以生成动画的状态 /// private void UpdateCanGenerateAnimation() { var hasPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2; bool hasValidObject; if (UseVirtualObject) { // 使用虚拟物体时,只需要有效的物体尺寸 hasValidObject = VirtualObjectLengthInMeters > 0 && VirtualObjectWidthInMeters > 0 && VirtualObjectHeightInMeters > 0; } else { // 使用选择物体时,需要已选择物体 hasValidObject = SelectedAnimatedObject != null; } // 启用手工指定模式时,必须指定碰撞检测对象 bool hasManualTargetsIfRequired = !IsManualCollisionTargetEnabled || HasManualCollisionTargets; CanGenerateAnimation = hasValidObject && hasPath && hasManualTargetsIfRequired; // 更新状态提示 if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets) { UpdateMainStatus("手工模式已启用,请先选择碰撞检测对象"); } else if (!hasPath && !hasValidObject) { UpdateMainStatus(UseVirtualObject ? "请选择动画路径" : "请选择移动物体和动画路径"); } else if (!hasValidObject) { UpdateMainStatus(UseVirtualObject ? "虚拟物体参数无效" : "请选择移动物体"); } else if (!hasPath) { UpdateMainStatus("请选择有效的动画路径"); } else { UpdateMainStatus("可以生成动画"); } // 同时更新动画按钮状态,因为对象或路径的变化会影响"开始动画"按钮的可用性 UpdateAnimationButtonStates(); } /// /// 计算当前路径的总长度 /// 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; } /// /// 统一更新动画按钮状态逻辑 /// 根据当前的路径、动画对象和动画管理器状态来决定按钮的可用性 /// 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); } } /// /// 生成碰撞检测报告数据 /// 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() .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 }; } } /// /// 显示碰撞报告 /// private async Task ShowCollisionReport(CollisionReportData reportData) { try { // 更新UI摘要信息 await _uiStateManager.ExecuteUIUpdateAsync(() => { if (reportData.IsValid) { // 碰撞报告已生成,显示 Clash Detective 权威结果 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 文档管理 /// /// 获取单例实例 /// private static AnimationControlViewModel _instance; public static AnimationControlViewModel Instance { get { return _instance; } set { _instance = value; } } /// /// 文档失效时的处理 /// private void OnDocumentInvalidated(object sender, EventArgs e) { try { LogManager.Info("[AnimationControlViewModel] 文档失效,重置状态"); Reset(); } catch (Exception ex) { LogManager.Error($"[AnimationControlViewModel] 处理文档失效失败: {ex.Message}"); } } /// /// 文档就绪时的处理 /// private void OnDocumentReady(object sender, EventArgs e) { try { LogManager.Info("[AnimationControlViewModel] 文档就绪,启用功能"); // 重新启用UI CanStartAnimation = true; CanGenerateAnimation = true; // 🔥 文档就绪时刷新碰撞检测历史列表 RefreshClashDetectiveResultsList(); UpdateMainStatus("文档已加载,就绪"); } catch (Exception ex) { LogManager.Error($"[AnimationControlViewModel] 处理文档就绪失败: {ex.Message}"); } } /// /// 重置视图模型状态 /// public void Reset() { try { LogManager.Info("[AnimationControlViewModel] 重置状态"); // 停止动画 if (_pathAnimationManager != null && _pathAnimationManager.IsAnimating) { _pathAnimationManager.CancelAnimation(); } // 重置角度修正值为0 ObjectRotationCorrection = LocalEulerRotationCorrection.Zero; LogManager.Debug("[重置状态] 已重置角度修正值为0"); // 清空选中对象 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; /// /// 清理资源 /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// 清理资源 /// protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { try { LogManager.Info("开始清理AnimationControlViewModel资源"); // 1. 取消事件订阅(在停止动画之前,避免事件处理中访问已释放的对象) // 取消配置变更事件订阅 UnsubscribeFromConfigChanges(); // 取消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; } } /// /// 递归统计测试中的碰撞结果总数(包括分组内的结果)- 用于报告 /// 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; } /// /// 递归统计分组内的碰撞结果数量 - 用于报告 /// 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; } /// /// 递归获取测试中的所有碰撞结果(包括分组内的)- 用于报告 /// private List GetAllClashResultsFromTest(ClashTest test) { var results = new List(); 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; } /// /// 递归获取分组内的所有碰撞结果 - 用于报告 /// private List GetAllClashResultsFromGroup(ClashResultGroup group) { var results = new List(); 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; } /// /// 添加到批处理队列 /// private async void ExecuteAddToBatchQueue() { try { if (!CanGenerateAnimation) { UpdateMainStatus("无法添加到批处理:缺少必要条件"); LogManager.Warning("尝试添加到批处理但条件不满足"); System.Windows.MessageBox.Show( "无法添加到批处理:缺少必要条件(路径未选择或路径点不足)", "添加失败", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning); return; } // 🔥 检查排除列表是否已设置 var excludedObjects = _pathAnimationManager?.GetExcludedObjects(); if (excludedObjects == null || excludedObjects.Count == 0) { LogManager.Warning("[批处理] 排除列表为空,提示用户"); var result = System.Windows.MessageBox.Show( "排除列表为空。对于复杂路径,建议先运行\"生成动画\"进行预计算碰撞分析,设置排除列表后再添加到批处理。\n\n" + "• 是:继续添加(不推荐,可能检测时间过长)\n" + "• 否:取消添加,先去设置排除列表\n\n" + "提示:排除列表可以显著减少复杂路径的碰撞检测时间。", "添加到批处理确认", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Warning ); if (result == System.Windows.MessageBoxResult.No) { LogManager.Info("[批处理] 用户选择取消添加,去设置排除列表"); return; } LogManager.Info("[批处理] 用户选择继续添加(不使用排除列表)"); } LogManager.Info($"[批处理] 添加路径到批处理队列: {CurrentPathRoute.Name}"); // 为批处理创建独立的检测记录快照,保存当前配置、排除列表和手工目标 int detectionRecordId = CreateCollisionDetectionRecordSnapshot("批处理任务创建", bindToAnimationManager: false); LogManager.Info($"[批处理] 已创建批处理检测记录 (Id={detectionRecordId})"); // 创建批处理队列项 // 注意:虚拟物体尺寸需要从米转换为模型单位存入数据库 double metersToUnitsForQueue = UnitsConverter.GetMetersToUnitsConversionFactor(); var queueItem = new BatchQueueItem { RouteId = CurrentPathRoute.Id, Status = BatchQueueStatus.Pending, CreatedTime = DateTime.Now, // 动画配置 FrameRate = _animationFrameRate, DurationSeconds = AnimationDuration, // 碰撞检测配置(米 → 模型单位) DetectionTolerance = _detectionTolerance * metersToUnitsForQueue, // 运动物体配置(米 → 模型单位,数据库存储模型单位) IsVirtualObject = UseVirtualObject, VirtualObjectLength = VirtualObjectLengthInMeters * metersToUnitsForQueue, VirtualObjectWidth = VirtualObjectWidthInMeters * metersToUnitsForQueue, VirtualObjectHeight = VirtualObjectHeightInMeters * metersToUnitsForQueue, // 被检测项配置 DetectAllObjects = !IsManualCollisionTargetEnabled, // 角度修正配置 ObjectRotationCorrection = _objectRotationCorrection.ZDegrees, // 🔥 关联的检测记录ID DetectionRecordId = detectionRecordId }; // 调用批处理管理器添加到队列 var batchQueueManager = Core.BatchQueueManager.Instance; // 确保BatchQueueManager有PathPlanningManager(用于获取数据库) if (_pathPlanningManager != null) { batchQueueManager.SetPathPlanningManager(_pathPlanningManager); } var itemId = await batchQueueManager.AddQueueItemAsync(queueItem); // 存储运动物体到 ModelItemReferences 表 if (!UseVirtualObject && SelectedAnimatedObject != null) { LogManager.Info($"[批处理] 准备存储运动物体: {SelectedAnimatedObject.DisplayName}"); var pathIdInfo = GetObjectPathIdInfo(); LogManager.Info($"[批处理] 运动物体 PathId: ModelIndex={pathIdInfo.ModelIndex}, PathId={pathIdInfo.PathId}"); // 只要 PathId 不为空就认为是有效的(ModelIndex=0 表示主模型,也是有效的) if (!string.IsNullOrEmpty(pathIdInfo.PathId)) { var db = _pathPlanningManager?.GetPathDatabase(); LogManager.Info($"[批处理] PathDatabase 是否为 null: {db == null}"); if (db != null) { await db.AddModelItemReferenceAsync( itemId, "BatchQueueItem", pathIdInfo.ModelIndex, pathIdInfo.PathId, SelectedAnimatedObject.DisplayName, SelectedAnimatedObject.DisplayName, "MovingObject" ); LogManager.Info($"[批处理] 已存储运动物体: {SelectedAnimatedObject.DisplayName}"); } } else { LogManager.Warning($"[批处理] 运动物体 PathId 无效,跳过存储"); } } else { LogManager.Info($"[批处理] 跳过存储运动物体: UseVirtualObject={UseVirtualObject}, SelectedAnimatedObject 是否为 null={SelectedAnimatedObject == null}"); } // 存储手工指定的碰撞检测目标到 ModelItemReferences 表 if (IsManualCollisionTargetEnabled) { var db = _pathPlanningManager?.GetPathDatabase(); if (db != null) { var validItems = GetValidManualCollisionTargets(pruneInvalidEntries: true); foreach (var item in validItems) { var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(item); await db.AddModelItemReferenceAsync( itemId, "BatchQueueItem", pathId.ModelIndex, pathId.PathId, item.DisplayName, item.DisplayName, "CollisionTarget" ); } LogManager.Info($"[批处理] 已存储 {validItems.Count} 个手工指定碰撞目标"); } } // 🔥 注意:排除列表和手工目标已通过 DetectionRecordId 关联到 CollisionDetectionRecords 表 // 不需要再保存到 ModelItemReferences 表 LogManager.Info($"[批处理] 已创建队列项: {CurrentPathRoute.Name}, " + $"虚拟物体: {queueItem.IsVirtualObject}, " + $"帧率: {queueItem.FrameRate}, " + $"时长: {queueItem.DurationSeconds:F2}秒, " + $"角度修正: {_objectRotationCorrection}, " + $"ID: {itemId}"); UpdateMainStatus($"已添加到批处理队列: {CurrentPathRoute.Name}"); // 显示成功提示 System.Windows.MessageBox.Show( $"已成功添加到批处理队列:{CurrentPathRoute.Name}\n" + $"队列项ID: {itemId}", "添加成功", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information); } catch (Exception ex) { LogManager.Error($"添加到批处理失败: {ex.Message}", ex); UpdateMainStatus($"添加失败: {ex.Message}"); // 显示失败提示 System.Windows.MessageBox.Show( $"添加到批处理队列失败:{ex.Message}", "添加失败", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } #endregion } }