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