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

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