NavisworksTransport/src/UI/WPF/ViewModels/AnimationControlViewModel.cs
2025-09-01 21:42:19 +08:00

1584 lines
61 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.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Clash;
using NavisworksTransport.Commands;
using NavisworksTransport.Core.Animation;
using NavisworksTransport.Core;
using NavisworksTransport.UI.WPF.Commands;
using NavisworksTransport.UI.WPF.Collections;
using NavisworksTransport.UI.WPF.Models;
using NavisworksTransport.Utils;
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 AnimationControlViewModel : ViewModelBase, IDisposable
{
#region
private readonly NavisworksTransport.Core.Animation.PathAnimationManager _pathAnimationManager;
private readonly NavisworksTransport.Core.Animation.LogisticsAnimationManager _logisticsAnimationManager;
private readonly ClashDetectiveIntegration _clashIntegration;
private readonly UIStateManager _uiStateManager;
// 动画参数相关字段
private ObservableCollection<int> _availableFrameRates;
private int _selectedFrameRate = 30;
private double _animationDuration = 10.0;
private double _currentAnimationTime = 0.0;
private bool _canStartAnimation = true;
private bool _canPauseAnimation = false;
private bool _canStopAnimation = false;
private string _animationStatus = "就绪";
private double _animationProgress = 0.0;
private string _startAnimationButtonText = "开始动画";
// 碰撞检测相关字段
private bool _hasCollisionResults = false;
private string _collisionStatus = "就绪";
private string _collisionSummary = "尚未运行碰撞检测";
// 碰撞检测参数字段
private double _collisionDetectionAccuracy = 0.1; // 检测精度(米)
private double _movementSpeed = 1.0; // 运动速度(米/秒)
private double _detectionGap = 0.05; // 检测间隙(米)
private int _animationFrameRate = 30; // 动画帧率(FPS)
private double _collisionDetectionFrequency = 10.0; // 检测频率(次/秒)
// 防抖机制相关字段
private DispatcherTimer _parameterUpdateTimer;
private readonly object _parameterUpdateLock = new object();
private volatile bool _hasParameterChanges = false;
// 当前选中路径
private PathRouteViewModel _currentPathRoute;
// 移动物体相关字段
private ModelItem _selectedAnimatedObject;
private string _selectedAnimatedObjectName = "未选择移动物体";
private bool _hasSelectedAnimatedObject = false;
private bool _canGenerateAnimation = false;
private string _generationStatus = "就绪";
#endregion
#region
/// <summary>
/// 可用帧率集合
/// </summary>
public ObservableCollection<int> AvailableFrameRates
{
get => _availableFrameRates;
set => SetProperty(ref _availableFrameRates, value);
}
/// <summary>
/// 选中的帧率
/// </summary>
public int SelectedFrameRate
{
get => _selectedFrameRate;
set => SetProperty(ref _selectedFrameRate, value);
}
/// <summary>
/// 动画持续时间(秒)
/// </summary>
public double AnimationDuration
{
get => _animationDuration;
set => SetProperty(ref _animationDuration, value);
}
/// <summary>
/// 当前动画时间
/// </summary>
public double CurrentAnimationTime
{
get => _currentAnimationTime;
set => SetProperty(ref _currentAnimationTime, value);
}
/// <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>
public string AnimationStatus
{
get => _animationStatus;
set => SetProperty(ref _animationStatus, value);
}
/// <summary>
/// 动画进度
/// </summary>
public double AnimationProgress
{
get => _animationProgress;
set => SetProperty(ref _animationProgress, value);
}
/// <summary>
/// 开始动画按钮文本
/// </summary>
public string StartAnimationButtonText
{
get => _startAnimationButtonText;
set => SetProperty(ref _startAnimationButtonText, value);
}
/// <summary>
/// 是否有碰撞检测结果
/// </summary>
public bool HasCollisionResults
{
get => _hasCollisionResults;
set => SetProperty(ref _hasCollisionResults, value);
}
/// <summary>
/// 碰撞检测状态
/// </summary>
public string CollisionStatus
{
get => _collisionStatus;
set => SetProperty(ref _collisionStatus, value);
}
/// <summary>
/// 碰撞检测摘要
/// </summary>
public string CollisionSummary
{
get => _collisionSummary;
set => SetProperty(ref _collisionSummary, value);
}
/// <summary>
/// 碰撞检测精度(米)
/// </summary>
public double CollisionDetectionAccuracy
{
get => _collisionDetectionAccuracy;
set
{
// 限制精度到2位小数
var roundedValue = Math.Round(value, 2);
if (SetProperty(ref _collisionDetectionAccuracy, roundedValue))
{
UpdateCollisionDetectionFrequency();
ScheduleParameterUpdate();
}
}
}
/// <summary>
/// 运动速度(米/秒)
/// </summary>
public double MovementSpeed
{
get => _movementSpeed;
set
{
// 限制精度到1位小数
var roundedValue = Math.Round(value, 1);
if (SetProperty(ref _movementSpeed, roundedValue))
{
UpdateCollisionDetectionFrequency();
ScheduleParameterUpdate();
}
}
}
/// <summary>
/// 检测间隙(米)
/// </summary>
public double DetectionGap
{
get => _detectionGap;
set
{
// 限制精度到2位小数
var roundedValue = Math.Round(value, 2);
if (SetProperty(ref _detectionGap, roundedValue))
{
ScheduleParameterUpdate();
}
}
}
/// <summary>
/// 动画帧率(FPS)
/// </summary>
public int AnimationFrameRate
{
get => _animationFrameRate;
set
{
if (SetProperty(ref _animationFrameRate, value))
{
UpdatePathAnimationManagerSettings();
}
}
}
/// <summary>
/// 碰撞检测频率(次/秒) - 计算得出
/// </summary>
public double CollisionDetectionFrequency
{
get => _collisionDetectionFrequency;
private set => SetProperty(ref _collisionDetectionFrequency, value);
}
/// <summary>
/// 当前选中的路径
/// </summary>
public PathRouteViewModel CurrentPathRoute
{
get => _currentPathRoute;
set
{
SetProperty(ref _currentPathRoute, value);
UpdateCanGenerateAnimation();
}
}
/// <summary>
/// 选中的移动物体
/// </summary>
public ModelItem SelectedAnimatedObject
{
get => _selectedAnimatedObject;
set
{
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);
}
/// <summary>
/// 生成状态
/// </summary>
public string GenerationStatus
{
get => _generationStatus;
set => SetProperty(ref _generationStatus, value);
}
#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; }
#endregion
#region
public AnimationControlViewModel() : base()
{
try
{
// 初始化管理器
_pathAnimationManager = new NavisworksTransport.Core.Animation.PathAnimationManager();
_logisticsAnimationManager = new NavisworksTransport.Core.Animation.LogisticsAnimationManager();
_clashIntegration = ClashDetectiveIntegration.Instance;
_uiStateManager = UIStateManager.Instance;
// 订阅PathAnimationManager事件
_pathAnimationManager.ProgressChanged += OnAnimationProgressChanged;
_pathAnimationManager.StateChanged += OnAnimationStateChanged;
// 初始化集合
AvailableFrameRates = new ThreadSafeObservableCollection<int>();
// 初始化设置
InitializeAnimationSettings();
// 初始化命令
InitializeCommands();
// 初始化碰撞检测集成
InitializeClashIntegration();
// 初始化防抖定时器
InitializeParameterUpdateTimer();
LogManager.Info("AnimationControlViewModel初始化完成含缓存管理");
}
catch (Exception ex)
{
LogManager.Error($"AnimationControlViewModel初始化失败: {ex.Message}", ex);
throw;
}
}
#endregion
#region
/// <summary>
/// 初始化动画设置
/// </summary>
private void InitializeAnimationSettings()
{
// 初始化可用帧率
AvailableFrameRates.Clear();
var frameRates = new[] { 15, 24, 30, 60 };
foreach (var rate in frameRates)
{
AvailableFrameRates.Add(rate);
}
SelectedFrameRate = 30; // 默认30fps
// 设置默认动画参数
AnimationDuration = 10.0;
CurrentAnimationTime = 0.0;
// 设置初始状态
CanStartAnimation = true;
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画";
AnimationStatus = "动画状态: 就绪";
AnimationProgress = 0;
// 初始化碰撞检测状态
HasCollisionResults = false;
CollisionStatus = "就绪";
CollisionSummary = "尚未运行碰撞检测";
// 初始化碰撞检测参数
CollisionDetectionAccuracy = 0.1; // 0.1米
MovementSpeed = 1.0; // 1米/秒
DetectionGap = 0.05; // 0.05米
AnimationFrameRate = 30; // 30FPS
UpdateCollisionDetectionFrequency(); // 计算初始检测频率
// 初始化动画管理器设置
UpdatePathAnimationManagerSettings();
LogManager.Info("动画设置初始化完成");
}
/// <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 ExecuteViewCollisionReportAsync(), () => HasCollisionResults);
SelectAnimatedObjectCommand = new RelayCommand(ExecuteSelectAnimatedObject);
ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
GenerateAnimationCommand = new RelayCommand(ExecuteGenerateAnimation, () => CanGenerateAnimation);
LogManager.Info("动画控制命令初始化完成");
}
/// <summary>
/// 初始化碰撞检测集成
/// </summary>
private void InitializeClashIntegration()
{
try
{
_clashIntegration.Initialize();
_clashIntegration.CollisionDetected += OnCollisionDetected;
LogManager.Info("碰撞检测集成初始化完成");
}
catch (Exception ex)
{
LogManager.Error($"碰撞检测集成初始化失败: {ex.Message}");
}
}
#endregion
#region
/// <summary>
/// 开始动画命令
/// </summary>
private async Task ExecuteStartAnimationAsync()
{
if (!CanStartAnimation || CurrentPathRoute == null || CurrentPathRoute.Points.Count < 2)
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
AnimationStatus = "请先选择包含至少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(() =>
{
AnimationStatus = "动画已在播放中";
CanStartAnimation = true;
CanPauseAnimation = false;
CanStopAnimation = false;
});
return;
}
// 开始播放物体移动动画
// 首先重置进度(开始全新动画时)
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
AnimationProgress = 0;
CurrentAnimationTime = 0.0;
});
_pathAnimationManager.StartAnimation();
LogManager.Info($"开始物体移动动画播放: {CurrentPathRoute.Name}, 帧率: {SelectedFrameRate}fps");
}
catch (Exception ex)
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
AnimationStatus = $"开始动画出错: {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(() =>
{
AnimationStatus = "暂停动画失败";
});
}
}
/// <summary>
/// 停止动画命令
/// </summary>
private async Task ExecuteStopAnimationAsync()
{
try
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
AnimationStatus = "动画状态: 已停止";
AnimationProgress = 0;
CurrentAnimationTime = 0.0;
CanStartAnimation = true;
CanPauseAnimation = false;
CanStopAnimation = false;
LogManager.Info("停止动画播放");
});
// 停止当前动画
_pathAnimationManager.StopAnimation();
}
catch (Exception ex)
{
LogManager.Error($"停止动画异常: {ex.Message}", ex);
}
}
/// <summary>
/// 查看碰撞报告命令
/// </summary>
private async Task ExecuteViewCollisionReportAsync()
{
if (!HasCollisionResults)
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CollisionStatus = "尚无碰撞检测结果可查看";
});
return;
}
try
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CollisionStatus = "正在生成碰撞报告...";
});
LogManager.Info("开始生成碰撞检测详细报告");
// 在后台线程生成报告
var reportData = await Task.Run(() =>
{
try
{
return GenerateCollisionReport();
}
catch (Exception ex)
{
LogManager.Error($"生成碰撞报告失败: {ex.Message}");
return null;
}
});
if (reportData != null)
{
// 显示报告 - 可以通过多种方式显示
await ShowCollisionReport(reportData);
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CollisionStatus = "碰撞报告已生成";
LogManager.Info("碰撞报告生成完成并已显示");
});
}
else
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CollisionStatus = "生成碰撞报告失败";
});
}
}
catch (Exception ex)
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
CollisionStatus = "查看碰撞报告出错";
});
LogManager.Error($"查看碰撞报告异常: {ex.Message}", ex);
}
}
#endregion
#region
/// <summary>
/// 设置当前路径
/// </summary>
public void SetCurrentPath(PathRouteViewModel pathRoute)
{
CurrentPathRoute = pathRoute;
// 更新按钮状态
var hasValidPath = pathRoute != null && pathRoute.Points.Count >= 2;
CanStartAnimation = hasValidPath;
// 更新状态文本
if (pathRoute == null)
{
AnimationStatus = "动画状态: 请选择路径";
CollisionStatus = "请选择路径";
}
else if (pathRoute.Points.Count < 2)
{
AnimationStatus = "动画状态: 路径点数不足需要至少2个点";
CollisionStatus = "路径点数不足";
}
else
{
AnimationStatus = "动画状态: 就绪";
CollisionStatus = "就绪";
}
LogManager.Info($"AnimationControlViewModel当前路径更新: {pathRoute?.Name ?? ""}, 点数: {pathRoute?.Points.Count ?? 0}");
}
/// <summary>
/// 处理动画进度变化事件
/// 直接使用Dispatcher更新UI避免UIStateManager的超时机制导致UI积压
/// </summary>
private void OnAnimationProgressChanged(object sender, double progressPercent)
{
// 直接使用Dispatcher.BeginInvoke进行UI更新避免UIStateManager的5秒超时导致积压
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
// 直接使用传入的百分比数值
AnimationProgress = progressPercent;
CurrentAnimationTime = (AnimationProgress / 100.0) * AnimationDuration;
}));
}
/// <summary>
/// 处理动画状态变化事件
/// 直接使用Dispatcher更新UI避免UIStateManager的超时机制导致UI积压
/// </summary>
private void OnAnimationStateChanged(object sender, NavisworksTransport.Core.Animation.AnimationState state)
{
// 直接使用Dispatcher.BeginInvoke进行UI更新避免UIStateManager的5秒超时导致积压
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
switch (state)
{
case NavisworksTransport.Core.Animation.AnimationState.Playing:
CanStartAnimation = false;
CanPauseAnimation = true;
CanStopAnimation = true;
StartAnimationButtonText = "开始动画"; // 播放中时按钮显示为灰色的"开始动画"
AnimationStatus = "动画状态: 播放中";
break;
case NavisworksTransport.Core.Animation.AnimationState.Paused:
CanStartAnimation = true; // 暂停状态下可以继续
CanPauseAnimation = false;
CanStopAnimation = true;
StartAnimationButtonText = "继续播放"; // 暂停状态下显示"继续播放"
AnimationStatus = "动画状态: 已暂停";
break;
case NavisworksTransport.Core.Animation.AnimationState.Finished:
CanStartAnimation = true;
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画"; // 完成后恢复"开始动画"
AnimationStatus = "动画状态: 已完成";
// 动画完成后更新碰撞检测状态
UpdateCollisionStatusAfterAnimation();
break;
case NavisworksTransport.Core.Animation.AnimationState.Stopped:
CanStartAnimation = true;
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画"; // 停止后恢复"开始动画"
AnimationStatus = "动画状态: 已停止";
break;
default:
StartAnimationButtonText = "开始动画";
AnimationStatus = $"动画状态: {state}";
break;
}
}));
}
/// <summary>
/// 处理碰撞检测事件
/// </summary>
private async void OnCollisionDetected(object sender, CollisionDetectedEventArgs e)
{
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
HasCollisionResults = e.Results.Count > 0;
CollisionSummary = e.Results.Count > 0
? $"发现 {e.Results.Count} 个碰撞点"
: "未发现碰撞";
});
}
/// <summary>
/// 执行选择移动物体命令
/// </summary>
private void ExecuteSelectAnimatedObject()
{
try
{
LogManager.Info("开始选择移动物体");
// 获取当前选中的模型项
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var selectedItems = doc.CurrentSelection.SelectedItems;
if (selectedItems.Count == 0)
{
GenerationStatus = "请先在Navisworks中选择一个物体";
LogManager.Warning("用户未选择任何物体");
return;
}
if (selectedItems.Count > 1)
{
GenerationStatus = "请只选择一个物体作为移动对象";
LogManager.Warning($"用户选择了{selectedItems.Count}个物体,需要只选择一个");
return;
}
var selectedItem = selectedItems.First;
// 检查选中项是否包含几何体(直接包含或子项包含)
bool hasAnyGeometry = HasGeometryRecursive(selectedItem);
if (!hasAnyGeometry)
{
GenerationStatus = "选中的项目及其子项都不包含几何体,请选择其他物体";
LogManager.Warning("选中的项目及其子项都不包含几何体");
return;
}
LogManager.Info($"选中物体信息: DisplayName={selectedItem.DisplayName}, HasGeometry={selectedItem.HasGeometry}, ChildCount={selectedItem.Children.Count()}");
// 设置选中的移动物体
SelectedAnimatedObject = selectedItem;
GenerationStatus = "移动物体选择成功";
LogManager.Info($"移动物体选择成功: {SelectedAnimatedObject.DisplayName}");
}
catch (Exception ex)
{
GenerationStatus = "选择移动物体失败";
LogManager.Error($"选择移动物体时发生错误: {ex.Message}");
}
}
/// <summary>
/// 执行清除移动物体命令
/// </summary>
/// <summary>
/// 执行清除移动物体命令
/// </summary>
private void ExecuteClearAnimatedObject()
{
try
{
LogManager.Info("开始清除移动物体选择并恢复原始位置");
// 如果有选中的物体,则恢复到原始位置
if (SelectedAnimatedObject != null)
{
try
{
LogManager.Info($"开始恢复物体 '{SelectedAnimatedObject.DisplayName}' 到原始位置");
// 使用ResetPermanentTransform清除所有增量变换恢复到设计文件中的原始位置
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var modelItems = new ModelItemCollection { SelectedAnimatedObject };
doc.Models.ResetPermanentTransform(modelItems);
LogManager.Info($"物体 '{SelectedAnimatedObject.DisplayName}' 已成功恢复到原始位置");
GenerationStatus = "已清除移动物体选择并恢复到原始位置";
}
catch (Exception restoreEx)
{
LogManager.Error($"恢复物体到原始位置时发生错误: {restoreEx.Message}");
GenerationStatus = $"已清除物体选择,但恢复位置失败: {restoreEx.Message}";
}
}
else
{
LogManager.Info("没有选中的物体需要清除");
GenerationStatus = "已清除移动物体选择";
}
// 清理选择状态
SelectedAnimatedObject = null;
LogManager.Info("移动物体选择已完全清除");
}
catch (Exception ex)
{
LogManager.Error($"清除移动物体选择时发生错误: {ex.Message}");
GenerationStatus = $"清除失败: {ex.Message}";
}
}
/// <summary>
/// 异步预计算碰撞排除列表
/// </summary>
/// <param name="animationObject">动画对象</param>
private async Task PrecomputeCollisionExclusionsAsync(ModelItem animationObject)
{
try
{
if (animationObject == null)
{
LogManager.Debug("[缓存预计算] 动画对象为空,跳过预计算");
return;
}
LogManager.Info($"[缓存预计算] 开始为新选择的动画对象预计算: {animationObject.DisplayName}");
// 在UI线程中更新开始状态
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
GenerationStatus = "正在分析动画对象...";
}));
// 在UI线程中执行Navisworks API操作线程安全
bool success;
try
{
success = _logisticsAnimationManager.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 = _logisticsAnimationManager.GetCacheStats();
GenerationStatus = $"动画对象分析完成";
LogManager.Info($"[缓存预计算] 预计算成功 - {cacheStats}");
}
else
{
GenerationStatus = "动画对象分析失败,将使用实时计算";
LogManager.Warning("[缓存预计算] 预计算失败,碰撞检测将使用实时计算模式");
}
}));
}
catch (Exception ex)
{
LogManager.Error($"[缓存预计算] 异步预计算过程异常: {ex.Message}", ex);
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
GenerationStatus = "动画对象分析异常";
}));
}
}
/// <summary>
/// 执行生成动画命令同步执行因为Navisworks API需要STA线程
/// </summary>
private void ExecuteGenerateAnimation()
{
try
{
if (!CanGenerateAnimation)
{
GenerationStatus = "无法生成动画:缺少必要条件";
LogManager.Warning("尝试生成动画但条件不满足");
return;
}
GenerationStatus = "正在生成动画...";
LogManager.Info("开始生成动画");
// 🔥 新增:在生成动画阶段构建碰撞检测缓存
GenerationStatus = "正在构建碰撞检测缓存...";
LogManager.Info("[动画生成] 开始构建碰撞检测缓存");
var cacheStartTime = DateTime.Now;
try
{
// 构建全局缓存(与具体移动物体无关)
ClashDetectiveIntegration.ClearAllCaches();
GenerationStatus = "正在缓存几何对象列表...";
ClashDetectiveIntegration.BuildAllGeometryItemsCache();
GenerationStatus = "正在缓存通道对象...";
var clashIntegration = ClashDetectiveIntegration.Instance;
clashIntegration.BuildChannelObjectsCache();
var cacheElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
LogManager.Info($"[动画生成] 碰撞检测缓存构建完成,耗时: {cacheElapsed:F1}ms");
// 构建特定于动画对象的缓存
GenerationStatus = "正在分析动画对象...";
var success = _logisticsAnimationManager.PrecomputeCollisionExclusions(SelectedAnimatedObject);
if (!success)
{
LogManager.Warning("[动画生成] 动画对象分析失败,将使用实时计算模式");
}
}
catch (Exception cacheEx)
{
LogManager.Warning($"[动画生成] 缓存构建失败: {cacheEx.Message},将使用实时计算模式");
}
// 原有的动画生成逻辑
GenerationStatus = "正在生成路径动画...";
// 将PathRouteViewModel的点转换为Point3D列表
var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
// 使用PathAnimationManager设置物体动画真正的物体移动动画
_pathAnimationManager.SetupAnimation(SelectedAnimatedObject, pathPoints, AnimationDuration);
// 更新状态
CanStartAnimation = true;
AnimationStatus = "动画已生成,可以开始播放";
GenerationStatus = "动画生成成功";
var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
LogManager.Info($"[动画生成] 动画生成完成,总耗时: {totalElapsed:F1}ms");
LogManager.Info($"动画生成成功: 物体={SelectedAnimatedObject.DisplayName}, 路径={CurrentPathRoute.Name}");
}
catch (Exception ex)
{
GenerationStatus = "动画生成失败";
AnimationStatus = "动画生成失败";
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 hasObject = SelectedAnimatedObject != null;
var hasPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
CanGenerateAnimation = hasObject && hasPath;
if (!hasObject && !hasPath)
{
GenerationStatus = "请选择移动物体和动画路径";
}
else if (!hasObject)
{
GenerationStatus = "请选择移动物体";
}
else if (!hasPath)
{
GenerationStatus = "请选择有效的动画路径";
}
else
{
GenerationStatus = "可以生成动画";
}
}
/// <summary>
/// 更新碰撞检测频率
/// </summary>
private void UpdateCollisionDetectionFrequency()
{
// 碰撞检测频率 = 运动速度 / 检测精度 (次/秒)
if (_collisionDetectionAccuracy > 0)
{
CollisionDetectionFrequency = _movementSpeed / _collisionDetectionAccuracy;
}
else
{
CollisionDetectionFrequency = 10.0; // 默认值
}
}
/// <summary>
/// 初始化参数更新防抖定时器
/// </summary>
private void InitializeParameterUpdateTimer()
{
_parameterUpdateTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(150) // 150毫秒防抖延迟
};
_parameterUpdateTimer.Tick += OnParameterUpdateTimerTick;
}
/// <summary>
/// 定时器触发时执行参数更新
/// </summary>
private void OnParameterUpdateTimerTick(object sender, EventArgs e)
{
_parameterUpdateTimer.Stop();
lock (_parameterUpdateLock)
{
if (_hasParameterChanges)
{
_hasParameterChanges = false;
UpdatePathAnimationManagerSettings();
}
}
}
/// <summary>
/// 计划参数更新(防抖机制)
/// </summary>
private void ScheduleParameterUpdate()
{
lock (_parameterUpdateLock)
{
_hasParameterChanges = true;
_parameterUpdateTimer.Stop();
_parameterUpdateTimer.Start();
}
}
/// <summary>
/// 更新PathAnimationManager的设置
/// </summary>
private void UpdatePathAnimationManagerSettings()
{
try
{
if (_pathAnimationManager != null)
{
_pathAnimationManager.SetAnimationFrameRate(_animationFrameRate);
_pathAnimationManager.SetCollisionDetectionAccuracy(_collisionDetectionAccuracy);
_pathAnimationManager.SetMovementSpeed(_movementSpeed);
_pathAnimationManager.SetDetectionGap(_detectionGap);
LogManager.Debug($"碰撞检测参数已更新: 帧率={_animationFrameRate}FPS, 精度={_collisionDetectionAccuracy:F2}m, 速度={_movementSpeed:F1}m/s, 间隙={_detectionGap:F2}m");
}
}
catch (Exception ex)
{
LogManager.Error($"更新动画管理器设置失败: {ex.Message}");
}
}
/// <summary>
/// 动画完成后更新碰撞检测状态
/// </summary>
private void UpdateCollisionStatusAfterAnimation()
{
try
{
// 给一个短暂延迟让CreateAllAnimationCollisionTests有时间完成
Task.Delay(1000).ContinueWith(_ => _uiStateManager.ExecuteUIUpdateAsync(() =>
{
// 检查ClashDetectiveIntegration是否有缓存结果
var clashIntegration = _clashIntegration;
if (clashIntegration != null)
{
// 通过检查是否有任何动画相关的碰撞测试来判断是否有结果
HasCollisionResults = true; // 假设动画完成后总是有结果可查看
CollisionStatus = "动画完成,碰撞检测已完成";
CollisionSummary = "请点击'查看碰撞报告'查看详细结果";
LogManager.Info("动画完成后碰撞状态已更新");
}
else
{
CollisionStatus = "碰撞检测未就绪";
CollisionSummary = "碰撞检测功能不可用";
LogManager.Warning("ClashDetectiveIntegration实例不可用");
}
}));
}
catch (Exception ex)
{
LogManager.Error($"动画完成后更新碰撞状态失败: {ex.Message}");
}
}
/// <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 testInfo = new CollisionTestInfo
{
TestName = test.DisplayName,
TestType = test.TestType.ToString(),
Tolerance = test.Tolerance,
CollisionCount = test.Children.Count,
Status = test.Status.ToString()
};
// 获取每个碰撞的详细信息
foreach (var clashResult in test.Children.Cast<ClashResult>())
{
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)
{
// 直接使用报告数据统计
CollisionSummary = $"动画检测: {reportData.TotalTests}个 | Clash Detective权威结果: {reportData.TotalCollisions}个";
}
else
{
CollisionSummary = $"报告生成失败:{reportData.ErrorMessage}";
}
});
// 如果报告有效且有碰撞数据,显示详细报告窗口
if (reportData.IsValid && reportData.TotalCollisions > 0)
{
// 调用ViewCollisionReportCommand显示详细报告窗口
try
{
// 创建综合碰撞报告命令(现在不需要传递参数了!)
var reportCommand = NavisworksTransport.Commands.ViewCollisionReportCommand.CreateComprehensive(autoHighlight: false);
// 执行命令UI显示会在正确的线程中处理
var result = await reportCommand.ExecuteAsync();
if (result.IsSuccess)
{
LogManager.Info("碰撞报告窗口已成功显示");
}
else
{
LogManager.Warning($"显示碰撞报告窗口失败: {result.ErrorMessage}");
}
}
catch (Exception ex)
{
LogManager.Error($"调用碰撞报告命令失败: {ex.Message}");
}
}
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);
}
}
/// <summary>
/// 将碰撞报告详情输出到日志
/// </summary>
private void LogCollisionReportDetails(CollisionReportData reportData)
{
if (!reportData.IsValid)
{
LogManager.Error($"碰撞报告无效: {reportData.ErrorMessage}");
return;
}
var report = new System.Text.StringBuilder();
report.AppendLine("=== 碰撞检测详细报告 ===");
report.AppendLine($"生成时间: {reportData.GeneratedTime:yyyy-MM-dd HH:mm:ss}");
report.AppendLine($"动画对象: {reportData.AnimatedObjectName}");
report.AppendLine($"动画路径: {reportData.PathName}");
report.AppendLine($"总测试数: {reportData.TotalTests}");
report.AppendLine($"总碰撞数: {reportData.TotalCollisions}");
report.AppendLine();
foreach (var test in reportData.Tests)
{
report.AppendLine($"【测试】 {test.TestName}");
report.AppendLine($" 类型: {test.TestType}, 容差: {test.Tolerance:F3}, 状态: {test.Status}");
report.AppendLine($" 碰撞数量: {test.CollisionCount}");
foreach (var collision in test.Collisions)
{
report.AppendLine($" 碰撞 {collision.CollisionId}:");
report.AppendLine($" 对象1: {collision.Object1Name} 位置: {collision.Object1Position}");
report.AppendLine($" 对象2: {collision.Object2Name} 位置: {collision.Object2Position}");
report.AppendLine($" 碰撞中心: {collision.CollisionCenter}");
report.AppendLine($" 距离: {collision.Distance:F3}, 状态: {collision.Status}");
}
report.AppendLine();
}
LogManager.Info(report.ToString());
}
#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. 优先清理防抖定时器,避免在清理过程中触发更新
if (_parameterUpdateTimer != null)
{
try
{
_parameterUpdateTimer.Stop();
_parameterUpdateTimer.Tick -= OnParameterUpdateTimerTick;
_parameterUpdateTimer = null;
LogManager.Debug("防抖定时器清理完成");
}
catch (Exception timerEx)
{
LogManager.Warning($"清理防抖定时器时出现警告: {timerEx.Message}");
}
}
// 2. 取消事件订阅(在停止动画之前,避免事件处理中访问已释放的对象)
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;
LogManager.Debug("碰撞检测事件订阅取消完成");
}
catch (Exception eventEx)
{
LogManager.Warning($"取消碰撞检测事件订阅时出现警告: {eventEx.Message}");
}
}
// 3. 直接清理动画管理器PathAnimationManager.Dispose内部已经优化了重复调用
// 不需要在这里再次调用StopAnimation让PathAnimationManager自己处理
if (_pathAnimationManager != null)
{
try
{
// 检查Navisworks应用程序是否仍然可用
var app = Autodesk.Navisworks.Api.Application.ActiveDocument;
if (app != null && app.Models != null)
{
_pathAnimationManager.Dispose();
LogManager.Debug("PathAnimationManager清理完成");
}
else
{
LogManager.Info("Navisworks文档已不可用跳过PathAnimationManager清理操作");
}
}
catch (Exception disposeEx)
{
LogManager.Warning($"清理PathAnimationManager时出现警告: {disposeEx.Message}");
}
}
// 4. 清理LogisticsAnimationManager缓存
if (_logisticsAnimationManager != null)
{
try
{
_logisticsAnimationManager.ClearExclusionCache();
_logisticsAnimationManager.Dispose();
LogManager.Debug("LogisticsAnimationManager缓存清理完成");
}
catch (Exception logisticsEx)
{
LogManager.Warning($"清理LogisticsAnimationManager缓存时出现警告: {logisticsEx.Message}");
}
}
LogManager.Info("AnimationControlViewModel资源清理完成");
}
catch (Exception ex)
{
LogManager.Error($"AnimationControlViewModel资源清理过程中发生异常: {ex.Message}");
// 即使清理过程中发生异常,也不应该抛出,因为这会干扰应用程序的正常关闭
}
}
_disposed = true;
}
}
#endregion
}
}