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

2898 lines
112 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

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