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.Core.Services;
using NavisworksTransport.Commands;
using NavisworksTransport.UI.WPF.Collections;
using NavisworksTransport.UI.WPF.Views;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;

namespace NavisworksTransport.UI.WPF.ViewModels
{
    /// <summary>
    /// 碰撞热点信息 - 用于分析高频碰撞物体
    /// </summary>
    public class CollisionHotspotInfo
    {
        public ModelItem Object { get; set; }
        public string ObjectName { get; set; }
        public int CollisionCount { get; set; }
        public double Percentage { get; set; }
        public string Reason { get; set; }
        public string RecommendedAction { get; set; }
    }

    /// <summary>
    /// 碰撞报告数据
    /// </summary>
    public class CollisionReportData
    {
        public bool IsValid { get; set; }
        public string ErrorMessage { get; set; } = string.Empty;
        public DateTime GeneratedTime { get; set; }
        public string AnimatedObjectName { get; set; } = string.Empty;
        public string PathName { get; set; } = string.Empty;
        public int TotalTests { get; set; }
        public int TotalCollisions { get; set; }
        public List<CollisionTestInfo> Tests { get; set; } = new List<CollisionTestInfo>();
    }

    /// <summary>
    /// 碰撞测试信息
    /// </summary>
    public class CollisionTestInfo
    {
        public string TestName { get; set; } = string.Empty;
        public string TestType { get; set; } = string.Empty;
        public double Tolerance { get; set; }
        public int CollisionCount { get; set; }
        public string Status { get; set; } = string.Empty;
        public List<CollisionDetailInfo> Collisions { get; set; } = new List<CollisionDetailInfo>();
    }

    /// <summary>
    /// 碰撞详细信息
    /// </summary>
    public class CollisionDetailInfo
    {
        public string CollisionId { get; set; } = string.Empty;
        public string Object1Name { get; set; } = string.Empty;
        public string Object1Position { get; set; } = string.Empty;
        public string Object2Name { get; set; } = string.Empty;
        public string Object2Position { get; set; } = string.Empty;
        public string CollisionCenter { get; set; } = string.Empty;
        public double Distance { get; set; }
        public string Status { get; set; } = string.Empty;
    }

    /// <summary>
    /// 手工指定的碰撞检测对象
    /// </summary>
    public class ManualCollisionTargetViewModel : INotifyPropertyChanged
    {
        private int _index;

        public ManualCollisionTargetViewModel(ModelItem modelItem, string displayName, string modelPath, int index = 0)
        {
            ModelItem = modelItem ?? throw new ArgumentNullException(nameof(modelItem));
            DisplayName = displayName;
            ModelPath = modelPath;
            InstanceGuid = modelItem.InstanceGuid;
            _index = index;
        }

        public ModelItem ModelItem { get; }
        public string DisplayName { get; }
        public string ModelPath { get; }
        public Guid InstanceGuid { get; }

        public int Index
        {
            get => _index;
            set
            {
                if (_index != value)
                {
                    _index = value;
                    OnPropertyChanged();
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    /// <summary>
    /// 排除对象ViewModel - 用于检测排除列表
    /// </summary>
    public class ExcludedObjectViewModel : INotifyPropertyChanged
    {
        private int _index;

        public ExcludedObjectViewModel(ModelItem modelItem, string displayName, string modelPath, int index = 0)
        {
            ModelItem = modelItem ?? throw new ArgumentNullException(nameof(modelItem));
            DisplayName = displayName;
            ModelPath = modelPath;
            InstanceGuid = modelItem.InstanceGuid;
            _index = index;
        }

        public ModelItem ModelItem { get; }
        public string DisplayName { get; }
        public string ModelPath { get; }
        public Guid InstanceGuid { get; }

        public int Index
        {
            get => _index;
            set
            {
                if (_index != value)
                {
                    _index = value;
                    OnPropertyChanged();
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    /// <summary>
    /// 碰撞构件ViewModel
    /// </summary>
    public class CollisionObjectViewModel
    {
        public int Index { get; set; }
        public string DisplayName { get; set; }
        public string ObjectName { get; set; }
        public string ModelPath { get; set; }
        public int ResultId { get; set; }
        public string TestName { get; set; }
        public int? ModelIndex { get; set; }
        public string PathId { get; set; }
        public ModelItem ModelItem { get; set; }
        public ICommand HighlightCommand { get; set; }
        public ICommand ClearHighlightCommand { get; set; }
        
        // 动画跟踪点位置信息（用于还原碰撞场景）
        public Point3D AnimatedObjectTrackedPosition { get; set; }
        public double AnimatedObjectTrackedYawRadians { get; set; }
        public Rotation3D AnimatedObjectTrackedRotation { get; set; }
        public bool AnimatedObjectHasTrackedRotation { get; set; }
        public bool HasPositionInfo { get; set; }
    }

    /// <summary>
    /// ClashDetective碰撞检测结果视图模型
    /// </summary>
    public class ClashDetectiveResultViewModel
    {
        private readonly Action _refreshCallback;

        public ClashDetectiveResultViewModel(CollisionDetectionRecord record, Action refreshCallback)
        {
            Record = record ?? throw new ArgumentNullException(nameof(record));
            _refreshCallback = refreshCallback ?? throw new ArgumentNullException(nameof(refreshCallback));
            ViewCommand = new RelayCommand(() => ExecuteView());
            ReportCommand = new RelayCommand(() => ExecuteReport());
            DeleteCommand = new RelayCommand(() => ExecuteDelete());
            CollisionObjects = new ObservableCollection<CollisionObjectViewModel>();
        }

        public CollisionDetectionRecord Record { get; }
        
        // 运动物体（从碰撞历史数据库中加载）
        public ModelItem AnimatedObject { get; set; }
        
        // 虚拟物体尺寸（当IsVirtualObject=true时使用）
        public double VirtualObjectLengthInMeters { get; set; }
        public double VirtualObjectWidthInMeters { get; set; }
        public double VirtualObjectHeightInMeters { get; set; }
        
        public string TestTimeDisplay => Record.TestTime?.ToString("MM-dd HH:mm:ss") ?? Record.CreatedTime.ToString("MM-dd HH:mm:ss");
        public string CollisionCountDisplay => $"{Record.CollisionCount}个";
        public ICommand ViewCommand { get; }
        public ICommand ReportCommand { get; }
        public ICommand DeleteCommand { get; }
        public ObservableCollection<CollisionObjectViewModel> CollisionObjects { get; }
        
        public void ExecuteHighlightCollisionObject(CollisionObjectViewModel collisionObject)
        {
            if (collisionObject == null) return;

            try
            {
                LogManager.Info($"[碰撞构件] 高亮显示构件: {collisionObject.DisplayName}");

                // 清除之前的高亮
                ModelHighlightHelper.ClearAllHighlights();

                // 直接使用ModelItem高亮
                if (collisionObject.ModelItem != null && ModelItemAnalysisHelper.IsModelItemValid(collisionObject.ModelItem))
                {
                    ModelHighlightHelper.HighlightItems(
                        ModelHighlightHelper.ClashDetectiveResultsCategory,
                        new[] { collisionObject.ModelItem });
                    LogManager.Info($"[碰撞构件] 已高亮构件: {collisionObject.DisplayName}");
                }
                else
                {
                    LogManager.Warning($"[碰撞构件] ModelItem无效或为空: {collisionObject.DisplayName}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[碰撞构件] 高亮显示失败: {ex.Message}", ex);
            }
        }

        private void ExecuteView()
        {
            // 高亮显示该次检测的碰撞对象
            var clashIntegration = ClashDetectiveIntegration.Instance;
            ModelHighlightHelper.HighlightClashDetectiveResults(Record.TestName, clashIntegration.GetCurrentPathClashResults);
        }

        private void ExecuteReport()
        {
            // 打开该次检测的详细报告
            try
            {
                var testName = Record.TestName;
                LogManager.Info($"[历史报告] 开始生成历史碰撞报告: {testName}");

                // 直接使用现有的报告生成命令，但指定测试名称
                // 常规检测已经高亮了，所以 hasHighlighted=true
                var command = GenerateCollisionReportCommand.CreateComprehensive(hasHighlighted: true);
                command.SetTestName(testName);
                _ = command.ExecuteAsync();
            }
            catch (Exception ex)
            {
                LogManager.Error($"[历史报告] 生成历史报告失败: {ex.Message}", ex);
                System.Windows.MessageBox.Show($"生成历史报告失败: {ex.Message}", "错误", 
                    System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
            }
        }

        private void ExecuteDelete()
        {
            // 删除该次检测记录
            var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
            if (pathDatabase != null)
            {
                pathDatabase.DeleteDetectionResult(Record.Id);
                // 通过回调刷新列表
                _refreshCallback?.Invoke();
            }
        }
    }

    /// <summary>
    /// 动画控制视图模型
    /// 专门处理路径动画播放和碰撞检测功能
    /// </summary>
    public class AnimationControlViewModel : ViewModelBase, IDisposable
    {
        #region 私有字段

        private readonly Core.Animation.PathAnimationManager _pathAnimationManager;
        private readonly ClashDetectiveIntegration _clashIntegration;
        private readonly UIStateManager _uiStateManager;
        private PathPlanningManager _pathPlanningManager;
        
        // 记录最近复用的历史检测记录ID（用于判断是否需要跳过动画生成）
        private int? _lastReusedRecordId;
        
        // 碰撞构件查看时保存的动画物体状态（用于恢复）
        private ModelItem _savedAnimatedObjectForCollision;

        #endregion

        #region 事件
        
        /// <summary>
        /// 请求滚动到指定碰撞历史记录的事件（用于代码选中后UI自动滚动到该项）
        /// </summary>
        public event EventHandler<ClashDetectiveResultViewModel> RequestScrollToClashDetectiveResult;
        private ModelItemTransformHelper.ObjectStateSnapshot _savedAnimatedObjectStateForCollision;

        // 动画参数相关字段（从配置初始化）
        private ObservableCollection<int> _availableFrameRates;
        private int _selectedFrameRate;
        private double _animationDuration;
        private bool _canStartAnimation = true;
        private bool _canPauseAnimation = false;
        private bool _canStopAnimation = false;

        // 碰撞检测相关字段
        private CollisionReportResult _lastGeneratedReport; // 缓存最后生成的报告

        // 碰撞检测参数字段（从配置初始化）
        private double _detectionTolerance; // 检测容差(米)
        private int _animationFrameRate; // 动画帧率(FPS)


        // 当前选中路径
        private PathRouteViewModel _currentPathRoute;

        // 移动物体相关字段
        private ModelItem _selectedAnimatedObject;
        private string _selectedAnimatedObjectName;
        private bool _hasSelectedAnimatedObject = false;
        private bool _canGenerateAnimation = false;

        // 虚拟物体相关字段
        private bool _useVirtualObject = false;     // 使用虚拟物体
        private double _virtualObjectLengthInMeters;  // 虚拟物体长度（米）
        private double _virtualObjectWidthInMeters;   // 虚拟物体宽度（米）
        private double _virtualObjectHeightInMeters;  // 虚拟物体高度（米）
        private double _safetyMarginInMeters;  // 检测间隙（米），从路径编辑同步

        // 角度修正相关字段
        private LocalEulerRotationCorrection _objectRotationCorrection = LocalEulerRotationCorrection.Zero;  // 物体绕宿主 X/Y/Z 轴的角度修正
        private double _objectGroundLiftHeightInMeters;
        private double? _autoAabbSx, _autoAabbSy, _autoAabbSz;

        // 移动物体原始尺寸（米，在选择物体时保存）
        private double _objectOriginalLength;  // 物体原始长度（X方向）
        private double _objectOriginalWidth;   // 物体原始宽度（Y方向）
        private double _objectOriginalHeight;  // 物体原始高度（Z方向）

        /// <summary>
        /// 物体原始长度（X方向，米）- 选择物体时保存，不受旋转影响
        /// </summary>
        public double ObjectOriginalLength => _objectOriginalLength;

        /// <summary>
        /// 物体原始宽度（Y方向，米）- 选择物体时保存，不受旋转影响
        /// </summary>
        public double ObjectOriginalWidth => _objectOriginalWidth;

        /// <summary>
        /// 物体原始高度（Z方向，米）- 选择物体时保存，不受旋转影响
        /// </summary>
        public double ObjectOriginalHeight => _objectOriginalHeight;

        // 手工碰撞对象相关字段
        private bool _isManualCollisionTargetEnabled = true;
        private ObservableCollection<ManualCollisionTargetViewModel> _manualCollisionTargets;
        private string _manualCollisionTargetSummary = "未指定碰撞检测对象";
        private DateTime? _manualTargetsLastSyncTime;
        private const int ManualCollisionTargetLimit = 60;
        private const string ManualTargetsHighlightCategory = ModelHighlightHelper.ManualTargetsCategory;
        private const string CollisionResultsHighlightCategory = ModelHighlightHelper.PrecomputeCollisionResultsCategory;

        // 检测排除对象相关字段
        private ObservableCollection<ExcludedObjectViewModel> _excludedObjects;
        private string _excludedObjectSummary = "未指定排除对象";
        private const string ExcludedObjectsHighlightCategory = "excludedObjects"; // 与ModelHighlightHelper中定义的绿色类别一致

        #endregion

        #region 公共属性


        /// <summary>
        /// 可用帧率集合
        /// </summary>
        public ObservableCollection<int> AvailableFrameRates
        {
            get => _availableFrameRates;
            set => SetProperty(ref _availableFrameRates, value);
        }

        /// <summary>
        /// 选中的帧率
        /// </summary>
        public int SelectedFrameRate
        {
            get => _selectedFrameRate;
            set
            {
                if (SetProperty(ref _selectedFrameRate, value))
                {
                    // 同步更新AnimationFrameRate字段
                    _animationFrameRate = value;

                    // 帧率变化时，步长需要重新计算（步长=路径长度/(帧率*时长)）
                    OnPropertyChanged(nameof(CollisionDetectionAccuracy));
                }
            }
        }

        /// <summary>
        /// 动画持续时间（秒）
        /// </summary>
        public double AnimationDuration
        {
            get => _animationDuration;
            set
            {
                if (SetProperty(ref _animationDuration, value))
                {
                    // 动画时长变化时，步长和速度需要重新计算
                    OnPropertyChanged(nameof(CollisionDetectionAccuracy));
                    OnPropertyChanged(nameof(MovementSpeed));
                }
            }
        }


        /// <summary>
        /// 是否可以开始动画
        /// </summary>
        public bool CanStartAnimation
        {
            get => _canStartAnimation;
            set => SetProperty(ref _canStartAnimation, value);
        }

        /// <summary>
        /// 是否可以暂停动画
        /// </summary>
        public bool CanPauseAnimation
        {
            get => _canPauseAnimation;
            set => SetProperty(ref _canPauseAnimation, value);
        }

        /// <summary>
        /// 是否可以停止动画
        /// </summary>
        public bool CanStopAnimation
        {
            get => _canStopAnimation;
            set => SetProperty(ref _canStopAnimation, value);
        }


        /// <summary>
        /// 是否有碰撞检测结果
        /// </summary>
        private bool _hasClashDetectiveResults;
        public bool HasClashDetectiveResults
        {
            get => _hasClashDetectiveResults;
            set
            {
                if (SetProperty(ref _hasClashDetectiveResults, value))
                {
                    System.Windows.Input.CommandManager.InvalidateRequerySuggested();
                }
            }
        }

        /// <summary>
        /// 最近一次自动生成的碰撞报告。
        /// 仅用于测试自动化读取结果，不参与业务流程分支。
        /// </summary>
        public CollisionReportResult LastGeneratedReport => _lastGeneratedReport;

        public bool HasGeneratedCollisionReport => _lastGeneratedReport != null;

        /// <summary>
        /// 使上次生成的碰撞报告失效。
        /// 测试服务在开始新动画前调用，避免跨测试/跨路径报告残留（HasGeneratedCollisionReport 依赖此字段）。
        /// </summary>
        public void InvalidateLastGeneratedReport()
        {
            _lastGeneratedReport = null;
        }



        /// <summary>
        /// 碰撞检测精度(米) - 步长，根据路径长度、帧率和时长计算得出
        /// 如果无法计算则返回0，表示数据不完整
        /// </summary>
        public double CollisionDetectionAccuracy
        {
            get
            {
                var pathLength = GetPathLength();
                if (pathLength > 0 && _animationFrameRate > 0 && _animationDuration > 0)
                {
                    // 步长 = 路径长度 / (帧率 * 时长)
                    return Math.Round(pathLength / (_animationFrameRate * _animationDuration), 3);
                }

                // 无法计算时返回0
                // 注意：如果路径为空(pathLength==0)，这是正常的初始状态，不输出警告
                // 只有在配置参数异常时才输出警告
                if (pathLength > 0 && (_animationFrameRate <= 0 || _animationDuration <= 0))
                {
                    LogManager.Warning($"[AnimationControlViewModel] 无法计算碰撞检测精度 - 路径长度:{pathLength}m, 帧率:{_animationFrameRate}fps, 时长:{_animationDuration}s");
                }
                return 0;
            }
        }

        /// <summary>
        /// 运动速度(米/秒) - 根据路径长度和时长计算得出
        /// 如果无法计算则返回0，表示数据不完整
        /// </summary>
        public double MovementSpeed
        {
            get
            {
                var pathLength = GetPathLength();
                if (pathLength > 0 && _animationDuration > 0)
                {
                    // 速度 = 路径长度 / 时长
                    return Math.Round(pathLength / _animationDuration, 2);
                }

                // 无法计算时返回0
                // 注意：如果路径为空(pathLength==0)，这是正常的初始状态，不输出警告
                // 只有在配置参数异常时才输出警告
                if (pathLength > 0 && _animationDuration <= 0)
                {
                    LogManager.Warning($"[AnimationControlViewModel] 无法计算运动速度 - 路径长度:{pathLength}m, 时长:{_animationDuration}s");
                }
                return 0;
            }
        }
        /// <summary>
        /// 路径长度(米) - 计算得出
        /// </summary>
        public double PathLength
        {
            get
            {
                return GetPathLength();
            }
        }

        /// <summary>
        /// 动画帧率(FPS)
        /// </summary>
        public int AnimationFrameRate
        {
            get => _animationFrameRate;
            set
            {
                if (SetProperty(ref _animationFrameRate, value))
                {
                    // 帧率变化时，步长需要重新计算
                    OnPropertyChanged(nameof(CollisionDetectionAccuracy));
                }
            }
        }

        /// <summary>
        /// 检测容差(米)
        /// </summary>
        public double DetectionTolerance
        {
            get => _detectionTolerance;
            set
            {
                if (SetProperty(ref _detectionTolerance, value))
                {
                    // 容差变化时，碰撞检测精度需要重新计算
                    OnPropertyChanged(nameof(CollisionDetectionAccuracy));
                }
            }
        }


        /// <summary>
        /// 当前选中的路径
        /// </summary>
        public PathRouteViewModel CurrentPathRoute
        {
            get => _currentPathRoute;
            set
            {
                var oldRoute = _currentPathRoute;
                if (SetProperty(ref _currentPathRoute, value))
                {
                    // 路径改变时，步长、速度和路径长度需要重新计算
                    OnPropertyChanged(nameof(CollisionDetectionAccuracy));
                    OnPropertyChanged(nameof(MovementSpeed));
                    OnPropertyChanged(nameof(PathLength));
                    UpdateCanGenerateAnimation();

                    // 路径改变时，刷新ClashDetective结果列表
                    RefreshClashDetectiveResultsList();
                    
                    // 🔥 路径改变时，如果已选择物体或虚拟物体，立即移动到新路径起点
                    // 注意：只有路径真正改变时才移动（避免初始设置null时的不必要移动）
                    if (value != null && oldRoute != value)
                    {
                        // 保留 lift（物体姿态不变，离地高度保持不变）。
                        // 保留 _autoAabbSx 缓存（物理尺寸与路径方向无关）。
                        // 保留 _objectRotationCorrection（物体姿态不变）。
                        MoveAnimatedObjectToPathStart();
                        // 用现有缓存刷新路径可视化和通行空间，不走 CalculateRotatedDimensions
                        UpdatePassageSpaceVisualization();
                    }
                }
            }
        }

        /// <summary>
        /// 选中的移动物体
        /// </summary>
        public ModelItem SelectedAnimatedObject
        {
            get => _selectedAnimatedObject;
            set
            {
                if (SetProperty(ref _selectedAnimatedObject, value))
                {
                    UpdateAnimatedObjectInfo();
                    UpdateCanGenerateAnimation();

                    // 预计算排除列表（异步）
                    _ = PrecomputeCollisionExclusionsAsync(value);

                    // 当选择了物体时，根据路径类型打开通行空间可视化
                    if (value != null)
                    {
                        UpdatePassageSpaceVisualization();
                        
                        // 🔥 立即将物体移动到路径起点（如果路径已选择）
                        MoveAnimatedObjectToPathStart();
                    }
                }
            }
        }

        /// <summary>
        /// 选中的移动物体名称
        /// </summary>
        public string SelectedAnimatedObjectName
        {
            get => _selectedAnimatedObjectName;
            set => SetProperty(ref _selectedAnimatedObjectName, value);
        }

        /// <summary>
        /// 是否已选择移动物体
        /// </summary>
        public bool HasSelectedAnimatedObject
        {
            get => _hasSelectedAnimatedObject;
            set => SetProperty(ref _hasSelectedAnimatedObject, value);
        }

        /// <summary>
        /// 是否可以生成动画
        /// </summary>
        public bool CanGenerateAnimation
        {
            get => _canGenerateAnimation;
            set => SetProperty(ref _canGenerateAnimation, value);
        }

        #region 虚拟物体属性

        /// <summary>
        /// 是否使用选择的模型物体
        /// </summary>
        /// <summary>
        /// 是否使用虚拟物体
        /// </summary>
        public bool UseVirtualObject
        {
            get => _useVirtualObject;
            set
            {
                if (SetProperty(ref _useVirtualObject, value))
                {
                    // ✨ 模式切换清理：切换到虚拟物体模式时，移除选择物体的动画数据
                    if (value)
                    {
                        try
                        {
                            // 🔥 将之前选择的实体物体归位（如果存在）
                            // 注意：使用 SelectedAnimatedObject 而不是 _pathAnimationManager.AnimatedObject
                            // 因为用户可能只选择了物体但还没有生成动画
                            if (_pathAnimationManager != null &&
                                SelectedAnimatedObject != null &&
                                !VirtualObjectManager.Instance.IsVirtualObjectActive)
                            {
                                // 先将实体物体设置到动画管理器，然后归位
                                _pathAnimationManager.SetAnimatedObject(SelectedAnimatedObject);
                                _pathAnimationManager.RestoreObjectToCADPosition();
                                _pathAnimationManager.ClearAnimationResults(); // 清理手动选择物体留下的动画数据
                                LogManager.Info("已切换到虚拟物体模式，将之前的手动选择物体归位并清理动画数据");
                            }

                            // 显示虚拟物体
                            LogManager.Info("正在显示虚拟物体...");
                            VirtualObjectManager.Instance.ShowVirtualObject(
                                VirtualObjectLengthInMeters, VirtualObjectWidthInMeters, VirtualObjectHeightInMeters);

                            // 重置角度修正值（虚拟物体重新选择时重置）
                            ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
                            LogManager.Debug("[切换虚拟物体] 已重置角度修正值为0");
                            
                            // 当切换到虚拟物体模式时，根据路径类型打开通行空间可视化
                            UpdatePassageSpaceVisualization();
                            
                            // 设置已选择物体状态（虚拟物体也算已选择）
                            HasSelectedAnimatedObject = true;
                            
                            // 🔥 立即将虚拟物体移动到路径起点（如果路径已选择）
                            MoveAnimatedObjectToPathStart();
                        }
                        catch (Exception ex)
                        {
                            LogManager.Warning($"切换到虚拟物体模式失败: {ex.Message}");
                        }
                    }
                    else
                    {
                        // ✨ 模式切换清理：切换到手动选择模式时，隐藏虚拟物体并清理数据
                        try
                        {
                            // 🔥 先将虚拟物体归位（如果之前被移动过）
                            if (_pathAnimationManager != null &&
                                VirtualObjectManager.Instance.IsVirtualObjectActive)
                            {
                                var metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
                                _pathAnimationManager.SetVirtualObjectParameters(true, 
                                    VirtualObjectLengthInMeters * metersToUnits, 
                                    VirtualObjectWidthInMeters * metersToUnits, 
                                    VirtualObjectHeightInMeters * metersToUnits);
                                _pathAnimationManager.RestoreObjectToCADPosition();
                                LogManager.Info("已切换到手动选择模式，虚拟物体已归位");
                            }
                            
                            // 隐藏虚拟物体
                            VirtualObjectManager.Instance.HideVirtualObject();
                            _pathAnimationManager?.ClearAnimationResults(); // 清理虚拟物体留下的动画数据
                            LogManager.Info("已切换到手动选择模式，自动隐藏虚拟物体并清理动画数据");
                            
                            // 🔥 如果已选择实体物体且路径已选择，将实体物体移动到路径起点
                            if (SelectedAnimatedObject != null && CurrentPathRoute != null)
                            {
                                MoveAnimatedObjectToPathStart();
                            }
                        }
                        catch (Exception ex)
                        {
                            LogManager.Warning($"隐藏虚拟物体失败: {ex.Message}");
                        }
                    }
                    UpdateCanGenerateAnimation();
                }
            }
        }

        /// <summary>
        /// 虚拟物体长度（米）- 只读，从路径编辑同步
        /// </summary>
        public double VirtualObjectLengthInMeters
        {
            get => _virtualObjectLengthInMeters;
            private set => SetProperty(ref _virtualObjectLengthInMeters, value);
        }

        /// <summary>
        /// 虚拟物体宽度（米）- 只读，从路径编辑同步
        /// </summary>
        public double VirtualObjectWidthInMeters
        {
            get => _virtualObjectWidthInMeters;
            private set => SetProperty(ref _virtualObjectWidthInMeters, value);
        }

        /// <summary>
        /// 虚拟物体高度（米）- 只读，从路径编辑同步
        /// </summary>
        public double VirtualObjectHeightInMeters
        {
            get => _virtualObjectHeightInMeters;
            private set => SetProperty(ref _virtualObjectHeightInMeters, value);
        }

        /// <summary>
        /// 物体绕宿主 X/Y/Z 轴的角度修正。
        /// </summary>
        public LocalEulerRotationCorrection ObjectRotationCorrection
        {
            get => _objectRotationCorrection;
            set
            {
                if (SetProperty(ref _objectRotationCorrection, value))
                {
                    // 手动改角度时清空自动调整/贴合地面的缓存尺寸，
                    // 让 UpdatePassageSpaceVisualization 走 CalculateRotatedDimensions 重新计算。
                    // 贴地/自动调整直接设字段不经过 setter，不受影响。
                    _autoAabbSx = null;
                    _autoAabbSy = null;
                    _autoAabbSz = null;
                    // 角度修正值改变时，更新物体朝向和通行空间
                    UpdateObjectRotation();
                }
            }
        }

        /// <summary>
        /// 设置虚拟物体参数（由主ViewModel调用）
        /// 参数单位：米
        /// </summary>
        public void SetVirtualObjectParameters(double length, double width, double height, double safetyMarginInMeters)
        {
            LogManager.Info($"[AnimationControlViewModel] SetVirtualObjectParameters被调用: length={length:F4}, width={width:F4}, height={height:F4}, safetyMarginInMeters={safetyMarginInMeters:F4}");
            VirtualObjectLengthInMeters = length;
            VirtualObjectWidthInMeters = width;
            VirtualObjectHeightInMeters = height;
            _safetyMarginInMeters = safetyMarginInMeters;

            LogManager.Info($"[AnimationControlViewModel] 虚拟物体参数已更新: {length:F1}米 × {width:F1}米 × {height:F1}米, 检测间隙: {safetyMarginInMeters:F2}米, _safetyMarginInMeters={_safetyMarginInMeters:F4}");

            // 如果当前使用虚拟物体模式，更新生成动画的可用状态
            if (UseVirtualObject)
            {
                UpdateCanGenerateAnimation();
                // 更新通行空间可视化
                UpdatePassageSpaceVisualization();
            }
        }

        #endregion

        #region 手工碰撞对象属性

        public bool IsManualCollisionTargetEnabled
        {
            get => _isManualCollisionTargetEnabled;
            set
            {
                if (SetProperty(ref _isManualCollisionTargetEnabled, value))
                {
                    HandleManualCollisionModeChanged();
                }
            }
        }

        public ObservableCollection<ManualCollisionTargetViewModel> ManualCollisionTargets => _manualCollisionTargets;

        public string ManualCollisionTargetSummary
        {
            get => _manualCollisionTargetSummary;
            set => SetProperty(ref _manualCollisionTargetSummary, value);
        }

        public bool HasManualCollisionTargets => _manualCollisionTargets?.Count > 0;

        public bool IsManualTargetModeActive => IsManualCollisionTargetEnabled && HasManualCollisionTargets;

        // 检测排除对象公共属性
        public ObservableCollection<ExcludedObjectViewModel> ExcludedObjects => _excludedObjects;

        public string ExcludedObjectSummary
        {
            get => _excludedObjectSummary;
            set => SetProperty(ref _excludedObjectSummary, value);
        }

        public bool HasExcludedObjects => _excludedObjects?.Count > 0;

        #endregion

        #region ClashDetective结果管理

        private ObservableCollection<ClashDetectiveResultViewModel> _clashDetectiveResults = new ObservableCollection<ClashDetectiveResultViewModel>();
        private ClashDetectiveResultViewModel _selectedClashDetectiveResult;

        public ObservableCollection<ClashDetectiveResultViewModel> ClashDetectiveResults => _clashDetectiveResults;

        public ClashDetectiveResultViewModel SelectedClashDetectiveResult
        {
            get => _selectedClashDetectiveResult;
            set
            {
                if (_selectedClashDetectiveResult != value)
                {
                    // 切换前恢复动画物体状态
                    if (_selectedClashDetectiveResult != null)
                    {
                        RestoreAnimatedObjectFromCollisionView();
                    }
                    
                    _selectedClashDetectiveResult = value;
                    OnPropertyChanged();
                    
                    // 触发滚动事件，让View滚动到选中的项
                    if (_selectedClashDetectiveResult != null)
                    {
                        RequestScrollToClashDetectiveResult?.Invoke(this, _selectedClashDetectiveResult);
                    }
                }
            }
        }

        public ICommand RefreshClashDetectiveResultsCommand { get; private set; }

        #endregion

        

        #region 媒体控制属性

        /// <summary>
        /// 时间轴位置（0-总帧数）
        /// </summary>
        public double TimelinePosition
        {
            get => _pathAnimationManager?.CurrentFrame ?? 0;
            set
            {
                if (_pathAnimationManager != null)
                {
                    _pathAnimationManager.SeekToFrame((int)Math.Round(value));
                }
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// 时间轴持续时间（总帧数）
        /// </summary>
        public double TimelineDuration => _pathAnimationManager?.TotalFrames - 1 ?? 0;

        /// <summary>
        /// 当前时间显示（格式化）
        /// </summary>
        public string CurrentTimeDisplay
        {
            get
            {
                if (_pathAnimationManager == null) return "00:00";

                var currentSeconds = (_pathAnimationManager.CurrentFrame / (double)(_pathAnimationManager.TotalFrames - 1)) * _animationDuration;
                return FormatTime(currentSeconds);
            }
        }

        /// <summary>
        /// 总时长显示（格式化）
        /// </summary>
        public string TotalTimeDisplay => FormatTime(_animationDuration);

        /// <summary>
        /// 帧信息显示
        /// </summary>
        public string FrameInfoDisplay
        {
            get
            {
                if (_pathAnimationManager == null) return "0/0";
                return $"{_pathAnimationManager.CurrentFrame + 1}/{_pathAnimationManager.TotalFrames}";
            }
        }

        /// <summary>
        /// 是否正在正向播放
        /// </summary>
        public bool IsPlayingForward => _pathAnimationManager?.PlaybackDirection == 1 &&
                                       _pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;

        /// <summary>
        /// 是否正在反向播放
        /// </summary>
        public bool IsPlayingReverse => _pathAnimationManager?.PlaybackDirection == -1 &&
                                       _pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;

        #endregion


        #endregion

        #region 命令

        // 原有命令
        public ICommand StartAnimationCommand { get; private set; }
        public ICommand PauseAnimationCommand { get; private set; }
        public ICommand StopAnimationCommand { get; private set; }
        public ICommand ViewCollisionReportCommand { get; private set; }
        public ICommand SelectAnimatedObjectCommand { get; private set; }
        public ICommand ClearAnimatedObjectCommand { get; private set; }
        public ICommand EditObjectRotationCommand { get; private set; }
        public ICommand GenerateAnimationCommand { get; private set; }
        public ICommand AddToBatchQueueCommand { get; private set; }
        public ICommand ApplyManualTargetsFromSelectionCommand { get; private set; }
        public ICommand ClearManualTargetsCommand { get; private set; }
        public ICommand RemoveManualTargetCommand { get; private set; }
        public ICommand HighlightManualTargetsCommand { get; private set; }
        public ICommand ClearManualHighlightsCommand { get; private set; }
        
        // 检测排除对象命令
        public ICommand AddExcludedObjectsFromSelectionCommand { get; private set; }
        public ICommand ClearExcludedObjectsCommand { get; private set; }
        public ICommand RemoveExcludedObjectCommand { get; private set; }
        public ICommand HighlightAllExcludedObjectsCommand { get; private set; }
        public ICommand ClearExcludedHighlightCommand { get; private set; }
        public ICommand HighlightPrecomputedCollisionResultsCommand { get; private set; }
        public ICommand ClearPrecomputedCollisionHighlightsCommand { get; private set; }
        public ICommand ToggleWireframeModeCommand { get; private set; }
        public ICommand HighlightClashDetectiveResultsCommand { get; private set; }
        public ICommand ClearClashDetectiveHighlightsCommand { get; private set; }

        // 媒体控制命令
        public ICommand PlayForwardCommand { get; private set; }
        public ICommand PlayReverseCommand { get; private set; }
        public ICommand StepForwardCommand { get; private set; }
        public ICommand StepBackwardCommand { get; private set; }
        public ICommand FastForwardCommand { get; private set; }
        public ICommand FastBackwardCommand { get; private set; }
        public ICommand SeekToStartCommand { get; private set; }
        public ICommand SeekToEndCommand { get; private set; }

        #endregion

        #region 构造函数

        public AnimationControlViewModel() : this(null)
        {
            // 调用带参数的构造函数，传入null
        }

        /// <summary>
        /// 带主ViewModel参数的构造函数，支持统一状态栏
        /// </summary>
        /// <param name="mainViewModel">主ViewModel，用于统一状态栏</param>
        public AnimationControlViewModel(LogisticsControlViewModel mainViewModel) : base()
        {
            try
            {
                // 设置主ViewModel引用到基类
                SetMainViewModel(mainViewModel);

                // 初始化管理器 - 使用单例模式确保与GenerateCollisionReportCommand使用同一实例
                _pathAnimationManager = NavisworksTransport.Core.Animation.PathAnimationManager.GetInstance();
                _clashIntegration = ClashDetectiveIntegration.Instance;
                _uiStateManager = UIStateManager.Instance;

                // 订阅PathAnimationManager事件
                _pathAnimationManager.ProgressChanged += OnAnimationProgressChanged;
                _pathAnimationManager.StateChanged += OnAnimationStateChanged;

                // 初始化集合
                AvailableFrameRates = new ThreadSafeObservableCollection<int>();
                _manualCollisionTargets = new ObservableCollection<ManualCollisionTargetViewModel>();
                _manualCollisionTargets.CollectionChanged += OnManualCollisionTargetsChanged;
                UpdateManualCollisionTargetSummary();

                // 初始化检测排除对象集合
                _excludedObjects = new ObservableCollection<ExcludedObjectViewModel>();
                _excludedObjects.CollectionChanged += OnExcludedObjectsChanged;
                UpdateExcludedObjectSummary();

                // 初始化设置
                InitializeAnimationSettings();
                
                // 初始化生成动画按钮状态（启用手工指定模式时检查列表）
                UpdateCanGenerateAnimation();

                // 初始化命令
                InitializeCommands();

                // 订阅碰撞检测事件
                _clashIntegration.CollisionDetected += OnCollisionDetected;
                _clashIntegration.ClashDetectiveResultSaved += OnClashDetectiveResultSaved;

                // 订阅文档状态事件
                DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated;
                DocumentStateManager.Instance.DocumentReady += OnDocumentReady;

                // 🔥 如果文档已经有效，立即刷新碰撞检测历史列表
                if (DocumentStateManager.Instance.IsDocumentValid)
                {
                    RefreshClashDetectiveResultsList();
                    LogManager.Info("[AnimationControlViewModel] 文档已有效，初始化时加载碰撞检测历史列表");
                }

                // 设置静态实例
                _instance = this;

                LogManager.Info("AnimationControlViewModel初始化完成 - 支持统一状态栏");
            }
            catch (Exception ex)
            {
                LogManager.Error($"AnimationControlViewModel初始化失败: {ex.Message}", ex);
                UpdateMainStatus("初始化失败，请检查日志");
                throw;
            }
        }

        #endregion

        #region 公共方法

        /// <summary>
        /// 设置PathPlanningManager（用于批处理队列功能）
        /// </summary>
        public void SetPathPlanningManager(PathPlanningManager pathPlanningManager)
        {
            _pathPlanningManager = pathPlanningManager;
            LogManager.Info("AnimationControlViewModel已设置PathPlanningManager");
        }

        #endregion

        #region 初始化方法

        /// <summary>
        /// 初始化动画设置
        /// </summary>
        private void InitializeAnimationSettings()
        {
            // 从配置加载动画参数
            var config = NavisworksTransport.Core.Config.ConfigManager.Instance.Current;

            // 初始化可用帧率
            AvailableFrameRates.Clear();
            var frameRates = new[] { 15, 24, 30, 60 };
            foreach (var rate in frameRates)
            {
                AvailableFrameRates.Add(rate);
            }

            // 从配置读取帧率
            SelectedFrameRate = config.Animation.FrameRate;

            // 从配置读取动画持续时间
            AnimationDuration = config.Animation.DurationSeconds;

            // 设置初始状态 - 默认状态未激活，等待有效动画
            CanPauseAnimation = false;
            CanStopAnimation = false;

            // 初始化碰撞检测状态
            HasClashDetectiveResults = false;
            UpdateMainStatus("碰撞检测就绪");

            // 从配置读取安全间隙（用于预计算）- 使用米单位接口
            _safetyMarginInMeters = config.PathEditing.SafetyMarginMeters;
            AnimationFrameRate = config.Animation.FrameRate;

            // 从配置读取检测容差（用于ClashDetective）- 使用米单位接口用于显示
            _detectionTolerance = config.Animation.DetectionToleranceMeters;

            // 🔥 从配置加载虚拟物体尺寸（与路径编辑保持一致，使用米单位接口）
            VirtualObjectLengthInMeters = config.PathEditing.ObjectLengthMeters;
            VirtualObjectWidthInMeters = config.PathEditing.ObjectWidthMeters;
            VirtualObjectHeightInMeters = config.PathEditing.ObjectHeightMeters;

            // 设置动画按钮的初始状态
            UpdateAnimationButtonStates();

            LogManager.Info($"动画设置初始化完成 - 帧率:{SelectedFrameRate}fps, 持续时间:{AnimationDuration}秒, 安全间隙:{_safetyMarginInMeters}米");

            // 订阅配置变更事件
            SubscribeToConfigChanges();
        }

        #region 配置变更处理

        /// <summary>
        /// 订阅配置变更事件
        /// </summary>
        private void SubscribeToConfigChanges()
        {
            try
            {
                ConfigManager.Instance.CategoryConfigurationChanged += OnCategoryConfigurationChanged;
                LogManager.Info("AnimationControlViewModel 已订阅配置变更事件");
            }
            catch (Exception ex)
            {
                LogManager.Error($"订阅配置变更事件失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 取消订阅配置变更事件
        /// </summary>
        private void UnsubscribeFromConfigChanges()
        {
            try
            {
                ConfigManager.Instance.CategoryConfigurationChanged -= OnCategoryConfigurationChanged;
                LogManager.Info("AnimationControlViewModel 已取消订阅配置变更事件");
            }
            catch (Exception ex)
            {
                LogManager.Warning($"取消订阅配置变更事件时发生异常: {ex.Message}");
            }
        }

        /// <summary>
        /// 配置变更事件处理
        /// </summary>
        private void OnCategoryConfigurationChanged(object sender, Tuple<ConfigCategory, ConfigurationChangedEventArgs> args)
        {
            try
            {
                var category = args.Item1;
                var eventArgs = args.Item2;

                // 处理 Animation 类别变更
                if (category == ConfigCategory.Animation || category == ConfigCategory.All)
                {
                    LogManager.Info("收到 Animation 配置变更通知，正在更新参数...");

                    var config = ConfigManager.Instance.Current;

                    // 更新动画参数
                    SelectedFrameRate = config.Animation.FrameRate;
                    AnimationDuration = config.Animation.DurationSeconds;
                    AnimationFrameRate = config.Animation.FrameRate;
                    _detectionTolerance = config.Animation.DetectionToleranceMeters;

                    LogManager.Info($"Animation 配置已更新 - 帧率:{SelectedFrameRate}fps, 持续时间:{AnimationDuration}秒");
                }

                // 处理 PathEditing 类别变更（物体参数和安全间隙）
                if (category == ConfigCategory.PathEditing || category == ConfigCategory.All)
                {
                    LogManager.Info("收到 PathEditing 配置变更通知，正在更新物体参数...");

                    var config = ConfigManager.Instance.Current;

                    // 更新物体参数
                    VirtualObjectLengthInMeters = config.PathEditing.ObjectLengthMeters;
                    VirtualObjectWidthInMeters = config.PathEditing.ObjectWidthMeters;
                    VirtualObjectHeightInMeters = config.PathEditing.ObjectHeightMeters;
                    _safetyMarginInMeters = config.PathEditing.SafetyMarginMeters;

                    LogManager.Info($"物体参数已更新 - 尺寸:{VirtualObjectLengthInMeters:F1}x{VirtualObjectWidthInMeters:F1}x{VirtualObjectHeightInMeters:F1}米, 安全间隙:{_safetyMarginInMeters:F2}米");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"处理配置变更事件失败: {ex.Message}", ex);
            }
        }

        #endregion

        /// <summary>
        /// 初始化命令
        /// </summary>
        private void InitializeCommands()
        {
            StartAnimationCommand = new RelayCommand(async () => await ExecuteStartAnimationAsync(), () => CanStartAnimation);
            PauseAnimationCommand = new RelayCommand(async () => await ExecutePauseAnimationAsync(), () => CanPauseAnimation);
            StopAnimationCommand = new RelayCommand(async () => await ExecuteStopAnimationAsync(), () => CanStopAnimation);
            ViewCollisionReportCommand = new RelayCommand(async () => await ExecuteViewCollisionReport(), () => HasClashDetectiveResults);
            SelectAnimatedObjectCommand = new RelayCommand(() =>
{
    // 重置角度修正值（每个物体的旋转独立）
    ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
    LogManager.Debug("[选择物体] 已重置角度修正值为0");
    ExecuteSelectAnimatedObject();
});
            ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
            EditObjectRotationCommand = new RelayCommand(ExecuteEditObjectRotation, () => HasSelectedAnimatedObject);
            GenerateAnimationCommand = new RelayCommand(ExecuteGenerateAnimation, () => CanGenerateAnimation);
            AddToBatchQueueCommand = new RelayCommand(ExecuteAddToBatchQueue, () => CanGenerateAnimation);
            ApplyManualTargetsFromSelectionCommand = new RelayCommand(ExecuteApplyManualTargetsFromSelection);
            ClearManualTargetsCommand = new RelayCommand(ExecuteClearManualTargets, () => HasManualCollisionTargets);
            RemoveManualTargetCommand = new RelayCommand<ManualCollisionTargetViewModel>(ExecuteRemoveManualTarget, target => target != null);
            HighlightManualTargetsCommand = new RelayCommand(ExecuteHighlightManualTargets, () => HasManualCollisionTargets);
            ClearManualHighlightsCommand = new RelayCommand(ExecuteClearManualHighlights, () => HasManualCollisionTargets);
            
            // 检测排除对象命令
            AddExcludedObjectsFromSelectionCommand = new RelayCommand(ExecuteAddExcludedObjectsFromSelection);
            ClearExcludedObjectsCommand = new RelayCommand(ExecuteClearExcludedObjects, () => HasExcludedObjects);
            RemoveExcludedObjectCommand = new RelayCommand<ExcludedObjectViewModel>(ExecuteRemoveExcludedObject, obj => obj != null);
            HighlightAllExcludedObjectsCommand = new RelayCommand(ExecuteHighlightAllExcludedObjects, () => HasExcludedObjects);
            ClearExcludedHighlightCommand = new RelayCommand(ExecuteClearExcludedHighlight, () => HasExcludedObjects);
            HighlightPrecomputedCollisionResultsCommand = new RelayCommand(ExecuteHighlightPrecomputedCollisionResults, () => HasClashDetectiveResults);
            ClearPrecomputedCollisionHighlightsCommand = new RelayCommand(ExecuteClearPrecomputedCollisionHighlights, () => HasClashDetectiveResults);
            ToggleWireframeModeCommand = new RelayCommand(ExecuteToggleWireframeMode);
            HighlightClashDetectiveResultsCommand = new RelayCommand(ExecuteHighlightClashDetectiveResults, () => HasClashDetectiveResults);
            ClearClashDetectiveHighlightsCommand = new RelayCommand(ExecuteClearClashDetectiveHighlights, () => HasClashDetectiveResults);
            RefreshClashDetectiveResultsCommand = new RelayCommand(ExecuteRefreshClashDetectiveResults);
            PlayForwardCommand = new RelayCommand(ExecutePlayForward, CanExecuteMediaCommands);
            PlayReverseCommand = new RelayCommand(ExecutePlayReverse, CanExecuteMediaCommands);
            StepForwardCommand = new RelayCommand(ExecuteStepForward, CanExecuteMediaCommands);
            StepBackwardCommand = new RelayCommand(ExecuteStepBackward, CanExecuteMediaCommands);
            FastForwardCommand = new RelayCommand(ExecuteFastForward, CanExecuteMediaCommands);
            FastBackwardCommand = new RelayCommand(ExecuteFastBackward, CanExecuteMediaCommands);
            SeekToStartCommand = new RelayCommand(ExecuteSeekToStart, CanExecuteMediaCommands);
            SeekToEndCommand = new RelayCommand(ExecuteSeekToEnd, CanExecuteMediaCommands);

            LogManager.Info("动画控制命令初始化完成");
        }


        #endregion

        #region 命令实现

        /// <summary>
        /// 开始动画命令
        /// </summary>
        private async Task ExecuteStartAnimationAsync()
        {
            if (!CanStartAnimation || CurrentPathRoute == null || CurrentPathRoute.Points.Count < 2)
            {
                await _uiStateManager.ExecuteUIUpdateAsync(() =>
                {
                    UpdateMainStatus("请先选择包含至少2个点的路径");
                });
                return;
            }

            try
            {
                // 根据PathAnimationManager的状态决定操作
                if (_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Paused)
                {
                    // 从暂停状态恢复
                    _pathAnimationManager.ResumeAnimation();
                    LogManager.Info("从暂停状态恢复动画播放");
                    return; // 恢复动画后直接返回，不再执行StartAnimation
                }
                else if (_pathAnimationManager.IsAnimating)
                {
                    LogManager.Warning("动画已在播放中");
                    await _uiStateManager.ExecuteUIUpdateAsync(() =>
                    {
                        UpdateMainStatus("动画已在播放中");
                        CanStartAnimation = true;
                        CanPauseAnimation = false;
                        CanStopAnimation = false;
                    });
                    return;
                }

                // 开始播放物体移动动画
                // 清理之前的碰撞缓存
                _pathAnimationManager?.ClearExclusionCache();
                LogManager.Info("已清理UI层碰撞检测缓存");

                // 清理之前的碰撞高亮
                ModelHighlightHelper.ClearCollisionHighlights();
                LogManager.Info("已清理之前的碰撞高亮");

                // 首先重置进度（开始全新动画时）
                _pathAnimationManager.StartAnimation();

                LogManager.Info($"开始物体移动动画播放: {CurrentPathRoute.Name}, 帧率: {SelectedFrameRate}fps");
            }
            catch (Exception ex)
            {
                await _uiStateManager.ExecuteUIUpdateAsync(() =>
                {
                    UpdateMainStatus($"开始动画出错: {ex.Message}");
                    LogManager.Error($"开始动画异常: {ex.Message}", ex);

                    // 恢复按钮状态
                    CanStartAnimation = true;
                    CanPauseAnimation = false;
                    CanStopAnimation = false;
                });
            }
        }

        /// <summary>
        /// 暂停动画命令
        /// </summary>
        private async Task ExecutePauseAnimationAsync()
        {
            try
            {
                // 调用PathAnimationManager暂停动画
                // UI状态会通过OnAnimationStateChanged事件自动更新
                _pathAnimationManager.PauseAnimation();

                LogManager.Info("暂停动画播放");
            }
            catch (Exception ex)
            {
                LogManager.Error($"暂停动画异常: {ex.Message}", ex);

                // 错误时手动恢复UI状态
                await _uiStateManager.ExecuteUIUpdateAsync(() =>
                {
                    UpdateMainStatus("暂停动画失败");
                });
            }
        }

        /// <summary>
        /// 停止动画命令
        /// </summary>
        private async Task ExecuteStopAnimationAsync()
        {
            try
            {
                await _uiStateManager.ExecuteUIUpdateAsync(() =>
                {
                    UpdateMainStatus("动画已停止");

                    CanStartAnimation = true;
                    CanPauseAnimation = false;
                    CanStopAnimation = false;

                    LogManager.Info("停止动画播放");
                });

                // 停止当前动画
                _pathAnimationManager.CancelAnimation();
            }
            catch (Exception ex)
            {
                LogManager.Error($"停止动画异常: {ex.Message}", ex);
            }
        }


        /// <summary>
        /// 查看碰撞报告命令（显示已缓存的报告）
        /// </summary>
        private async Task ExecuteViewCollisionReport()
        {
            if (!HasClashDetectiveResults)
            {
                UpdateMainStatus("尚无碰撞检测结果可查看");
                return;
            }

            try
            {
                // 检查是否有已缓存的报告
                if (_lastGeneratedReport == null)
                {
                    UpdateMainStatus("报告尚未生成，请等待碰撞检测完成");
                    LogManager.Warning("尝试查看报告但缓存为空");
                    return;
                }

                UpdateMainStatus("正在显示碰撞报告...", -1, true);
                LogManager.Info("开始显示已缓存的碰撞报告");

                // 在UI线程上显示缓存的报告
                await _uiStateManager.ExecuteUIUpdateAsync(() =>
                {
                    try
                    {
                        // 使用WPF碰撞报告对话框直接显示缓存的报告
                        var reportDialog = NavisworksTransport.UI.WPF.Views.CollisionReportDialog.ShowReport(_lastGeneratedReport);

                        if (reportDialog != null)
                        {
                            UpdateMainStatus("碰撞报告已显示");
                            LogManager.Info("成功显示缓存的碰撞报告");
                        }
                        else
                        {
                            UpdateMainStatus("显示报告对话框失败");
                            LogManager.Error("CollisionReportDialog.ShowReport返回null");
                        }
                    }
                    catch (Exception dialogEx)
                    {
                        UpdateMainStatus($"显示报告失败: {dialogEx.Message}");
                        LogManager.Error($"显示WPF报告对话框失败: {dialogEx.Message}");
                    }
                });
            }
            catch (Exception ex)
            {
                UpdateMainStatus("查看碰撞报告出错");
                LogManager.Error($"查看碰撞报告异常: {ex.Message}", ex);
            }
        }

        #region 媒体控制命令实现

        /// <summary>
        /// 检查媒体控制命令是否可执行
        /// </summary>
        private bool CanExecuteMediaCommands()
        {
            return _pathAnimationManager != null &&
                   _pathAnimationManager.CurrentState != NavisworksTransport.Core.Animation.AnimationState.Idle &&
                   _pathAnimationManager.TotalFrames > 0;
        }

        /// <summary>
        /// 执行正向播放命令
        /// </summary>
        private void ExecutePlayForward()
        {
            try
            {
                _pathAnimationManager?.PlayForward();
                UpdateMediaControlProperties();
                LogManager.Info("执行正向播放命令");
            }
            catch (Exception ex)
            {
                LogManager.Error($"正向播放命令执行失败: {ex.Message}");
                UpdateMainStatus("正向播放失败");
            }
        }

        /// <summary>
        /// 执行反向播放命令
        /// </summary>
        private void ExecutePlayReverse()
        {
            try
            {
                _pathAnimationManager?.PlayReverse();
                UpdateMediaControlProperties();
                LogManager.Info("执行反向播放命令");
            }
            catch (Exception ex)
            {
                LogManager.Error($"反向播放命令执行失败: {ex.Message}");
                UpdateMainStatus("反向播放失败");
            }
        }

        /// <summary>
        /// 执行单帧前进命令
        /// </summary>
        private void ExecuteStepForward()
        {
            try
            {
                _pathAnimationManager?.StepForward();
                UpdateMediaControlProperties();
                LogManager.Debug("执行单帧前进命令");
            }
            catch (Exception ex)
            {
                LogManager.Error($"单帧前进命令执行失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 执行单帧后退命令
        /// </summary>
        private void ExecuteStepBackward()
        {
            try
            {
                _pathAnimationManager?.StepBackward();
                UpdateMediaControlProperties();
                LogManager.Debug("执行单帧后退命令");
            }
            catch (Exception ex)
            {
                LogManager.Error($"单帧后退命令执行失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 执行快进10帧命令
        /// </summary>
        private void ExecuteFastForward()
        {
            try
            {
                _pathAnimationManager?.FastForward();
                UpdateMediaControlProperties();
                LogManager.Debug("执行快进10帧命令");
            }
            catch (Exception ex)
            {
                LogManager.Error($"快进命令执行失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 执行快退10帧命令
        /// </summary>
        private void ExecuteFastBackward()
        {
            try
            {
                _pathAnimationManager?.FastBackward();
                UpdateMediaControlProperties();
                LogManager.Debug("执行快退10帧命令");
            }
            catch (Exception ex)
            {
                LogManager.Error($"快退命令执行失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 执行跳转到开头命令
        /// </summary>
        private void ExecuteSeekToStart()
        {
            try
            {
                _pathAnimationManager?.SeekToFrame(0);
                UpdateMediaControlProperties();
                LogManager.Info("跳转到动画开头");
            }
            catch (Exception ex)
            {
                LogManager.Error($"跳转到开头失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 执行跳转到结尾命令
        /// </summary>
        private void ExecuteSeekToEnd()
        {
            try
            {
                int lastFrame = (_pathAnimationManager?.TotalFrames ?? 1) - 1;
                _pathAnimationManager?.SeekToFrame(lastFrame);
                UpdateMediaControlProperties();
                LogManager.Info("跳转到动画结尾");
            }
            catch (Exception ex)
            {
                LogManager.Error($"跳转到结尾失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 执行选择移动物体命令
        /// </summary>
        private void ExecuteSelectAnimatedObject()
        {
            try
            {
                var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
                if (doc == null || doc.CurrentSelection.IsEmpty)
                {
                    LogManager.Warning("请先在场景中选择一个物体");
                    return;
                }

                var newObject = doc.CurrentSelection.SelectedItems.First;
                if (newObject == null) return;

                // 1. 保存旧物体引用，在设置新物体之前
                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. 选择实体物体意味着切换到实体模式，避免后续起点同步仍走虚拟物体分支。
                if (UseVirtualObject)
                {
                    UseVirtualObject = false;
                    LogManager.Debug("[选择物体] 已切换到实体物体模式");
                }

                // 4. 对“新物体”先清空角度修正，再触发 SelectedAnimatedObject 的起点落位。
                //    SelectedAnimatedObject 的 setter 会立即触发 MoveAnimatedObjectToPathStart()，
                //    如果角度在后面才归零，就会先按旧角度把新选择的物体摆到起点。
                if (!isSameObject)
                {
                    _pathAnimationManager?.SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection.Zero);
                    _pathAnimationManager?.SetObjectStartVerticalLiftDirect(0.0);
                    ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
                    _objectGroundLiftHeightInMeters = 0.0;
                    _autoAabbSx = null; _autoAabbSy = null; _autoAabbSz = null;
                    LogManager.Debug("[选择物体] 已重置角度修正值为0（新物体）");
                }
                else
                {
                    LogManager.Debug("[选择物体] 保持当前角度修正值（同一物体）");
                }

                // 5. 先注册到动画管理器，再设置 SelectedAnimatedObject。
                // SelectedAnimatedObject 的 setter 会立即触发 MoveAnimatedObjectToPathStart()；
                // 如果先移动、后 SetAnimatedObject，会把“已摆到起点后的当前姿态”再次当成参考姿态缓存。
                _pathAnimationManager?.SetAnimatedObject(newObject);
                SelectedAnimatedObject = newObject;
                LogManager.Info($"已选择移动物体: {SelectedAnimatedObject.DisplayName}");

            }
            catch (Exception ex)
            {
                LogManager.Error($"选择物体失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 更新媒体控制相关属性
        /// </summary>
        private void UpdateMediaControlProperties()
        {
            OnPropertyChanged(nameof(TimelinePosition));
            OnPropertyChanged(nameof(TimelineDuration));
            OnPropertyChanged(nameof(CurrentTimeDisplay));
            OnPropertyChanged(nameof(FrameInfoDisplay));
            OnPropertyChanged(nameof(IsPlayingForward));
            OnPropertyChanged(nameof(IsPlayingReverse));
        }

        /// <summary>
        /// 格式化时间显示
        /// </summary>
        private string FormatTime(double seconds)
        {
            var timeSpan = TimeSpan.FromSeconds(Math.Max(0, seconds));
            return $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
        }

        #endregion

        #endregion

        #region 辅助方法

        /// <summary>
        /// 设置当前路径
        /// </summary>
        public void SetCurrentPath(PathRouteViewModel pathRoute)
        {
            // 🔥 先同步路径到 PathAnimationManager（在设置 CurrentPathRoute 之前）
            // 避免 CurrentPathRoute setter 中调用 MoveAnimatedObjectToPathStart 时 _route 还未设置
            if (pathRoute != null && _pathAnimationManager != null)
            {
                var coreRoute = pathRoute.Route;
                if (coreRoute != null)
                {
                    _pathAnimationManager.SetRoute(coreRoute);
                    LogManager.Info($"[AnimationControlViewModel] 已同步路径到 PathAnimationManager: {coreRoute.Name}");
                }
            }

            CurrentPathRoute = pathRoute;

            // 添加调试日志
            if (pathRoute != null)
            {
                LogManager.Info($"[AnimationControlViewModel] 设置当前路径: 名称='{pathRoute.Name}', ID='{pathRoute.Id}', 点数={pathRoute.Points?.Count ?? 0}");
            }
            else
            {
                LogManager.Info("[AnimationControlViewModel] 清空当前路径");
            }

            // 路径改变时清除已生成的动画（如果有的话）
            if (_pathAnimationManager != null &&
                (_pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Ready ||
                 _pathAnimationManager.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Finished))
            {
                try
                {
                    _pathAnimationManager.CancelAnimation(); // 这会将状态设置为Stopped，需要重新生成动画
                    LogManager.Info("路径更改，已清除之前生成的动画");
                }
                catch (Exception ex)
                {
                    LogManager.Warning($"清除之前动画时出现警告: {ex.Message}");
                }
            }

            // 路径改变时，步长、速度和路径长度需要重新计算
            OnPropertyChanged(nameof(CollisionDetectionAccuracy));
            OnPropertyChanged(nameof(MovementSpeed));
            OnPropertyChanged(nameof(PathLength));

            // 按钮状态更新
            UpdateAnimationButtonStates();

            // 更新碰撞状态
            if (pathRoute == null)
            {
                UpdateMainStatus("请选择路径");
            }
            else if (pathRoute.Points.Count < 2)
            {
                UpdateMainStatus("路径点数不足");
            }
            else
            {
                UpdateMainStatus("碰撞检测就绪");
            }

            LogManager.Info($"AnimationControlViewModel当前路径更新: {pathRoute?.Name ?? "无"}, 点数: {pathRoute?.Points.Count ?? 0}");
        }

        /// <summary>
        /// 处理动画进度变化事件
        /// 使用Dispatcher更新UI
        /// </summary>
        private void OnAnimationProgressChanged(object sender, double progressPercent)
        {
            System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                // 更新时间轴相关属性
                OnPropertyChanged(nameof(TimelinePosition));
                OnPropertyChanged(nameof(CurrentTimeDisplay));
                OnPropertyChanged(nameof(FrameInfoDisplay));
            }));
        }

        /// <summary>
        /// 处理动画状态变化事件
        /// </summary>
        private void OnAnimationStateChanged(object sender, Core.Animation.AnimationState state)
        {
            System.Windows.Application.Current.Dispatcher.BeginInvoke(new Func<Task>(async () =>
            {
                switch (state)
                {
                    case Core.Animation.AnimationState.Playing:
                        CanStartAnimation = false;
                        CanPauseAnimation = true;
                        CanStopAnimation = true;
                        UpdateMainStatus("动画播放中");
                        UpdateMediaControlProperties(); // 更新媒体控制属性
                        break;
                    case Core.Animation.AnimationState.Paused:
                        CanStartAnimation = true;  // 暂停状态下可以继续
                        CanPauseAnimation = false;
                        CanStopAnimation = true;
                        UpdateMainStatus("动画已暂停");
                        UpdateMediaControlProperties(); // 更新媒体控制属性
                        break;
                    case Core.Animation.AnimationState.Finished:
                        CanStartAnimation = true;
                        CanPauseAnimation = false;
                        CanStopAnimation = false;
                        UpdateMainStatus("动画已完成");
                        UpdateMediaControlProperties(); // 更新媒体控制属性
                        
                        // 检查检测是否被取消
                        if (_clashIntegration?.WasLastTestCanceled == true)
                        {
                            LogManager.Info("[动画完成] 检测已被取消，跳过报告生成和高亮");
                            // 重置取消标志
                            _clashIntegration.ClearWasLastTestCanceled();
                            break;
                        }

                        ModelHighlightHelper.ClearCollisionHighlights();
                        await GenerateAndSaveCollisionReport();
                        HighlightClashDetectiveResults();
                        break;
                    case NavisworksTransport.Core.Animation.AnimationState.Stopped:
                        CanStartAnimation = true;
                        CanPauseAnimation = false;
                        CanStopAnimation = false;
                        UpdateMainStatus("动画已停止");
                        UpdateMediaControlProperties(); // 更新媒体控制属性
                        // 先清除所有碰撞高亮（包括预计算和ClashDetective）
                        ModelHighlightHelper.ClearCollisionHighlights();
                        // 注意：Stopped状态不进行碰撞检测（例如切换路径时）
                        break;
                    default:
                        UpdateMainStatus($"动画状态: {state}");
                        UpdateMediaControlProperties(); // 更新媒体控制属性
                        break;
                }
            }));
        }

        /// <summary>
        /// 生成并保存碰撞报告（无论有无碰撞都生成）
        /// </summary>
        private async Task GenerateAndSaveCollisionReport()
        {
            try
            {
                var animationManager = Core.Animation.PathAnimationManager.GetInstance();
                var hasAnimationCollisions = animationManager?.AllCollisionResults?.Count > 0;

                // 统一使用GenerateCollisionReportCommand生成报告（无论有无碰撞）
                UpdateMainStatus($"正在生成碰撞报告...", -1, true);
                
                var generateReportCommand = NavisworksTransport.Commands.GenerateCollisionReportCommand.CreateComprehensive();
                
                // 设置检测记录ID（优先使用ID查询，更可靠）
                var detectionRecordId = _pathAnimationManager?.CurrentDetectionRecordId;
                generateReportCommand.SetDetectionRecordId(detectionRecordId);
                LogManager.Info($"生成碰撞报告，使用检测记录ID: {detectionRecordId?.ToString() ?? "null"}");
                
                var result = await generateReportCommand.ExecuteAsync();

                if (result.IsSuccess && result is PathPlanningResult<CollisionReportResult> reportResult)
                {
                    // 缓存报告结果，供后续查看使用
                    _lastGeneratedReport = reportResult.Data;

                    // 保存到数据库
                    await SaveCollisionReportToDatabase(reportResult.Data);

                    var collisionCount = reportResult.Data.TotalCollisions;
                    if (collisionCount > 0)
                    {
                        UpdateMainStatus($"✅ 碰撞报告已生成并保存：{collisionCount} 个碰撞");
                        LogManager.Info("碰撞报告已自动生成并保存到数据库");
                    }
                    else
                    {
                        UpdateMainStatus($"✅ 碰撞报告已生成并保存：0 个碰撞");
                        LogManager.Info("碰撞报告已自动生成并保存到数据库（无碰撞）");
                    }
                }
                else
                {
                    UpdateMainStatus($"碰撞报告生成失败: {result.ErrorMessage}");
                    LogManager.Error($"自动生成碰撞报告失败: {result.ErrorMessage}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"生成碰撞报告失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 高亮ClashDetective结果
        /// </summary>
        private void HighlightClashDetectiveResults()
        {
            try
            {
                // 检查测试名称是否有效，如果为空可能是检测被跳过（配置相同）
                if (string.IsNullOrEmpty(_clashIntegration?.CurrentTestName))
                {
                    LogManager.Info("当前没有ClashDetective测试（可能配置相同已跳过检测），从历史记录加载");
                    // 使用当前检测记录ID查询数据库获取TestName
                    var detectionRecordId = _pathAnimationManager?.CurrentDetectionRecordId;
                    if (detectionRecordId.HasValue)
                    {
                        var pathDatabase = _pathPlanningManager?.GetPathDatabase();
                        var record = pathDatabase?.GetDetectionResultById(detectionRecordId.Value);
                        if (record != null && !string.IsNullOrEmpty(record.TestName))
                        {
                            // 设置到ClashDetectiveIntegration以便后续使用
                            _clashIntegration.SetCurrentTestName(record.TestName);
                            LogManager.Info($"已从数据库加载TestName: {record.TestName}");
                        }
                    }
                }
                
                // 再次检查测试名称
                if (string.IsNullOrEmpty(_clashIntegration?.CurrentTestName))
                {
                    LogManager.Info("无法获取ClashDetective测试名称，跳过高亮");
                    HasClashDetectiveResults = false;
                    return;
                }

                // 检查是否有当前测试的ClashDetective结果
                var clashResults = _clashIntegration.GetCurrentTestResults();

                // 统一处理：无论有无碰撞都高亮（有碰撞时高亮碰撞对象，无碰撞时无操作）
                if (clashResults != null && clashResults.Count > 0)
                {
                    ModelHighlightHelper.HighlightClashDetectiveResults(_clashIntegration.CurrentTestName, _clashIntegration.GetCurrentPathClashResults);
                    HasClashDetectiveResults = true;
                    LogManager.Info($"动画结束，已高亮ClashDetective检测结果：{clashResults.Count}个碰撞");
                }
                else
                {
                    HasClashDetectiveResults = false;
                }
                
                // 插入步骤：如果没有碰撞，显示祝贺窗口
                if (clashResults == null || clashResults.Count == 0)
                {
                    LogManager.Info("🎉 恭喜！本次动画未检测到任何碰撞，路径规划非常安全！");
                    DialogHelper.ShowAutoClosingMessageBox(
                        "🎉 恭喜！本次动画仿真过程中未发现任何碰撞！\n\n您的物流路径规划非常成功，物体可以安全通行。",
                        "仿真完成 - 无碰撞",
                        System.Windows.MessageBoxImage.Information,
                        autoCloseMilliseconds: 4000);
                }
                
                // 统一流程：刷新历史列表并显示报告（无论有无碰撞）
                LogManager.Info($"[HighlightClashDetectiveResults] 刷新历史列表，当前路径: {CurrentPathRoute?.Name}");
                RefreshClashDetectiveResultsList();
                if (_lastGeneratedReport != null)
                {
                    NavisworksTransport.UI.WPF.Views.CollisionReportDialog.ShowReport(_lastGeneratedReport);
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"高亮ClashDetective结果失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 处理ClashDetective的碰撞检测事件 - 已废弃，报告生成统一在CheckAndHighlightClashDetectiveResults中处理
        /// </summary>
        private void OnCollisionDetected(object sender, CollisionDetectedEventArgs e)
        {
            // 报告生成已统一在CheckAndHighlightClashDetectiveResults中处理
            // 此事件保留用于日志记录和未来扩展
            try
            {
                var clashResults = _clashIntegration?.GetCurrentTestResults();
                var collisionCount = clashResults?.Count ?? 0;
                LogManager.Info($"[OnCollisionDetected] 碰撞检测事件触发，碰撞数量: {collisionCount}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"处理碰撞检测事件失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// ClashDetective结果保存事件处理
        /// </summary>
        private void OnClashDetectiveResultSaved(object sender, ClashDetectiveResultSavedEventArgs e)
        {
            try
            {
                // 刷新ClashDetective结果列表
                RefreshClashDetectiveResultsList();
                LogManager.Info($"ClashDetective结果已保存，列表已刷新: 路径={e.PathName}, 测试={e.TestName}, 碰撞数={e.CollisionCount}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"处理ClashDetective结果保存事件失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 保存碰撞报告到数据库
        /// </summary>
        private async Task SaveCollisionReportToDatabase(CollisionReportResult reportResult)
        {
            try
            {
                var pathPlanningManager = PathPlanningManager.Instance;
                var pathDatabase = pathPlanningManager?.GetPathDatabase();

                if (pathDatabase != null && reportResult != null)
                {
                    // 获取路由ID
                    string routeId = CurrentPathRoute?.Id ?? "";

                    // 注意：检测记录（包括排除列表）已在生成动画时保存到 CollisionDetectionRecords 表
                    // 这里只保存碰撞报告，不重复保存排除列表

                    // 从报告中获取所有需要的数据
                    await Task.Run(() =>
                    {
                        // 保存碰撞报告
                        pathDatabase.SaveCollisionReport(
                            routeId,
                            reportResult.PathName,
                            SelectedAnimatedObjectName ?? "未知对象",
                            reportResult.UniqueCollidedObjectsCount,
                            reportResult.FrameRate,
                            reportResult.Duration,
                            reportResult.DetectionTolerance,
                            reportResult.AnimationCollisions,
                            reportResult.TotalCollisions
                        );

                        // 关联排除对象到检测记录
                        if (reportResult.ResultId > 0 && _excludedObjects != null && _excludedObjects.Count > 0)
                        {
                            // 获取排除对象的记录ID
                            var excludedRecords = pathDatabase.GetExcludedObjects(routeId, includeGlobal: true);
                            var excludedObjectIds = excludedRecords.Select(r => r.Id).ToList();
                            pathDatabase.LinkExcludedObjectsToDetectionRecord(reportResult.ResultId, excludedObjectIds);
                            LogManager.Info($"[排除列表] 已关联 {excludedObjectIds.Count} 个排除对象到检测记录 (DetectionRecordId={reportResult.ResultId})");
                        }
                    });

                    LogManager.Info($"碰撞报告已保存到数据库 - 路径:{reportResult.PathName}, " +
                                  $"碰撞构件:{reportResult.UniqueCollidedObjectsCount}, " +
                                  $"动画碰撞:{reportResult.AnimationCollisions}, " +
                                  $"ClashDetective:{reportResult.TotalCollisions}, " +
                                  $"排除对象:{_excludedObjects?.Count ?? 0}");
                }
                else
                {
                    LogManager.Warning("无法获取PathDatabase实例或报告数据为空，碰撞报告未保存到数据库");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"保存碰撞报告到数据库失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 执行清除移动物体命令
        /// 清除移动物体选择、停止当前动画并更新按钮状态
        /// </summary>
        private void ExecuteClearAnimatedObject()
        {
            try
            {
                if (_pathAnimationManager != null)
                {
                    // 1. 归位并清理
                    _pathAnimationManager.RestoreObjectToCADPosition();
                    _pathAnimationManager.ClearAnimationResults();
                    _pathAnimationManager.SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection.Zero);
                    _pathAnimationManager.SetObjectStartVerticalLiftDirect(0.0);
                    _pathAnimationManager.SetObjectStartPlacementMode(ObjectStartPlacementMode.AlignToPathPose);
                    LogManager.Info("已清除PathAnimationManager中的动画数据并归位物体");
                }

                // 2. 先清空选择，避免角度修正归零时又触发“移动到起点”链路。
                SelectedAnimatedObject = null;

                // 3. 重置角度修正值为0
                ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
                _objectGroundLiftHeightInMeters = 0.0;
                LogManager.Debug("[清除物体] 已重置角度修正值为0");

                // 4. 重置属性
                UpdateAnimatedObjectInfo();
                UpdateCanGenerateAnimation();

                // 5. 清理高亮
                ModelHighlightHelper.ClearCollisionHighlights();

                LogManager.Info("移动物体已完全清除并归位");
                UpdateMainStatus("已清除物体选择并恢复位置");
            }
            catch (Exception ex)
            {
                LogManager.Error($"清除失败: {ex.Message}");
                UpdateMainStatus($"清除失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 统一的调整物体入口（第一步）：只设置调整结果（姿态修正 + 上下偏移），
        /// 然后自动触发路径对齐（第二步，各路径自己的处理）。
        /// 所有调整功能（角度输入/自动调整/贴合地面）都走此接口。
        /// </summary>
        private void ApplyObjectAdjustment(LocalEulerRotationCorrection correction, double liftInMeters)
        {
            _objectRotationCorrection = correction;
            _objectGroundLiftHeightInMeters = liftInMeters;
            OnPropertyChanged(nameof(ObjectRotationCorrection));

            if (_pathAnimationManager != null)
            {
                _pathAnimationManager.ApplyObjectStartPlacementRequest(
                    ObjectStartPlacementRequest.CreateRotationCorrection(correction, liftInMeters));
            }
        }

        /// <summary>
        /// 统一的平移模式入口：保持物体姿态，仅平移到路径起点（含上下偏移）。
        /// </summary>
        private void ApplyObjectTranslationOnly(double liftInMeters)
        {
            _objectGroundLiftHeightInMeters = liftInMeters;

            if (_pathAnimationManager != null)
            {
                _pathAnimationManager.ApplyObjectStartPlacementRequest(
                    ObjectStartPlacementRequest.CreateTranslationOnly(liftInMeters));
            }
        }

        /// <summary>
        /// 执行编辑物体角度修正
        /// </summary>
        private void ExecuteEditObjectRotation()
        {
            try
            {
                // 上下偏移：沿宿主 up 轴的垂直位移，与路径类型无关，对所有路径启用
                bool enableGroundLift = CurrentPathRoute != null;
                var autoAdjustmentRequest = CreateObjectPassageProjectionOptimizationRequest();
                LocalEulerRotationCorrection prefillRotation = _objectRotationCorrection;
                (double, double, double)? prefillAabb = null;

                ShowEditRotationDialog:
                var dialog = new Views.EditRotationWindow(
                    prefillRotation,
                    _objectGroundLiftHeightInMeters,
                    enableGroundLift,
                    autoAdjustmentRequest,
                    autoAdjustmentRequest == null
                        ? (Func<ObjectPassageProjectionOptimizationResult>)null
                        : (() => OptimizeObjectPassageProjectionByMeasuredAabb(autoAdjustmentRequest)),
                    alignToGroundProvider: HasSelectedAnimatedObject
                        ? (Func<Vector3, ObjectPassageProjectionOptimizationResult>)
                            (faceNormal => OptimizeAlignToGround(faceNormal))
                        : null);
                if (prefillAabb.HasValue)
                    dialog.AutoAdjustAabbSizes = prefillAabb;

                if (dialog.ShowDialog() == true)
                {
                    var placementRequest = dialog.AdjustmentRequest;
                    _objectGroundLiftHeightInMeters = placementRequest.VerticalLiftInMeters;
                    bool isAutoAdjusted = dialog.AutoAdjustAabbSizes.HasValue;

                    // 统一调整接口：平移模式保持姿态；其余设置姿态修正+上下偏移，
                    // 内部自动触发路径对齐（第二步，各路径自己的处理）
                    if (placementRequest.PreserveInitialPose)
                    {
                        ApplyObjectTranslationOnly(placementRequest.VerticalLiftInMeters);
                    }
                    else
                    {
                        ApplyObjectAdjustment(placementRequest.RotationCorrection, placementRequest.VerticalLiftInMeters);
                    }

                    // 手动改角度/自动调整后实测AABB缓存，与贴地一致（路径斜向时世界AABB不准）
                    var aabbAdapter = CoordinateSystemManager.Instance.CreateHostAdapter();
                    var aabbDoc = Autodesk.Navisworks.Api.Application.ActiveDocument;
                    var aabbItems = new ModelItemCollection { SelectedAnimatedObject };
                    MeasureXAlignedAabbAndSetCache(SelectedAnimatedObject, aabbItems, aabbAdapter, aabbDoc);

                    string liftSummary = enableGroundLift
                        ? $", 上下偏移={_objectGroundLiftHeightInMeters:F3}m"
                        : string.Empty;
                    string statusSummary = placementRequest.PreserveInitialPose
                        ? $"物体已调整为平移模式{liftSummary}"
                        : $"物体角度修正: {_objectRotationCorrection}{liftSummary}";

                    LogManager.Info(
                        $"物体调整已更新: 模式={placementRequest.PlacementMode}, 角度={_objectRotationCorrection}, " +
                        $"上下偏移={_objectGroundLiftHeightInMeters:F3}m");
                    UpdateMainStatus(statusSummary);

                    // 更新通行空间可视化（考虑旋转后的尺寸）
                    UpdatePassageSpaceVisualization();
                }
                else if (dialog.AlignToGroundRequested)
                {
                    // 读取对话框当前上下偏移值
                    _objectGroundLiftHeightInMeters = dialog.AdjustmentRequest.VerticalLiftInMeters;
                    FaceInferToolPlugin.FaceInferred += OnFaceInferredForAlignToGround;
                    FaceInferToolPlugin.Activate();
                    return;
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"编辑物体角度失败: {ex.Message}");
                UpdateMainStatus($"编辑物体角度失败: {ex.Message}");
            }
        }

        private void OnFaceInferredForAlignToGround(object sender, FaceInferResult result)
        {
            FaceInferToolPlugin.FaceInferred -= OnFaceInferredForAlignToGround;
            try { Autodesk.Navisworks.Api.Application.MainDocument.Tool.Value = Tool.Select; } catch { }

            if (!result.IsValid)
            {
                MessageBox.Show(result.FailureReason ?? "未能识别有效的面。", "贴合地面", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var faceNormal = new Vector3((float)result.Normal.X, (float)result.Normal.Y, (float)result.Normal.Z);
            ApplyAlignToGround(faceNormal);
        }

        /// <summary>
        /// 实际旋转物体到X轴方向测AABB，获取沿路径/垂直路径/高度的准确尺寸，再转回来。
        /// 路径斜向时世界AABB的X/Y/Z不对应语义尺寸，必须实际旋转物体用Navisworks API测量。
        /// 设置 _autoAabbSx=沿路径, _autoAabbSy=高度, _autoAabbSz=垂直路径。
        /// </summary>
        private void MeasureXAlignedAabbAndSetCache(ModelItem item, ModelItemCollection modelItems,
            HostCoordinateAdapter adapter, Autodesk.Navisworks.Api.Document doc)
        {
            if (CurrentPathRoute?.Points == null || CurrentPathRoute.Points.Count < 2) return;

            var pathStart = CurrentPathRoute.Points[0];
            var pathStartVec = new Vector3((float)pathStart.X, (float)pathStart.Y, (float)pathStart.Z);
            Vector3 pathFwd = Vector3.Zero;
            for (int i = 1; i < CurrentPathRoute.Points.Count; i++)
            {
                var p = CurrentPathRoute.Points[i];
                var c = new Vector3((float)p.X, (float)p.Y, (float)p.Z) - pathStartVec;
                if (c.LengthSquared() > 1e-8f) { pathFwd = c; break; }
            }
            if (pathFwd.LengthSquared() <= 1e-8f) return;

            Vector3 hostUp = adapter.HostUpVector3;
            Vector3 pathFwdH = pathFwd - hostUp * Vector3.Dot(pathFwd, hostUp);
            if (pathFwdH.LengthSquared() <= 1e-8f) return;
            pathFwdH = Vector3.Normalize(pathFwdH);

            Vector3 horizontalPerpX = Vector3.Normalize(Vector3.Cross(hostUp, Vector3.UnitX));
            float angleToX = (float)Math.Atan2(
                Vector3.Dot(pathFwdH, horizontalPerpX),
                Vector3.Dot(pathFwdH, Vector3.UnitX));

            // 实际旋转物体到X轴方向，测AABB，再转回来
            Quaternion yawToX = Quaternion.CreateFromAxisAngle(hostUp, -angleToX);
            var rotateToX = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)).Factor();
            rotateToX.Rotation = new Rotation3D(yawToX.X, yawToX.Y, yawToX.Z, yawToX.W);
            doc.Models.OverridePermanentTransform(modelItems, rotateToX.Combine(), false);

            BoundingBox3D xAlignedBounds = item.BoundingBox();
            int upIndex = adapter.HostUpAxisIndex;
            _autoAabbSx = xAlignedBounds.Max.X - xAlignedBounds.Min.X;
            _autoAabbSy = upIndex == 1 ? xAlignedBounds.Max.Y - xAlignedBounds.Min.Y : xAlignedBounds.Max.Z - xAlignedBounds.Min.Z;
            _autoAabbSz = upIndex == 1 ? xAlignedBounds.Max.Z - xAlignedBounds.Min.Z : xAlignedBounds.Max.Y - xAlignedBounds.Min.Y;

            // 转回来
            Quaternion yawBack = Quaternion.CreateFromAxisAngle(hostUp, angleToX);
            var rotateBack = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)).Factor();
            rotateBack.Rotation = new Rotation3D(yawBack.X, yawBack.Y, yawBack.Z, yawBack.W);
            doc.Models.OverridePermanentTransform(modelItems, rotateBack.Combine(), false);

            double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor();
            LogManager.Debug(
                $"[X轴对齐AABB] 沿路径={_autoAabbSx * unitsToMeters:F2}m, " +
                $"垂直路径={_autoAabbSz * unitsToMeters:F2}m, " +
                $"高度={_autoAabbSy * unitsToMeters:F2}m, angleToX={angleToX * 180.0 / Math.PI:F1}°");
        }

        private void ApplyAlignToGround(Vector3 faceNormal)
        {
            var item = UseVirtualObject
                ? VirtualObjectManager.Instance.CurrentVirtualObject
                : SelectedAnimatedObject;
            if (item == null) return;

            var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
            var modelItems = new ModelItemCollection { item };
            var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
            var geom = item.FindFirstGeometry() ?? item.Geometry;

            // 读路径起点世界变换（复位前）
            var psActive = geom?.ActiveTransform ?? item.Transform;
            var psRot = psActive.Factor().Rotation;
            var psQ = new Quaternion((float)psRot.A, (float)psRot.B, (float)psRot.C, (float)psRot.D);

            // 复位 CAD
            doc.Models.ResetPermanentTransform(modelItems);
            var cadActive = geom?.ActiveTransform ?? item.Transform;
            var cadRot = cadActive.Factor().Rotation;
            var cadQ = new Quaternion((float)cadRot.A, (float)cadRot.B, (float)cadRot.C, (float)cadRot.D);
            var localN = Vector3.Normalize(Vector3.Transform(Vector3.Normalize(faceNormal), Quaternion.Inverse(psQ)));
            var cadWorldN = Vector3.Normalize(Vector3.Transform(localN, cadQ));

            var t = -adapter.HostUpVector3;
            float d = Vector3.Dot(cadWorldN, t);
            Quaternion overrideQ;
            if (d > 0.9999f) overrideQ = Quaternion.Identity;
            else if (d < -0.9999f) overrideQ = Quaternion.CreateFromAxisAngle(Vector3.UnitX, (float)Math.PI);
            else overrideQ = Quaternion.CreateFromAxisAngle(Vector3.Normalize(Vector3.Cross(cadWorldN, t)), (float)Math.Acos(d));

            // 贴地后绕宿主 Up 搜索 yaw，使物体在路径方向的截面积最小
            // （与"自动调整"同目标，但贴地已固定俯仰/翻滚，只剩 yaw 单自由度）
            var routePoints = CurrentPathRoute?.Points;
            double? searchedYawDegreesForLog = null;
            double pathYawRadians = 0.0;
            bool hasPathYaw = false;
            // 搜索返回的语义尺寸（模型单位）：沿路径 / 垂直路径 / 沿 hostUp
            double searchedForwardExtent = 0.0;
            double searchedWidthAcrossPath = 0.0;
            double searchedHeightAlongUp = 0.0;
            bool hasSearchedExtents = false;
            // 贴合地面后绕宿主 Up 搜索 yaw 对齐路径方向：与路径类型无关，
            // 只要路径有方向（≥2 个点）就执行（纯垂直路径无水平方向时搜索自然跳过）
            if (routePoints != null && routePoints.Count >= 2)
            {
                var start = routePoints[0];
                var startVector = new Vector3((float)start.X, (float)start.Y, (float)start.Z);
                Vector3 hostForward = Vector3.Zero;
                for (int i = 1; i < routePoints.Count; i++)
                {
                    var point = routePoints[i];
                    var candidate = new Vector3((float)point.X, (float)point.Y, (float)point.Z) - startVector;
                    if (candidate.LengthSquared() > 1e-8f)
                    {
                        hostForward = candidate;
                        break;
                    }
                }
                if (hostForward.LengthSquared() > 1e-8f
                    && PathTargetFrameResolver.TryResolvePlanarHostYaw(
                        hostForward, CoordinateSystemManager.Instance.ResolvedType, out pathYawRadians))
                {
                    hasPathYaw = true;
                    double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
                    double sizeX, sizeY, sizeZ;
                    if (UseVirtualObject)
                    {
                        sizeX = VirtualObjectLengthInMeters * metersToUnits;
                        sizeY = VirtualObjectWidthInMeters * metersToUnits;
                        sizeZ = VirtualObjectHeightInMeters * metersToUnits;
                    }
                    else
                    {
                        sizeX = _objectOriginalLength * metersToUnits;
                        sizeY = _objectOriginalWidth * metersToUnits;
                        sizeZ = _objectOriginalHeight * metersToUnits;
                    }

                    Quaternion faceDownOnly = overrideQ;
                    overrideQ = AlignToGroundMinCrossSectionYawSearcher.Search(
                        faceDownOnly, cadQ, sizeX, sizeY, sizeZ, hostForward, adapter,
                        out searchedWidthAcrossPath, out searchedHeightAlongUp, out searchedForwardExtent);
                    hasSearchedExtents = true;

                    // 从结果反算实际 yaw 角度用于日志
                    Quaternion deltaQ = Quaternion.Normalize(
                        Quaternion.Inverse(faceDownOnly) * overrideQ);
                    float dot = Vector3.Dot(adapter.HostUpVector3,
                        new Vector3(deltaQ.X, deltaQ.Y, deltaQ.Z));
                    float angle = 2.0f * (float)Math.Acos(Math.Min(1.0, Math.Abs(deltaQ.W)));
                    searchedYawDegreesForLog = (dot >= 0 ? angle : -angle) * 180.0 / Math.PI;
                }
            }

            // 把绝对贴地姿态转成"相对于 baseline 的 host 欧拉修正"。
            // PathAnimationManager 增量链从 CAD 姿态出发，需要扣除 CAD 自带 yaw + pathYaw。
            LocalEulerRotationCorrection resultCorrection;
            if (hasPathYaw)
            {
                resultCorrection = AlignToGroundMinCrossSectionYawSearcher.ComposeHostCorrection(
                    overrideQ, cadQ, pathYawRadians, adapter);
            }
            else
            {
                // 无路径 yaw（纯垂直路径或无路径）：直接转 overrideQ，
                // 无 baseline 可扣除，correction 等价于绝对姿态。
                Quaternion correctionCanonQ = AlignToGroundMinCrossSectionYawSearcher.HostQuaternionToCanonical(
                    overrideQ, adapter);
                resultCorrection = ObjectPassageProjectionOptimizer.CanonicalQuaternionToHostEulerCorrection(
                    correctionCanonQ, adapter.HostType);
            }

            // 统一调整接口：设置姿态修正 + 上下偏移，自动触发路径对齐（第二步）
            ApplyObjectAdjustment(resultCorrection, _objectGroundLiftHeightInMeters);

            // 回写上下偏移：贴地时用实际包围盒贴地，但确认/动画后用 correction 重建姿态，
            // 旋转后物体底边位置会变。与自动调整同方法：应用 correction 重建→测量底边→算 lift，
            // 测量后用新 correction+lift 完整重建，使物体停留在贴地等价姿态。
            if (CurrentPathRoute?.PathType == PathType.Ground && CurrentPathRoute.Points?.Count > 0
                && _pathAnimationManager != null)
            {
                double computedLift = _pathAnimationManager.ComputeGroundLiftForCorrection(
                    resultCorrection, _objectGroundLiftHeightInMeters, item);
                _objectGroundLiftHeightInMeters = computedLift;
                _pathAnimationManager.SetObjectStartVerticalLiftDirect(_objectGroundLiftHeightInMeters);

                // 物体已在最终贴地姿态（ComputeGroundLiftForCorrection 重建完成）。
                // 路径可能斜向，实际旋转物体到X轴方向测AABB获取准确语义尺寸。
                if (hasSearchedExtents)
                {
                    MeasureXAlignedAabbAndSetCache(item, modelItems, adapter, doc);
                }
            }

            LogManager.Info(
                $"[贴合地面] faceNormal=({faceNormal.X:F4},{faceNormal.Y:F4},{faceNormal.Z:F4}) " +
                $"cadWorldN=({cadWorldN.X:F4},{cadWorldN.Y:F4},{cadWorldN.Z:F4}) t=({t.X:F4},{t.Y:F4},{t.Z:F4}) " +
                $"overrideQ=({overrideQ.X:F4},{overrideQ.Y:F4},{overrideQ.Z:F4},{overrideQ.W:F4}) " +
                $"searchedYaw={(searchedYawDegreesForLog.HasValue ? searchedYawDegreesForLog.Value.ToString("F2") : "N/A")}° " +
                $"correction={_objectRotationCorrection}");

            var liftSummary = CurrentPathRoute?.PathType == PathType.Ground
                ? $", 上下偏移={_objectGroundLiftHeightInMeters:F3}m"
                : string.Empty;
            UpdateMainStatus($"物体已贴合地面: {_objectRotationCorrection}{liftSummary}");
            UpdatePassageSpaceVisualization();
        }

        private ObjectPassageProjectionOptimizationResult OptimizeAlignToGround(Vector3 faceNormal)
        {
            if (_pathAnimationManager == null || !HasSelectedAnimatedObject)
                return ObjectPassageProjectionOptimizationResult.CreateFailure("未选择动画物体。");

            var request = CreateObjectPassageProjectionOptimizationRequest();
            if (request == null)
                return ObjectPassageProjectionOptimizationResult.CreateFailure("无法创建优化请求。");

            var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
            var hostUp = adapter.HostUpVector3;

            var hst = CoordinateSystemManager.Instance.ResolvedType;
            var hostSide = Vector3.Cross(Vector3.UnitX, hostUp);

            var originalCorrection = _objectRotationCorrection;
            double originalLiftInMeters = _objectGroundLiftHeightInMeters;
            ObjectPassageProjectionOptimizationResult result;

            try
            {
                // 取面在路径起点，校正从 CAD 出发。
                // 路径起点法向量 → 物体局部法向量 → CAD 世界法向量
                var item = UseVirtualObject
                    ? VirtualObjectManager.Instance.CurrentVirtualObject
                    : SelectedAnimatedObject;

                // 先记录路径起点的 ActiveTransform
                var psActiveTransform = item?.Transform;
                var psRot = psActiveTransform?.Factor().Rotation ?? Rotation3D.Identity;
                var psQ = new Quaternion((float)psRot.A, (float)psRot.B, (float)psRot.C, (float)psRot.D);
                var invPsQ = Quaternion.Inverse(psQ);

                // 复位到 CAD，读 CAD 原始变换
                var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
                var modelItems = new ModelItemCollection { item };
                doc.Models.ResetPermanentTransform(modelItems);
                var cadActiveTransform = item.Transform;
                var cadRot = cadActiveTransform.Factor().Rotation;
                var cadQ = new Quaternion((float)cadRot.A, (float)cadRot.B, (float)cadRot.C, (float)cadRot.D);

                // 世界→局部→CAD世界
                var localNormal = Vector3.Normalize(Vector3.Transform(Vector3.Normalize(faceNormal), invPsQ));
                var cadFaceNormal = Vector3.Normalize(Vector3.Transform(localNormal, cadQ));

                // CAD 空间算对齐
                var canonicalFaceNormal = adapter.ToCanonicalVector3(cadFaceNormal);
                var canonicalTargetDown = adapter.ToCanonicalVector3(-hostUp);
                float dot = Vector3.Dot(canonicalFaceNormal, canonicalTargetDown);
                Quaternion alignQuatCanonical;
                if (dot > 0.999f) alignQuatCanonical = Quaternion.Identity;
                else if (dot < -0.999f) alignQuatCanonical = Quaternion.CreateFromAxisAngle(Vector3.UnitX, (float)Math.PI);
                else
                {
                    var axis = Vector3.Normalize(Vector3.Cross(canonicalFaceNormal, canonicalTargetDown));
                    alignQuatCanonical = Quaternion.CreateFromAxisAngle(axis, (float)Math.Acos(dot));
                }

                var correction = ObjectPassageProjectionOptimizer.CanonicalQuaternionToHostEulerCorrection(alignQuatCanonical);
                LogManager.Info(
                    $"[贴合优化] faceNormal=({faceNormal.X:F4},{faceNormal.Y:F4},{faceNormal.Z:F4}), " +
                    $"canonFace=({canonicalFaceNormal.X:F4},{canonicalFaceNormal.Y:F4},{canonicalFaceNormal.Z:F4}), " +
                    $"canonTarget=({canonicalTargetDown.X:F4},{canonicalTargetDown.Y:F4},{canonicalTargetDown.Z:F4}), " +
                    $"dot={dot:F4}, alignQ=({alignQuatCanonical.X:F4},{alignQuatCanonical.Y:F4},{alignQuatCanonical.Z:F4},{alignQuatCanonical.W:F4}), " +
                    $"correction={correction}");
                result = ObjectPassageProjectionOptimizationResult.CreateSuccess(correction, new ObjectPassageProjectionScore(0, 0));
            }
            finally
            {
                var restoreRequest = ObjectStartPlacementRequest.CreateRotationCorrection(
                    originalCorrection, originalLiftInMeters);
                _pathAnimationManager.ApplyObjectStartPlacementRequest(restoreRequest);
            }

            return result;
        }

        private static bool IsBetterForAlign(
            ObjectPassageProjectionScore candidate,
            ObjectPassageProjectionScore currentBest,
            double areaRelativeTieTolerance,
            bool? requireWidthLarger)
        {
            if (double.IsInfinity(currentBest.Area)) return true;

            double tolerance = Math.Max(0.0, areaRelativeTieTolerance) * Math.Max(1.0, currentBest.Area);
            if (candidate.Area < currentBest.Area - tolerance) return true;

            if (Math.Abs(candidate.Area - currentBest.Area) <= tolerance)
            {
                if (requireWidthLarger.HasValue)
                {
                    bool cOk = requireWidthLarger.Value
                        ? candidate.WidthAcrossPath >= candidate.HeightAlongHostUp
                        : candidate.HeightAlongHostUp >= candidate.WidthAcrossPath;
                    bool bOk = requireWidthLarger.Value
                        ? currentBest.WidthAcrossPath >= currentBest.HeightAlongHostUp
                        : currentBest.HeightAlongHostUp >= currentBest.WidthAcrossPath;
                    if (cOk && !bOk) return true;
                    if (!cOk && bOk) return false;
                }
                if (candidate.HeightAlongHostUp < currentBest.HeightAlongHostUp - 1e-9) return true;
            }
            return false;
        }

        private ObjectPassageProjectionOptimizationResult OptimizeObjectPassageProjectionByMeasuredAabb(
            ObjectPassageProjectionOptimizationRequest request)
        {
            if (_pathAnimationManager == null || !HasSelectedAnimatedObject)
            {
                return ObjectPassageProjectionOptimizationResult.CreateFailure("未选择动画物体，无法自动调整。");
            }

            ModelItem animatedObject = UseVirtualObject
                ? VirtualObjectManager.Instance.CurrentVirtualObject
                : SelectedAnimatedObject;
            if (animatedObject == null)
            {
                return ObjectPassageProjectionOptimizationResult.CreateFailure("动画物体为空，无法自动调整。");
            }

            // 固定评估坐标系：前向=宿主X轴，截面=YZ平面
            CoordinateSystemType hst = CoordinateSystemManager.Instance.ResolvedType;
            var hostUp = hst == CoordinateSystemType.YUp ? Vector3.UnitY : Vector3.UnitZ;
            var hostSide = Vector3.Cross(Vector3.UnitX, hostUp);

            var originalCorrection = _objectRotationCorrection;
            double originalLiftInMeters = _objectGroundLiftHeightInMeters;
            ObjectPassageProjectionOptimizationResult result;

            _pathAnimationManager.RestoreObjectToCADPosition();
            try
            {
                // 第一轮：无约束评测，找到真实最小截面积
                result = ObjectPassageProjectionOptimizer.OptimizeWithEvaluator(
                    request,
                    correction =>
                    {
                        _pathAnimationManager.ApplyRotationCorrectionInPlace(correction);
                        BoundingBox3D bounds = animatedObject.BoundingBox();
                        return CalculateMeasuredAabbProjectionScore(bounds, hostSide, hostUp);
                    });

                // 检查约束：如果不满足，在等价姿态中重选
                if (result.Success)
                {
                    bool meetsConstraint = request.RequireWidthLarger
                        ? result.Score.WidthAcrossPath >= result.Score.HeightAlongHostUp
                        : result.Score.HeightAlongHostUp >= result.Score.WidthAcrossPath;
                    if (!meetsConstraint)
                    {
                        LogManager.Debug(
                            $"[自动调整] 最优截面不满足门型约束，带约束重新搜索");
                        result = ObjectPassageProjectionOptimizer.OptimizeWithEvaluator(
                            request,
                            correction =>
                            {
                                _pathAnimationManager.ApplyRotationCorrectionInPlace(correction);
                                BoundingBox3D bounds = animatedObject.BoundingBox();
                                var score = CalculateMeasuredAabbProjectionScore(bounds, hostSide, hostUp);
                                bool ok = request.RequireWidthLarger
                                    ? score.WidthAcrossPath >= score.HeightAlongHostUp
                                    : score.HeightAlongHostUp >= score.WidthAcrossPath;
                                return ok ? score : ObjectPassageProjectionScore.Invalid;
                            });
                    }
                }

            // 计算上下偏移：应用最优旋转后，使物体底面贴合物流车高度
            if (result.Success && CurrentPathRoute?.PathType == PathType.Ground)
            {
                var measureRequest = ObjectStartPlacementRequest.CreateRotationCorrection(result.Correction, 0);
                _pathAnimationManager.ApplyObjectStartPlacementRequest(measureRequest);
                BoundingBox3D measureBounds = animatedObject.BoundingBox();

                // 物体底面和地面在世界坐标系中的差值
                double objectBottomWorld = hst == CoordinateSystemType.YUp
                    ? measureBounds.Min.Y : measureBounds.Min.Z;
                Point3D pathStartPoint = new Point3D(
                    CurrentPathRoute.Points[0].X,
                    CurrentPathRoute.Points[0].Y,
                    CurrentPathRoute.Points[0].Z);
                double groundLevel = hst == CoordinateSystemType.YUp
                    ? pathStartPoint.Y : pathStartPoint.Z;

                // 需要的上下偏移 = (地面 + 车高) - 物体当前底面
                double neededLiftModelUnits = groundLevel + request.VehicleHeightModelUnits - objectBottomWorld;

                result = ObjectPassageProjectionOptimizationResult.CreateSuccess(
                    result.Correction, result.Score, neededLiftModelUnits,
                    measureBounds.Max.X - measureBounds.Min.X,
                    measureBounds.Max.Y - measureBounds.Min.Y,
                    measureBounds.Max.Z - measureBounds.Min.Z);

                LogManager.Debug(
                    $"[自动调整] 物体底面={objectBottomWorld:F3}, 地面={groundLevel:F3}, " +
                    $"车高={request.VehicleHeightModelUnits:F3}, 计算偏移={neededLiftModelUnits:F3} (模型单位)");
            }
            }
            finally
            {
                var restoreRequest = ObjectStartPlacementRequest.CreateRotationCorrection(
                    originalCorrection,
                    originalLiftInMeters);
                _pathAnimationManager.ApplyObjectStartPlacementRequest(restoreRequest);
            }

            if (result.Success)
            {
                LogManager.Debug(
                    $"[自动调整实测] 最优角度={result.Correction}, " +
                    $"实测截面宽度={result.Score.WidthAcrossPath:F3}, 高度={result.Score.HeightAlongHostUp:F3}, 面积={result.Score.Area:F3}");
            }

            return result;
        }

        private ObjectPassageProjectionOptimizationRequest CreateObjectPassageProjectionOptimizationRequest()
        {
            if (CurrentPathRoute?.Points == null || CurrentPathRoute.Points.Count < 2)
            {
                LogManager.Warning("[自动调整] 当前路径点不足，无法构造优化请求");
                return null;
            }

            if (!TryResolveStartPathDirection(out Vector3 hostPathForward))
            {
                LogManager.Warning("[自动调整] 路径起点方向退化，无法构造优化请求");
                return null;
            }

            double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
            double sizeX;
            double sizeY;
            double sizeZ;
            Quaternion baselineHostRotation = Quaternion.Identity;

            // 基线为路径方向 yaw（动画系统已旋转到该方向）
            var hostType = CoordinateSystemManager.Instance.ResolvedType;
            if (PathTargetFrameResolver.TryResolvePlanarHostYaw(hostPathForward, hostType, out double pathYawRad))
            {
                var upVec = hostType == CoordinateSystemType.YUp ? Vector3.UnitY : Vector3.UnitZ;
                baselineHostRotation = Quaternion.CreateFromAxisAngle(upVec, (float)pathYawRad);
                LogManager.Info($"[自动调整] 基线 yaw={pathYawRad * 180.0 / Math.PI:F1}°, 四元数=({baselineHostRotation.W:F4},{baselineHostRotation.X:F4},{baselineHostRotation.Y:F4},{baselineHostRotation.Z:F4})");
            }
            else
            {
                LogManager.Warning("[自动调整] 无法从路径方向计算 yaw，基线保持恒等");
            }

            if (UseVirtualObject)
            {
                sizeX = VirtualObjectLengthInMeters * metersToUnits;
                sizeY = VirtualObjectWidthInMeters * metersToUnits;
                sizeZ = VirtualObjectHeightInMeters * metersToUnits;
            }
            else if (SelectedAnimatedObject != null)
            {
                sizeX = _objectOriginalLength * metersToUnits;
                sizeY = _objectOriginalWidth * metersToUnits;
                sizeZ = _objectOriginalHeight * metersToUnits;
            }
            else
            {
                LogManager.Warning("[自动调整] 未选择动画物体，无法构造优化请求");
                return null;
            }

            var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
            return new ObjectPassageProjectionOptimizationRequest(
                sizeX,
                sizeY,
                sizeZ,
                hostPathForward,
                adapter.HostUpVector3,
                baselineHostRotation: baselineHostRotation);
        }

        private bool TryResolveStartPathDirection(out Vector3 hostPathForward)
        {
            hostPathForward = Vector3.Zero;

            if (CurrentPathRoute?.Points == null || CurrentPathRoute.Points.Count < 2)
            {
                return false;
            }

            var start = CurrentPathRoute.Points[0];
            var startVector = new Vector3((float)start.X, (float)start.Y, (float)start.Z);
            for (int i = 1; i < CurrentPathRoute.Points.Count; i++)
            {
                var point = CurrentPathRoute.Points[i];
                var candidate = new Vector3((float)point.X, (float)point.Y, (float)point.Z) - startVector;
                if (candidate.LengthSquared() > 1e-8f)
                {
                    hostPathForward = candidate;
                    return true;
                }
            }

            return false;
        }

        private static bool TryCreatePassageProjectionAxes(
            ObjectPassageProjectionOptimizationRequest request,
            out Vector3 hostSide,
            out Vector3 hostUp)
        {
            hostSide = Vector3.Zero;
            hostUp = Vector3.Zero;
            if (request == null || request.HostUp.LengthSquared() < 1e-8f)
            {
                return false;
            }

            hostUp = Vector3.Normalize(request.HostUp);
            Vector3 hostForward = request.HostPathForward - hostUp * Vector3.Dot(request.HostPathForward, hostUp);
            if (hostForward.LengthSquared() < 1e-8f)
            {
                return false;
            }

            hostForward = Vector3.Normalize(hostForward);
            hostSide = Vector3.Cross(hostForward, hostUp);
            if (hostSide.LengthSquared() < 1e-8f)
            {
                return false;
            }

            hostSide = Vector3.Normalize(hostSide);
            return true;
        }

        private static ObjectPassageProjectionScore CalculateMeasuredAabbProjectionScore(
            BoundingBox3D bounds,
            Vector3 hostSide,
            Vector3 hostUp)
        {
            double sizeX = bounds.Max.X - bounds.Min.X;
            double sizeY = bounds.Max.Y - bounds.Min.Y;
            double sizeZ = bounds.Max.Z - bounds.Min.Z;
            double width = ProjectAabbExtent(sizeX, sizeY, sizeZ, hostSide);
            double height = ProjectAabbExtent(sizeX, sizeY, sizeZ, hostUp);
            return new ObjectPassageProjectionScore(width, height);
        }

        private static double ProjectAabbExtent(
            double sizeX,
            double sizeY,
            double sizeZ,
            Vector3 targetAxis)
        {
            Vector3 normalized = Vector3.Normalize(targetAxis);
            return Math.Abs(normalized.X) * sizeX +
                   Math.Abs(normalized.Y) * sizeY +
                   Math.Abs(normalized.Z) * sizeZ;
        }

        private static Quaternion Rotation3DToHostQuaternion(Rotation3D rotation)
        {
            var linear = new Transform3D(rotation).Linear;
            var matrix = new Matrix4x4(
                (float)linear.Get(0, 0), (float)linear.Get(0, 1), (float)linear.Get(0, 2), 0f,
                (float)linear.Get(1, 0), (float)linear.Get(1, 1), (float)linear.Get(1, 2), 0f,
                (float)linear.Get(2, 0), (float)linear.Get(2, 1), (float)linear.Get(2, 2), 0f,
                0f, 0f, 0f, 1f);
            return Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(matrix));
        }

        private void ApplyTranslationOnlyObjectPlacement()
        {
            try
            {
                if (_pathAnimationManager == null || !HasSelectedAnimatedObject)
                {
                    return;
                }

                _objectRotationCorrection = LocalEulerRotationCorrection.Zero;
                _pathAnimationManager.SetObjectRotationCorrectionDirect(LocalEulerRotationCorrection.Zero);
                _pathAnimationManager.SetObjectStartVerticalLiftDirect(0.0);
                _pathAnimationManager.SetObjectStartPlacementMode(ObjectStartPlacementMode.PreserveInitialPose);
                OnPropertyChanged(nameof(ObjectRotationCorrection));

                if (_pathAnimationManager.MoveObjectToPathStartPreservingInitialPose())
                {
                    LogManager.Info("[角度修正] 已按终点原始位姿整体搬运到路径起点");
                    UpdateMainStatus("物体已按终点原始位姿平移到路径起点");
                }
                else
                {
                    LogManager.Warning("[角度修正] 平移模式未能将物体移动到路径起点");
                    UpdateMainStatus("平移模式执行失败");
                }

                UpdatePassageSpaceVisualization();
            }
            catch (Exception ex)
            {
                LogManager.Error($"平移到路径起点失败: {ex.Message}");
                UpdateMainStatus($"平移到路径起点失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 更新物体旋转（应用角度修正）
        /// </summary>
        private void UpdateObjectRotation()
        {
            try
            {
                if (_pathAnimationManager != null && HasSelectedAnimatedObject)
                {
                    // 将角度修正传递给动画管理器
                    _pathAnimationManager.SetObjectRotationCorrection(_objectRotationCorrection);
                    LogManager.Debug($"[角度修正] 已更新物体旋转: {_objectRotationCorrection}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"更新物体旋转失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 将动画对象（物体或虚拟物体）移动到当前路径的起点
        /// 用于在选择物体/虚拟物体或切换路径时立即定位到起点，方便用户预览和调整角度
        /// </summary>
        private void MoveAnimatedObjectToPathStart()
        {
            try
            {
                // 检查是否有路径
                if (CurrentPathRoute == null || CurrentPathRoute.Points == null || CurrentPathRoute.Points.Count < 2)
                {
                    LogManager.Debug("[移动到起点] 路径未选择或路径点不足，跳过移动");
                    return;
                }

                // 检查是否有动画对象（物体或虚拟物体）
                if (!HasSelectedAnimatedObject)
                {
                    LogManager.Debug("[移动到起点] 未选择动画对象，跳过移动");
                    return;
                }

                // 获取路径点
                var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();

                // 根据当前模式确定动画对象
                ModelItem animatedObject = null;
                if (UseVirtualObject)
                {
                    // 虚拟物体模式：获取当前虚拟物体
                    animatedObject = VirtualObjectManager.Instance.CurrentVirtualObject;
                    if (animatedObject == null)
                    {
                        LogManager.Debug("[移动到起点] 虚拟物体未创建，跳过移动");
                        return;
                    }
                    
                    // 设置虚拟物体参数到动画管理器（转换为模型单位）
                    var metersToUnitsMove = UnitsConverter.GetMetersToUnitsConversionFactor();
                    _pathAnimationManager?.SetVirtualObjectParameters(true, 
                        VirtualObjectLengthInMeters * metersToUnitsMove, 
                        VirtualObjectWidthInMeters * metersToUnitsMove, 
                        VirtualObjectHeightInMeters * metersToUnitsMove);
                }
                else
                {
                    // 实体物体模式：使用选中的物体
                    animatedObject = SelectedAnimatedObject;
                    if (animatedObject == null)
                    {
                        LogManager.Debug("[移动到起点] 选中物体为空，跳过移动");
                        return;
                    }
                }

                // 执行移动
                if (_pathAnimationManager != null && animatedObject != null)
                {
                    bool moved = _pathAnimationManager.MoveObjectToPathStart(animatedObject, pathPoints);
                    if (moved)
                    {
                        LogManager.Info($"[移动到起点] {(UseVirtualObject ? "虚拟物体" : "物体")} 已移动到路径起点");
                    }
                    else
                    {
                        LogManager.Warning($"[移动到起点] {(UseVirtualObject ? "虚拟物体" : "物体")} 移动失败（可能路径未设置）");
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Warning($"[移动到起点] 移动动画对象到路径起点失败: {ex.Message}");
            }
        }

        #region 手工碰撞对象命令

        private void ExecuteApplyManualTargetsFromSelection()
        {
            try
            {
                var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
                var selectedItems = doc?.CurrentSelection?.SelectedItems;

                if (selectedItems == null || selectedItems.Count == 0)
                {
                    UpdateMainStatus("请在Navisworks中选择需要检测的对象");
                    LogManager.Warning("[手工碰撞] 未选择任何对象");
                    return;
                }

                var toAdd = new List<ManualCollisionTargetViewModel>();
                foreach (ModelItem item in selectedItems)
                {
                    if (item == null)
                        continue;

                    if (!ModelItemAnalysisHelper.IsModelItemValid(item) || !HasGeometryRecursive(item))
                        continue;

                    if (ManualTargetExists(item))
                        continue;

                    var displayName = ModelItemAnalysisHelper.GetSafeDisplayName(item);
                    var modelPath = BuildModelPath(item);
                    toAdd.Add(new ManualCollisionTargetViewModel(item, displayName, modelPath));
                }

                if (toAdd.Count == 0)
                {
                    if (_manualCollisionTargets.Count >= ManualCollisionTargetLimit)
                    {
                        UpdateMainStatus($"手工目标数量已达上限({ManualCollisionTargetLimit})");
                    }
                    else
                    {
                        UpdateMainStatus("选中的对象已在列表中或不包含几何体");
                    }
                    return;
                }

                int availableSlots = ManualCollisionTargetLimit - _manualCollisionTargets.Count;
                if (availableSlots <= 0)
                {
                    UpdateMainStatus($"手工目标数量已达上限({ManualCollisionTargetLimit})");
                    return;
                }

                var addedCount = Math.Min(availableSlots, toAdd.Count);
                foreach (var entry in toAdd.Take(addedCount))
                {
                    _manualCollisionTargets.Add(entry);
                }

                // 更新序号
                UpdateManualCollisionTargetIndices();

                _manualTargetsLastSyncTime = DateTime.Now;
                UpdateManualCollisionTargetSummary();
                RefreshManualTargetHighlights();
                SyncManualCollisionTargetsToAnimationManager();

                UpdateMainStatus($"已添加 {addedCount} 个碰撞检测对象");
                LogManager.Info($"[手工碰撞] 添加 {addedCount} 个对象，目前共 {_manualCollisionTargets.Count} 个");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[手工碰撞] 同步选中对象失败: {ex.Message}");
                UpdateMainStatus("同步手工对象失败");
            }
        }

        private void ExecuteClearManualTargets()
        {
            if (_manualCollisionTargets.Count == 0)
                return;

            _manualCollisionTargets.Clear();
            _manualTargetsLastSyncTime = null;
            ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
            SyncManualCollisionTargetsToAnimationManager();
            UpdateManualCollisionTargetSummary();
            UpdateMainStatus("已清空手工碰撞对象");
            LogManager.Info("[手工碰撞] 手工对象列表已清空");
        }

        private void ExecuteRemoveManualTarget(ManualCollisionTargetViewModel target)
        {
            if (target == null)
                return;

            if (_manualCollisionTargets.Remove(target))
            {
                UpdateMainStatus($"已移除 {target.DisplayName}");
                LogManager.Info($"[手工碰撞] 移除对象 {target.DisplayName}");

                if (_manualCollisionTargets.Count == 0)
                {
                    ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
                }
                else
                {
                    RefreshManualTargetHighlights();
                }
            }
        }

        private void ExecuteHighlightManualTargets()
        {
            if (_manualCollisionTargets.Count == 0)
                return;

            RefreshManualTargetHighlights(forceHighlight: true);
            UpdateMainStatus("已高亮手工指定对象");
        }

        private void ExecuteClearManualHighlights()
        {
            ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
            UpdateMainStatus("已清除手工对象高亮");
        }

        /// <summary>
        /// 聚焦到手工指定的对象
        /// </summary>
        public void FocusOnManualTarget(ManualCollisionTargetViewModel target)
        {
            if (target?.ModelItem == null) return;

            try
            {
                // 先高亮该对象
                var items = new List<ModelItem> { target.ModelItem };
                ModelHighlightHelper.HighlightItems(ManualTargetsHighlightCategory, items);
                
                // 聚焦到对象（斜上方45度视角）
                ViewpointHelper.FocusOnModelItem(target.ModelItem, ViewpointHelper.ViewpointStrategy.ModelFocus);
                
                UpdateMainStatus($"已聚焦到: {target.DisplayName}");
                LogManager.Info($"聚焦到手工指定对象: {target.DisplayName}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"聚焦到手工指定对象失败: {ex.Message}", ex);
                UpdateMainStatus($"聚焦失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 聚焦到排除的对象
        /// </summary>
        public void FocusOnExcludedObject(ExcludedObjectViewModel excludedObject)
        {
            if (excludedObject?.ModelItem == null) return;

            try
            {
                // 先高亮该对象（使用排除对象专用颜色）
                var items = new List<ModelItem> { excludedObject.ModelItem };
                ModelHighlightHelper.HighlightItems(ExcludedObjectsHighlightCategory, items);
                
                // 聚焦到对象（斜上方45度视角）
                ViewpointHelper.FocusOnModelItem(excludedObject.ModelItem, ViewpointHelper.ViewpointStrategy.ModelFocus);
                
                UpdateMainStatus($"已聚焦到排除对象: {excludedObject.DisplayName}");
                LogManager.Info($"聚焦到排除对象: {excludedObject.DisplayName}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"聚焦到排除对象失败: {ex.Message}", ex);
                UpdateMainStatus($"聚焦失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 聚焦到碰撞构件，并移动动画物体到碰撞位置
        /// </summary>
        public void FocusOnCollisionObject(CollisionObjectViewModel collisionObject)
        {
            if (collisionObject?.ModelItem == null) return;
            
            // 从历史记录中获取运动物体
            var selectedResult = SelectedClashDetectiveResult;
            var animatedObject = selectedResult?.AnimatedObject;
            
            // 如果是虚拟物体且尚未创建，现在创建它
            if (selectedResult?.Record?.IsVirtualObject == true && animatedObject == null)
            {
                try
                {
                    double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(Autodesk.Navisworks.Api.Application.ActiveDocument.Units);
                    // 创建并显示虚拟物体（尺寸从模型单位转换为米）
                    animatedObject = VirtualObjectManager.Instance.CreateVirtualObject(
                        selectedResult.VirtualObjectLengthInMeters * unitsToMeters,
                        selectedResult.VirtualObjectWidthInMeters * unitsToMeters,
                        selectedResult.VirtualObjectHeightInMeters * unitsToMeters);
                    selectedResult.AnimatedObject = animatedObject;
                    LogManager.Info($"[碰撞构件] 创建虚拟物体并聚焦: {selectedResult.VirtualObjectLengthInMeters:F2}x{selectedResult.VirtualObjectWidthInMeters:F2}x{selectedResult.VirtualObjectHeightInMeters:F2}");
                }
                catch (Exception ex)
                {
                    LogManager.Warning($"[碰撞构件] 创建虚拟物体失败: {ex.Message}");
                }
            }
            
            if (animatedObject == null)
            {
                LogManager.Warning($"[碰撞构件] 未找到运动物体，仅聚焦到被撞对象: {collisionObject.DisplayName}");
                // 仅聚焦，不移动物体
                CollisionSceneHelper.MoveToCollisionAndFocus(
                    collisionObject.ModelItem, 
                    null,
                    collisionObject.AnimatedObjectTrackedPosition,
                    collisionObject.AnimatedObjectTrackedYawRadians,
                    collisionObject.AnimatedObjectTrackedRotation,
                    collisionObject.AnimatedObjectHasTrackedRotation,
                    false);
                return;
            }

            // 🔥 关键修复：如果运动物体是虚拟物体，确保它是可见的（可能被之前的切换隐藏了）
            if (selectedResult?.Record?.IsVirtualObject == true)
            {
                try
                {
                    double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(Autodesk.Navisworks.Api.Application.ActiveDocument.Units);
                    // 显示虚拟物体（如果已存在且尺寸相同，会显示；如果尺寸不同，会更新尺寸）
                    VirtualObjectManager.Instance.ShowVirtualObject(
                        selectedResult.VirtualObjectLengthInMeters * unitsToMeters,
                        selectedResult.VirtualObjectWidthInMeters * unitsToMeters,
                        selectedResult.VirtualObjectHeightInMeters * unitsToMeters);
                    LogManager.Debug("[碰撞构件] 虚拟物体已确保可见");
                }
                catch (Exception ex)
                {
                    LogManager.Warning($"[碰撞构件] 显示虚拟物体失败: {ex.Message}");
                }
            }

            try
            {
                // 首次查看时保存动画物体状态
                if (_savedAnimatedObjectStateForCollision == null || _savedAnimatedObjectForCollision != animatedObject)
                {
                    _savedAnimatedObjectForCollision = animatedObject;
                    _savedAnimatedObjectStateForCollision = CollisionSceneHelper.SaveAnimatedObjectState(animatedObject);
                }
                
                // 移动动画物体到碰撞位置并聚焦
                CollisionSceneHelper.MoveToCollisionAndFocus(
                    collisionObject.ModelItem, 
                    animatedObject,
                    collisionObject.AnimatedObjectTrackedPosition,
                    collisionObject.AnimatedObjectTrackedYawRadians,
                    collisionObject.AnimatedObjectTrackedRotation,
                    collisionObject.AnimatedObjectHasTrackedRotation,
                    collisionObject.HasPositionInfo);
                
                // 🔥 虚拟物体缩放已通过MoveItemToPositionAndYawWithCurrentScale保留
                
                UpdateMainStatus($"已聚焦到碰撞构件: {collisionObject.DisplayName}");
                LogManager.Info($"聚焦到碰撞构件: {collisionObject.DisplayName}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"聚焦到碰撞构件失败: {ex.Message}", ex);
                UpdateMainStatus($"聚焦失败: {ex.Message}");
            }
        }
        
        /// <summary>
        /// 恢复动画物体到原始状态（失去焦点或切换历史记录时调用）
        /// </summary>
        public void RestoreAnimatedObjectFromCollisionView()
        {
            if (_savedAnimatedObjectForCollision != null && _savedAnimatedObjectStateForCollision != null)
            {
                CollisionSceneHelper.RestoreAnimatedObjectState(_savedAnimatedObjectForCollision, _savedAnimatedObjectStateForCollision);
                LogManager.Info($"[碰撞构件] 运动物体已恢复到原始状态: {_savedAnimatedObjectForCollision.DisplayName}");
            }
            
            // 隐藏虚拟物体（如果之前显示的是虚拟物体）
            if (VirtualObjectManager.Instance.IsVirtualObjectActive)
            {
                VirtualObjectManager.Instance.HideVirtualObject();
                LogManager.Info("[碰撞构件] 虚拟物体已隐藏");
            }
            
            _savedAnimatedObjectStateForCollision = null;
            _savedAnimatedObjectForCollision = null;
        }

        #endregion

        #region 检测排除对象命令

        /// <summary>
        /// 从当前选择添加排除对象
        /// </summary>
        private void ExecuteAddExcludedObjectsFromSelection()
        {
            try
            {
                var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
                var selectedItems = doc?.CurrentSelection?.SelectedItems;

                if (selectedItems == null || selectedItems.Count == 0)
                {
                    UpdateMainStatus("请在Navisworks中选择需要排除的对象");
                    LogManager.Warning("[排除对象] 未选择任何对象");
                    return;
                }

                var toAdd = new List<ExcludedObjectViewModel>();
                foreach (ModelItem item in selectedItems)
                {
                    if (item == null)
                        continue;

                    if (!ModelItemAnalysisHelper.IsModelItemValid(item) || !HasGeometryRecursive(item))
                        continue;

                    if (ExcludedObjectExists(item))
                        continue;

                    var displayName = ModelItemAnalysisHelper.GetSafeDisplayName(item);
                    var modelPath = BuildModelPath(item);
                    toAdd.Add(new ExcludedObjectViewModel(item, displayName, modelPath));
                }

                if (toAdd.Count == 0)
                {
                    UpdateMainStatus("选中的对象已在排除列表中或不包含几何体");
                    return;
                }

                int startIndex = _excludedObjects.Count;
                foreach (var vm in toAdd)
                {
                    vm.Index = ++startIndex;
                    _excludedObjects.Add(vm);
                }

                UpdateMainStatus($"已添加 {toAdd.Count} 个排除对象");
                LogManager.Info($"[排除对象] 添加 {toAdd.Count} 个对象到排除列表");

                // 同步到PathAnimationManager
                SyncExcludedObjectsToAnimationManager();
            }
            catch (Exception ex)
            {
                LogManager.Error($"添加排除对象失败: {ex.Message}");
                UpdateMainStatus("添加排除对象失败");
            }
        }

        /// <summary>
        /// 清除所有排除对象
        /// </summary>
        private void ExecuteClearExcludedObjects()
        {
            try
            {
                int count = _excludedObjects.Count;
                _excludedObjects.Clear();
                ModelHighlightHelper.ClearCategory(ExcludedObjectsHighlightCategory);
                
                // 同步到PathAnimationManager
                _pathAnimationManager?.ClearExcludedObjects();
                
                UpdateMainStatus($"已清除 {count} 个排除对象");
                LogManager.Info($"[排除对象] 清除所有 {count} 个排除对象");
            }
            catch (Exception ex)
            {
                LogManager.Error($"清除排除对象失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 移除单个排除对象
        /// </summary>
        private void ExecuteRemoveExcludedObject(ExcludedObjectViewModel obj)
        {
            if (obj == null)
                return;

            if (_excludedObjects.Remove(obj))
            {
                // 重新编号
                for (int i = 0; i < _excludedObjects.Count; i++)
                {
                    _excludedObjects[i].Index = i + 1;
                }

                UpdateMainStatus($"已移除排除对象 {obj.DisplayName}");
                LogManager.Info($"[排除对象] 移除 {obj.DisplayName}");

                // 同步到PathAnimationManager（会清除缓存）
                SyncExcludedObjectsToAnimationManager();

                // 清除该对象的高亮显示
                ModelHighlightHelper.ClearCategory(ExcludedObjectsHighlightCategory);
            }
        }

        /// <summary>
        /// 高亮显示所有排除对象（全部高亮）
        /// </summary>
        private void ExecuteHighlightAllExcludedObjects()
        {
            try
            {
                if (_excludedObjects.Count == 0)
                    return;

                var itemsToHighlight = _excludedObjects.Select(e => e.ModelItem).Where(m => m != null).ToList();
                ModelHighlightHelper.HighlightItems(ExcludedObjectsHighlightCategory, itemsToHighlight);
                
                UpdateMainStatus($"已高亮全部 {itemsToHighlight.Count} 个排除对象");
                LogManager.Info($"[排除对象] 高亮显示全部 {itemsToHighlight.Count} 个对象");
            }
            catch (Exception ex)
            {
                LogManager.Error($"高亮排除对象失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 清除排除对象高亮
        /// </summary>
        private void ExecuteClearExcludedHighlight()
        {
            try
            {
                ModelHighlightHelper.ClearCategory(ExcludedObjectsHighlightCategory);
                UpdateMainStatus("已清除排除对象高亮");
                LogManager.Info("[排除对象] 清除高亮");
            }
            catch (Exception ex)
            {
                LogManager.Error($"清除排除对象高亮失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 高亮显示单个排除对象（点选时）
        /// </summary>
        public void HighlightSingleExcludedObject(ExcludedObjectViewModel obj)
        {
            try
            {
                if (obj?.ModelItem == null)
                    return;

                var items = new List<ModelItem> { obj.ModelItem };
                ModelHighlightHelper.HighlightItems(ExcludedObjectsHighlightCategory, items);
                
                LogManager.Debug($"[排除对象] 高亮显示单个对象: {obj.DisplayName}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"高亮单个排除对象失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 检查排除对象是否已存在
        /// </summary>
        private bool ExcludedObjectExists(ModelItem item)
        {
            if (item == null) return false;
            
            // 使用ModelItemEquals比较底层原生对象（项目中推荐的比较方式）
            bool exists = _excludedObjects.Any(e => ModelItemAnalysisHelper.ModelItemEquals(e.ModelItem, item));
            
            if (exists)
            {
                LogManager.Debug($"[排除对象] 对象已存在: {ModelItemAnalysisHelper.GetSafeDisplayName(item)}");
            }
            
            return exists;
        }

        /// <summary>
        /// 同步排除对象到PathAnimationManager
        /// </summary>
        private void SyncExcludedObjectsToAnimationManager()
        {
            var objectsToExclude = _excludedObjects.Select(e => e.ModelItem).Where(m => m != null).ToList();
            // 使用 SetExcludedObjectsAndClearCache 方法，更新排除列表并清除缓存（确保预计算能实时反映变更）
            _pathAnimationManager?.SetExcludedObjectsAndClearCache(objectsToExclude);
            LogManager.Debug($"[排除对象] 已同步 {objectsToExclude.Count} 个对象到PathAnimationManager，并清除动画缓存");
        }

        /// <summary>
        /// 从对话框结果添加排除对象到UI列表
        /// </summary>
        private void AddExcludedObjectsToUIList(List<ModelItem> objects)
        {
            if (objects == null || objects.Count == 0) return;

            int startIndex = _excludedObjects.Count;
            foreach (var item in objects)
            {
                if (item == null || ExcludedObjectExists(item))
                    continue;

                var displayName = ModelItemAnalysisHelper.GetSafeDisplayName(item);
                var modelPath = BuildModelPath(item);
                var vm = new ExcludedObjectViewModel(item, displayName, modelPath, ++startIndex);
                _excludedObjects.Add(vm);
            }

            UpdateExcludedObjectSummary();
            LogManager.Info($"[排除对象] 从对话框添加 {_excludedObjects.Count} 个排除对象到UI列表");
        }

        /// <summary>
        /// 排除对象集合变更事件处理
        /// </summary>
        private void OnExcludedObjectsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            OnPropertyChanged(nameof(HasExcludedObjects));
            UpdateExcludedObjectSummary();
            
            // 刷新命令可用状态
            System.Windows.Input.CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// 更新排除对象统计信息
        /// </summary>
        private void UpdateExcludedObjectSummary()
        {
            if (_excludedObjects == null || _excludedObjects.Count == 0)
            {
                ExcludedObjectSummary = "未指定排除对象";
            }
            else
            {
                ExcludedObjectSummary = $"已指定 {_excludedObjects.Count} 个排除对象";
            }
        }

        #endregion

        #region 碰撞结果高亮命令

        private void ExecuteHighlightPrecomputedCollisionResults()
        {
            try
            {
                var deduplicatedResults = _clashIntegration?.GetDeduplicatedCollisionResults();
                LogManager.Debug($"[预计算碰撞结果高亮] 获取到去重后的预计算结果数量: {deduplicatedResults?.Count ?? 0}");

                if (deduplicatedResults == null || deduplicatedResults.Count == 0)
                {
                    UpdateMainStatus("没有可高亮的去重预计算碰撞结果对象");
                    LogManager.Warning("[预计算碰撞结果高亮] 去重预计算缓存为空");
                    return;
                }

                // 输出前3个碰撞结果的信息
                for (int i = 0; i < Math.Min(3, deduplicatedResults.Count); i++)
                {
                    var collision = deduplicatedResults[i];
                    LogManager.Debug($"[预计算碰撞结果高亮] 碰撞{i+1}: {collision.DisplayName}, Item1={collision.Item1?.DisplayName}, Item2={collision.Item2?.DisplayName}");
                }

                ModelHighlightHelper.ManageCollisionHighlightsByCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory, deduplicatedResults, clearOtherCategories: false);
                UpdateMainStatus($"已高亮 {deduplicatedResults.Count} 个去重预计算碰撞结果");
                LogManager.Info($"[预计算碰撞结果高亮] 已高亮 {deduplicatedResults.Count} 个结果");
            }
            catch (Exception ex)
            {
                LogManager.Error($"高亮预计算碰撞结果失败: {ex.Message}", ex);
                UpdateMainStatus("高亮预计算碰撞结果失败");
            }
        }

        private void ExecuteClearPrecomputedCollisionHighlights()
        {
            ModelHighlightHelper.ClearCategory(CollisionResultsHighlightCategory);
            UpdateMainStatus("已清除预计算碰撞结果高亮");
        }

        /// <summary>
        /// 🔥 分析预计算碰撞结果，自动高亮并显示分析对话框
        /// </summary>
        private async Task AnalyzeAndHighlightPrecomputedCollisionsAsync()
        {
            try
            {
                if (DialogHelper.IsTestAutomationModeEnabled)
                {
                    LogManager.Info("[碰撞分析] 自动测试模式下跳过预计算碰撞分析");
                    SaveCollisionDetectionRecord();
                    return;
                }

                // 1. 获取预计算碰撞结果（从PathAnimationManager，现在缓存帧中已包含碰撞结果）
                var allResults = _pathAnimationManager?.AllCollisionResults;
                if (allResults == null || allResults.Count == 0)
                {
                    LogManager.Info("[碰撞分析] 没有预计算碰撞结果，跳过分析");
                    // 🔥 没有预计算碰撞，配置已确定，直接保存检测记录
                    var recordId = SaveCollisionDetectionRecord();
                    if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value))
                    {
                        LogManager.Info("[碰撞分析] 用户选择使用历史记录，跳过动画生成");
                        return;
                    }
                    return;
                }

                // 去重：按碰撞对象对去重（取每对的第一次出现）
                var deduplicatedResults = allResults
                    .GroupBy(r => new { Item1 = r.Item1, Item2 = r.Item2 })
                    .Select(g => g.First())
                    .ToList();

                LogManager.Info($"[碰撞分析] 开始分析 {allResults.Count} 个碰撞结果（去重后 {deduplicatedResults.Count} 个）");

                // 2. 自动高亮所有预计算碰撞结果
                ModelHighlightHelper.ManageCollisionHighlightsByCategory(
                    ModelHighlightHelper.PrecomputeCollisionResultsCategory, 
                    deduplicatedResults, 
                    clearOtherCategories: false);
                LogManager.Info($"[碰撞分析] 已高亮 {deduplicatedResults.Count} 个碰撞对象");

                // 3. 分析碰撞热点（使用原始结果 - 按频率统计）
                var hotspots = AnalyzeCollisionHotspots(allResults);
                
                if (hotspots.Count > 0)
                {
                    LogManager.Info($"[碰撞分析] 发现 {hotspots.Count} 个高频碰撞物体");
                    foreach (var hotspot in hotspots.Take(5))
                    {
                        LogManager.Info($"  - {hotspot.ObjectName}: {hotspot.CollisionCount} 次 ({hotspot.Percentage:F1}%) - {hotspot.Reason}");
                    }

                    // 4. 显示分析对话框（在UI线程）- 传递原始总数用于百分比计算
                    await _uiStateManager.ExecuteUIUpdateAsync(() =>
                    {
                        ShowCollisionAnalysisDialog(hotspots, allResults.Count);
                    });
                }
                else
                {
                    LogManager.Info("[碰撞分析] 没有发现明显的高频碰撞物体");
                    // 🔥 有碰撞但无高频热点，配置已确定，保存检测记录
                    var recordId = SaveCollisionDetectionRecord();
                    if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value))
                    {
                        // 用户选择使用历史记录，跳过后续流程
                        LogManager.Info("[碰撞分析] 用户选择使用历史记录，跳过动画生成");
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[碰撞分析] 分析预计算碰撞结果失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 分析碰撞热点 - 找出高频碰撞的物体
        /// </summary>
        private List<CollisionHotspotInfo> AnalyzeCollisionHotspots(List<CollisionResult> results)
        {
            // 按被撞物体分组统计
            var objectStats = results
                .Where(r => r.Item2 != null)
                .GroupBy(r => r.Item2)
                .Select(g => new
                {
                    Object = g.Key,
                    Count = g.Count(),
                    Percentage = (double)g.Count() / results.Count * 100
                })
                .Where(x => x.Count >= 10 || x.Percentage > 10) // 只关注高频物体
                .OrderByDescending(x => x.Count)
                .ToList();

            var hotspots = new List<CollisionHotspotInfo>();
            
            foreach (var stat in objectStats)
            {
                var objectName = stat.Object.DisplayName ?? "未知对象";
                var reason = GetHotspotReason(objectName, stat.Object);
                
                hotspots.Add(new CollisionHotspotInfo
                {
                    Object = stat.Object,
                    ObjectName = objectName,
                    CollisionCount = stat.Count,
                    Percentage = stat.Percentage,
                    Reason = reason,
                    RecommendedAction = reason.Contains("地面") || reason.Contains("楼板") || reason.Contains("墙体") 
                        ? "建议排除" 
                        : "需要检查"
                });
            }

            return hotspots;
        }

        /// <summary>
        /// 获取热点原因分析
        /// </summary>
        private string GetHotspotReason(string objectName, ModelItem item)
        {
            var name = objectName.ToLower();
            
            // 检查名称特征
            if (name.Contains("地面") || name.Contains("楼板") || name.Contains("floor") || name.Contains("slab"))
                return "地面/楼板 - 大面积平面物体，容易产生假阳性碰撞";
            
            if (name.Contains("墙体") || name.Contains("wall") || name.Contains("外墙"))
                return "墙体 - 沿路径延伸，容易产生假阳性碰撞";
            
            if (name.Contains("楼梯") || name.Contains("stair"))
                return "楼梯 - 结构复杂，可能产生多次碰撞检测";
            
            if (name.Contains("屋顶") || name.Contains("roof") || name.Contains("天花板"))
                return "屋顶/天花板 - 位于上方，可能产生假阳性碰撞";
            
            // 检查包围盒大小
            try
            {
                var bbox = item.BoundingBox();
                var volume = (bbox.Max.X - bbox.Min.X) * (bbox.Max.Y - bbox.Min.Y) * (bbox.Max.Z - bbox.Min.Z);
                if (volume > 1000) // 大于1000立方米
                    return "大型物体 - 包围盒很大，容易与路径相交";
            }
            catch (Exception ex)
            {
                LogManager.Debug($"[碰撞分析] 获取物体包围盒失败: {ex.Message}");
            }
            
            return "未知原因 - 建议手动检查";
        }

        /// <summary>
        /// 显示碰撞分析对话框
        /// </summary>
        private void ShowCollisionAnalysisDialog(List<CollisionHotspotInfo> hotspots, int totalCollisions)
        {
            try
            {
                var dialog = new CollisionAnalysisDialog(hotspots, totalCollisions);
                DialogHelper.SetOwnerSafely(dialog);
                dialog.Closed += (_, __) => HandleCollisionAnalysisDialogClosed(dialog, totalCollisions);
                dialog.Show();
                dialog.Activate();
            }
            catch (Exception ex)
            {
                LogManager.Error($"[碰撞分析] 显示分析对话框失败: {ex.Message}", ex);
            }
        }

        private void HandleCollisionAnalysisDialogClosed(CollisionAnalysisDialog dialog, int totalCollisions)
        {
            try
            {
                if (dialog == null)
                {
                    return;
                }

                switch (dialog.SelectedAction)
                {
                    case CollisionAnalysisDialog.CollisionAnalysisAction.Regenerate:
                        LogManager.Info($"[碰撞分析] 用户选择排除 {dialog.ExcludedObjects.Count} 个物体并重新生成");

                        AddExcludedObjectsToUIList(dialog.ExcludedObjects);

                        var allExcludedObjects = _excludedObjects.Select(e => e.ModelItem).Where(m => m != null).ToList();
                        _pathAnimationManager.SetExcludedObjectsAndClearCache(allExcludedObjects);

                        System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                        {
                            ExecuteGenerateAnimation();
                        }), DispatcherPriority.Background);

                        UpdateMainStatus($"已排除 {allExcludedObjects.Count} 个物体，正在重新生成...");
                        break;

                    case CollisionAnalysisDialog.CollisionAnalysisAction.Continue:
                        LogManager.Info("[碰撞分析] 用户选择直接继续生成");
                        UpdateMainStatus($"预计算碰撞分析完成，共 {totalCollisions} 个候选碰撞");

                        var recordId = SaveCollisionDetectionRecord();
                        if (recordId.HasValue && IsReusedHistoryRecord(recordId.Value))
                        {
                            LogManager.Info("[碰撞分析] 用户选择使用历史记录，跳过动画生成");
                        }
                        break;

                    case CollisionAnalysisDialog.CollisionAnalysisAction.Cancel:
                    case CollisionAnalysisDialog.CollisionAnalysisAction.None:
                    default:
                        UpdateMainStatus("预计算碰撞分析已取消");
                        break;
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[碰撞分析] 处理分析窗口结果失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 保存检测记录到数据库
        /// 在生成动画完成后调用，创建检测记录并保存当时的完整配置
        /// </summary>
        private int? SaveCollisionDetectionRecord()
        {
            try
            {
                var pathDatabase = _pathPlanningManager?.GetPathDatabase();
                if (pathDatabase == null)
                {
                    LogManager.Warning("[检测记录] 数据库未初始化，无法保存检测记录");
                    return null;
                }

                string routeId = CurrentPathRoute?.Id ?? "";

                if (!DialogHelper.IsTestAutomationModeEnabled)
                {
                    // 🔥 检查是否已有相同配置的检测记录
                    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($"[检测记录] 用户选择重新生成，创建新记录");
                    }
                }
                else
                {
                    LogManager.Info("[检测记录] 自动测试模式下跳过重复检测记录检查，直接创建新记录");
                }
                
                int recordId = CreateCollisionDetectionRecordSnapshot("动画生成", bindToAnimationManager: true);

                // 🔥 注册动画配置哈希与检测记录ID的映射（用于后续检查重复配置）
                try
                {
                    var animatedObject = UseVirtualObject ? VirtualObjectManager.Instance.CurrentVirtualObject : SelectedAnimatedObject;
                    var route = CurrentPathRoute?.Route;
                    if (animatedObject != null && route != null)
                    {
                        var animationHash = _pathAnimationManager.ComputeAnimationConfigHashFromRoute(animatedObject, route);
                        _pathAnimationManager.RegisterAnimationHashToRecordId(animationHash, recordId);
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Warning($"[检测记录] 注册动画哈希映射失败: {ex.Message}");
                }

                return recordId;
            }
            catch (Exception ex)
            {
                LogManager.Error($"[检测记录] 保存失败: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 创建碰撞检测记录快照。
        /// 生成动画和批处理都应该创建自己的配置快照，不依赖旧检测结果。
        /// </summary>
        private int CreateCollisionDetectionRecordSnapshot(string scenarioName, bool bindToAnimationManager)
        {
            var pathDatabase = _pathPlanningManager?.GetPathDatabase();
            if (pathDatabase == null)
            {
                throw new InvalidOperationException("数据库未初始化，无法保存检测记录");
            }

            string routeId = CurrentPathRoute?.Id ?? string.Empty;
            string testName = $"{scenarioName}_{DateTime.Now:MMdd_HHmmssfff}";
            double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();

            var record = new CollisionDetectionRecord
            {
                RouteId = routeId,
                CreatedTime = DateTime.Now,
                FrameRate = _animationFrameRate,
                DurationSeconds = AnimationDuration,
                DetectionTolerance = _detectionTolerance * metersToUnits,
                IsVirtualObject = UseVirtualObject,
                AnimatedObjectName = UseVirtualObject ? "虚拟物体" : SelectedAnimatedObject?.DisplayName,
                DetectAllObjects = !IsManualCollisionTargetEnabled,
                ObjectRotationCorrection = _objectRotationCorrection.ZDegrees,
                Description = $"{scenarioName}于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
                TestName = testName,
                TestTime = DateTime.Now,
                CollisionCount = 0,
                AnimationCollisionCount = 0
            };

            if (UseVirtualObject)
            {
                record.VirtualObjectLength = VirtualObjectLengthInMeters * metersToUnits;
                record.VirtualObjectWidth = VirtualObjectWidthInMeters * metersToUnits;
                record.VirtualObjectHeight = VirtualObjectHeightInMeters * metersToUnits;
            }
            else if (SelectedAnimatedObject != null)
            {
                try
                {
                    var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(SelectedAnimatedObject);
                    record.ObjectModelIndex = pathId.ModelIndex;
                    record.ObjectPathId = pathId.PathId;
                }
                catch (Exception ex)
                {
                    LogManager.Warning($"[检测记录] 获取运动物体PathId失败: {ex.Message}");
                }
            }

            int recordId = pathDatabase.SaveCollisionDetectionRecord(record);
            LogManager.Info($"[检测记录] 已创建{scenarioName}记录 (Id={recordId})");

            if (bindToAnimationManager)
            {
                _pathAnimationManager.CurrentDetectionRecordId = recordId;
                _pathAnimationManager.IsUsingHistoryRecord = false;
            }

            var excludedObjects = _pathAnimationManager?.GetExcludedObjects();
            if (excludedObjects != null && excludedObjects.Count > 0)
            {
                var excludedRecords = new List<CollisionDetectionExcludedObjectRecord>();
                foreach (var obj in excludedObjects)
                {
                    if (obj == null) continue;
                    try
                    {
                        var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(obj);
                        excludedRecords.Add(new CollisionDetectionExcludedObjectRecord
                        {
                            DetectionRecordId = recordId,
                            ModelIndex = pathId.ModelIndex,
                            PathId = pathId.PathId,
                            DisplayName = obj.DisplayName,
                            ObjectName = obj.DisplayName,
                            Reason = "预计算碰撞分析排除"
                        });
                    }
                    catch (Exception ex)
                    {
                        LogManager.Warning($"[检测记录] 准备排除对象记录失败: {obj.DisplayName}, {ex.Message}");
                    }
                }

                if (excludedRecords.Count > 0)
                {
                    pathDatabase.SaveCollisionDetectionExcludedObjects(recordId, excludedRecords);
                    LogManager.Info($"[检测记录] 已保存 {excludedRecords.Count} 个排除对象");
                }
            }

            if (IsManualCollisionTargetEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0)
            {
                var manualTargetRecords = new List<CollisionDetectionManualTargetRecord>();
                foreach (var target in _manualCollisionTargets)
                {
                    if (target?.ModelItem == null) continue;
                    try
                    {
                        var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(target.ModelItem);
                        manualTargetRecords.Add(new CollisionDetectionManualTargetRecord
                        {
                            DetectionRecordId = recordId,
                            ModelIndex = pathId.ModelIndex,
                            PathId = pathId.PathId,
                            DisplayName = target.ModelItem.DisplayName,
                            ObjectName = target.ModelItem.DisplayName
                        });
                    }
                    catch (Exception ex)
                    {
                        LogManager.Warning($"[检测记录] 准备手工目标记录失败: {target.DisplayName}, {ex.Message}");
                    }
                }

                if (manualTargetRecords.Count > 0)
                {
                    pathDatabase.SaveCollisionDetectionManualTargets(recordId, manualTargetRecords);
                    LogManager.Info($"[检测记录] 已保存 {manualTargetRecords.Count} 个手工目标");
                }
            }

            return recordId;
        }

        /// <summary>
        /// 用户选择析析
        /// </summary>
        private enum UserChoice
        {
            CreateNew,   // 创建新记录
            UseExisting  // 使用历史记录
        }

        /// <summary>
        /// 检查是否已有相同配置的检测记录
        /// 策略：1.先检查内存缓存 2.SQL过滤基础参数 3.比较排除列表和手工目标
        /// </summary>
        /// <returns>(记录ID, 用户选择)，不存在或用户取消则返回null</returns>
        private (int id, UserChoice userChoice)? CheckExistingDetectionRecord()
        {
            try
            {
                var routeId = CurrentPathRoute?.Id;
                if (string.IsNullOrEmpty(routeId))
                {
                    return null;
                }

                var pathDatabase = _pathPlanningManager?.GetPathDatabase();
                if (pathDatabase == null)
                {
                    return null;
                }

                var animatedObject = UseVirtualObject ? VirtualObjectManager.Instance.CurrentVirtualObject : SelectedAnimatedObject;
                if (animatedObject == null)
                {
                    return null;
                }

                // 1. 先检查内存缓存（程序运行期间已处理过的）
                var currentHash = _pathAnimationManager.ComputeAnimationConfigHashFromRoute(animatedObject, CurrentPathRoute.Route);
                var cachedRecordId = _pathAnimationManager.GetDetectionRecordIdByHash(currentHash);
                if (cachedRecordId.HasValue)
                {
                    var cachedRecord = pathDatabase.GetDetectionResultById(cachedRecordId.Value);
                    if (cachedRecord != null)
                    {
                        LogManager.Info($"[CheckExistingDetectionRecord] 内存缓存命中: RecordId={cachedRecordId}");
                        return ShowDuplicateConfigDialog(cachedRecord);
                    }
                }

                // 2. SQL过滤基础参数匹配的记录
                // 注意：数据库存储的是模型单位（不带Meters后缀），查询时传模型单位
                var factor = UnitsConverter.GetMetersToUnitsConversionFactor();
                var matchingRecords = pathDatabase.FindDetectionRecordsByBaseParams(
                    routeId,
                    _animationFrameRate,
                    AnimationDuration,
                    _detectionTolerance * factor,  // 米 → 模型单位
                    UseVirtualObject,
                    UseVirtualObject ? VirtualObjectLengthInMeters * factor : (double?)null,  // 米 → 模型单位
                    UseVirtualObject ? VirtualObjectWidthInMeters * factor : (double?)null,
                    UseVirtualObject ? VirtualObjectHeightInMeters * factor : (double?)null,
                    _objectRotationCorrection.ZDegrees,
                    IsManualCollisionTargetEnabled
                );

                if (matchingRecords == null || matchingRecords.Count == 0)
                {
                    LogManager.Info("[CheckExistingDetectionRecord] SQL基础参数过滤无匹配");
                    return null;
                }

                LogManager.Info($"[CheckExistingDetectionRecord] SQL找到 {matchingRecords.Count} 条记录，开始比较排除列表");

                // 3. 逐个比较排除列表和手工目标
                var currentExcluded = GetCurrentExcludedObjects();
                var currentTargets = GetCurrentManualTargets();

                foreach (var record in matchingRecords)
                {
                    if (IsExcludedListMatch(record.Id, currentExcluded, pathDatabase) &&
                        IsManualTargetListMatch(record.Id, currentTargets, pathDatabase))
                    {
                        LogManager.Info($"[CheckExistingDetectionRecord] 找到完全匹配: RecordId={record.Id}");
                        
                        // 注册到内存缓存
                        _pathAnimationManager.RegisterAnimationHashToRecordId(currentHash, record.Id);
                        
                        return ShowDuplicateConfigDialog(record);
                    }
                }

                LogManager.Info("[CheckExistingDetectionRecord] 未找到完全匹配的记录");
                return null;
            }
            catch (Exception ex)
            {
                LogManager.Warning($"[CheckExistingDetectionRecord] 检查失败: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 显示重复配置对话框
        /// </summary>
        private (int id, UserChoice userChoice)? ShowDuplicateConfigDialog(CollisionDetectionRecord record)
        {
            if (TestAutomationHttpService.ShouldAutoChooseCreateNewDetectionRecord)
            {
                _lastReusedRecordId = null;
                LogManager.Info($"[检测记录] 测试自动化已接管重复配置对话框，自动选择重新生成新记录 (ExistingId={record.Id})");
                return (record.Id, UserChoice.CreateNew);
            }

            var message = $"检测配置已存在！\n\n" +
                          $"找到一条相同配置的历史检测记录：\n" +
                          $"• 测试名称：{record.TestName ?? "未命名"}\n" +
                          $"• 检测时间：{record.TestTime:yyyy-MM-dd HH:mm:ss}\n" +
                          $"• 碰撞数量：{record.CollisionCount} 个\n\n" +
                          $"您可以：\n" +
                          $"• 【是】直接使用历史记录（跳过重复检测）\n" +
                          $"• 【否】重新生成新记录";
            
            var result = DialogHelper.ShowMessageBox(
                message,
                "检测配置已存在",
                System.Windows.MessageBoxButton.YesNo,
                System.Windows.MessageBoxImage.Question);
            
            if (result == System.Windows.MessageBoxResult.Yes)
            {
                _lastReusedRecordId = record.Id;
                return (record.Id, UserChoice.UseExisting);
            }
            else
            {
                _lastReusedRecordId = null;
                return (record.Id, UserChoice.CreateNew);
            }
        }

        /// <summary>
        /// 获取当前排除对象列表（ModelIndex + PathId）
        /// </summary>
        private List<(int ModelIndex, string PathId)> GetCurrentExcludedObjects()
        {
            var result = new List<(int, string)>();
            var excludedObjects = _pathAnimationManager?.GetExcludedObjects();
            
            if (excludedObjects != null)
            {
                foreach (var obj in excludedObjects)
                {
                    if (obj == null) continue;
                    try
                    {
                        var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(obj);
                        result.Add((pathId.ModelIndex, pathId.PathId));
                    }
                    catch { }
                }
            }
            
            return result.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList();
        }

        /// <summary>
        /// 获取当前手工碰撞目标列表（ModelIndex + PathId）
        /// </summary>
        private List<(int ModelIndex, string PathId)> GetCurrentManualTargets()
        {
            var result = new List<(int, string)>();
            
            if (IsManualCollisionTargetEnabled && _manualCollisionTargets != null)
            {
                foreach (var target in _manualCollisionTargets)
                {
                    if (target?.ModelItem == null) continue;
                    try
                    {
                        var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(target.ModelItem);
                        result.Add((pathId.ModelIndex, pathId.PathId));
                    }
                    catch { }
                }
            }
            
            return result.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList();
        }

        /// <summary>
        /// 比较排除列表是否匹配
        /// </summary>
        private bool IsExcludedListMatch(int recordId, List<(int ModelIndex, string PathId)> currentList, PathDatabase db)
        {
            var dbList = db.GetDetectionExcludedObjects(recordId)
                .Select(e => (e.ModelIndex, e.PathId))
                .OrderBy(x => x.ModelIndex)
                .ThenBy(x => x.PathId)
                .ToList();
            
            return dbList.SequenceEqual(currentList);
        }

        /// <summary>
        /// 比较手工目标列表是否匹配
        /// </summary>
        private bool IsManualTargetListMatch(int recordId, List<(int ModelIndex, string PathId)> currentList, PathDatabase db)
        {
            var dbList = db.GetDetectionManualTargets(recordId)
                .Select(t => (t.ModelIndex, t.PathId))
                .OrderBy(x => x.ModelIndex)
                .ThenBy(x => x.PathId)
                .ToList();
            
            return dbList.SequenceEqual(currentList);
        }

        private void ExecuteToggleWireframeMode()
        {
            try
            {
                var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
                if (doc == null || doc.IsClear)
                {
                    LogManager.Warning("[线框模式] 没有活动的文档");
                    UpdateMainStatus("无法切换线框模式：没有活动文档");
                    return;
                }

                // 创建当前视角的副本
                var newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();

                // 判断当前是否已经是线框模式
                bool isWireframe = (newViewpoint.RenderStyle == ViewpointRenderStyle.Wireframe);

                if (!isWireframe)
                {
                    // 切换到线框模式
                    newViewpoint.RenderStyle = ViewpointRenderStyle.Wireframe;

                    LogManager.Info("[线框模式] 视图模式已切换为Wireframe");
                    UpdateMainStatus("已切换到线框模式");
                }
                else
                {
                    // 切换回着色模式
                    newViewpoint.RenderStyle = ViewpointRenderStyle.Shaded;

                    LogManager.Info("[线框模式] 视图模式已切换回Shaded");
                    UpdateMainStatus("已切换回着色模式");
                }

                // 应用修改后的视角
                doc.CurrentViewpoint.CopyFrom(newViewpoint);
            }
            catch (Exception ex)
            {
                LogManager.Error($"切换线框模式失败: {ex.Message}", ex);
                UpdateMainStatus("切换线框模式失败");
            }
        }

        private void ExecuteHighlightClashDetectiveResults()
        {
            if (string.IsNullOrEmpty(_clashIntegration?.CurrentTestName))
            {
                LogManager.Warning("[ClashDetective高亮] 当前测试名称为空，无法高亮结果");
                return;
            }

            ModelHighlightHelper.HighlightClashDetectiveResults(_clashIntegration.CurrentTestName, _clashIntegration.GetCurrentPathClashResults);
            UpdateMainStatus("已高亮ClashDetective检测结果");
        }

        private void ExecuteClearClashDetectiveHighlights()
        {
            ModelHighlightHelper.ClearClashDetectiveHighlights();
            UpdateMainStatus("已清除ClashDetective高亮");
        }

        /// <summary>
        /// 刷新ClashDetective结果列表
        /// </summary>
        private void RefreshClashDetectiveResultsList()
        {
            try
            {
                var pathName = CurrentPathRoute?.Name;
                LogManager.Debug($"[刷新碰撞历史] CurrentPathRoute={CurrentPathRoute?.Id}, Name={pathName}");
                if (string.IsNullOrEmpty(pathName))
                {
                    LogManager.Warning("[刷新碰撞历史] 路径名称为空，清空列表");
                    _clashDetectiveResults.Clear();
                    HasClashDetectiveResults = false;
                    return;
                }

                var pathDatabase = PathPlanningManager.Instance?.GetPathDatabase();
                if (pathDatabase == null)
                {
                    _clashDetectiveResults.Clear();
                    HasClashDetectiveResults = false;
                    return;
                }

                var records = pathDatabase.GetDetectionResultsByPath(pathName);
                LogManager.Debug($"[刷新碰撞历史] 查询到 {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.Debug($"[碰撞历史] 已加载运动物体: {resultViewModel.AnimatedObject?.DisplayName ?? "未找到"} for TestName={record.TestName}");
                        }
                        catch (Exception ex)
                        {
                            LogManager.Warning($"[碰撞历史] 无法加载运动物体: ModelIndex={record.ObjectModelIndex}, PathId={record.ObjectPathId}, 错误: {ex.Message}");
                        }
                    }
                    
                    // 同时加载该结果的碰撞构件清单
                    var collisionObjectRecords = pathDatabase.GetClashDetectiveCollisionObjects(record.Id);
                    // record.Id 就是 DetectionRecordId
                    if (collisionObjectRecords != null)
                    {
                        int index = 1;
                        foreach (var objRecord in collisionObjectRecords)
                        {
                            CollisionObjectViewModel objViewModel = null;
                            ModelItem modelItem = null;
                            string modelPath = "路径获取失败";

                            // 通过 ModelIndex 和 PathId 重建 ModelItem
                            if (objRecord.ModelIndex.HasValue && !string.IsNullOrEmpty(objRecord.PathId))
                            {
                                try
                                {
                                    var pathIdObj = new Autodesk.Navisworks.Api.DocumentParts.ModelItemPathId
                                    {
                                        ModelIndex = objRecord.ModelIndex.Value,
                                        PathId = objRecord.PathId
                                    };
                                    modelItem = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.ResolvePathId(pathIdObj);

                                    // 获取完整的节点树路径
                                    if (modelItem != null)
                                    {
                                        modelPath = BuildModelPath(modelItem);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    LogManager.Warning($"[碰撞构件] 无法通过 PathId 找到 ModelItem: ModelIndex={objRecord.ModelIndex}, PathId={objRecord.PathId}, 错误: {ex.Message}");
                                }
                            }

                            objViewModel = new CollisionObjectViewModel
                            {
                                Index = index++,
                                DisplayName = objRecord.DisplayName,
                                ObjectName = objRecord.ObjectName,
                                ModelPath = modelPath,
                                ResultId = objRecord.DetectionRecordId,
                                TestName = record.TestName,
                                ModelIndex = objRecord.ModelIndex,
                                PathId = objRecord.PathId,
                                ModelItem = modelItem,
                                HighlightCommand = new RelayCommand(() => resultViewModel.ExecuteHighlightCollisionObject(objViewModel)),
                                // 填充碰撞位置信息
                                AnimatedObjectTrackedPosition = objRecord.HasPositionInfo && objRecord.AnimatedObjectTrackedPosX.HasValue ? 
                                    new Point3D(objRecord.AnimatedObjectTrackedPosX.Value, objRecord.AnimatedObjectTrackedPosY.Value, objRecord.AnimatedObjectTrackedPosZ.Value) : null,
                                AnimatedObjectTrackedYawRadians = objRecord.AnimatedObjectTrackedYawRadians ?? 0,
                                AnimatedObjectTrackedRotation = objRecord.AnimatedObjectHasTrackedRotation &&
                                                objRecord.AnimatedObjectTrackedRotA.HasValue &&
                                                objRecord.AnimatedObjectTrackedRotB.HasValue &&
                                                objRecord.AnimatedObjectTrackedRotC.HasValue &&
                                                objRecord.AnimatedObjectTrackedRotD.HasValue
                                    ? new Rotation3D(
                                        objRecord.AnimatedObjectTrackedRotA.Value,
                                        objRecord.AnimatedObjectTrackedRotB.Value,
                                        objRecord.AnimatedObjectTrackedRotC.Value,
                                        objRecord.AnimatedObjectTrackedRotD.Value)
                                    : Rotation3D.Identity,
                                AnimatedObjectHasTrackedRotation = objRecord.AnimatedObjectHasTrackedRotation,
                                HasPositionInfo = objRecord.HasPositionInfo
                            };
                            resultViewModel.CollisionObjects.Add(objViewModel);
                        }
                    }
                    
                    _clashDetectiveResults.Add(resultViewModel);
                }

                // 更新HasClashDetectiveResults属性
                HasClashDetectiveResults = _clashDetectiveResults.Count > 0;

                UpdateMainStatus($"已刷新ClashDetective结果列表：{_clashDetectiveResults.Count}条记录");
                LogManager.Info($"ClashDetective结果列表已刷新：{_clashDetectiveResults.Count}条记录");
            }
            catch (Exception ex)
            {
                LogManager.Error($"刷新ClashDetective结果列表失败: {ex.Message}");
                _clashDetectiveResults.Clear();
                HasClashDetectiveResults = false;
            }
        }

        private void ExecuteRefreshClashDetectiveResults()
        {
            RefreshClashDetectiveResultsList();
        }

        /// <summary>
        /// 在历史列表中选中指定ID的记录，并打开碰撞报告
        /// </summary>
        private void SelectHistoryRecordById(int recordId)
        {
            try
            {
                // 先刷新列表确保记录存在
                RefreshClashDetectiveResultsList();
                
                // 查找并选中记录
                var targetRecord = _clashDetectiveResults.FirstOrDefault(r => r.Record?.Id == recordId);
                if (targetRecord != null)
                {
                    // 先设置选中项，确保UI更新
                    SelectedClashDetectiveResult = targetRecord;
                    LogManager.Info($"[SelectHistoryRecordById] 已在历史列表中选中记录 (Id={recordId})");
                    
                    // 更新状态栏显示
                    if (targetRecord.Record?.CollisionCount > 0)
                    {
                        UpdateMainStatus($"已加载历史记录：{targetRecord.Record.TestName}，{targetRecord.Record.CollisionCount} 个碰撞");
                    }
                    else
                    {
                        UpdateMainStatus($"已加载历史记录：{targetRecord.Record.TestName}，无碰撞");
                    }
                    
                    // 🔥 打开该历史记录的碰撞报告，让用户明确知道使用了哪条记录
                    if (!string.IsNullOrEmpty(targetRecord.Record?.TestName))
                    {
                        LogManager.Info($"[SelectHistoryRecordById] 正在打开历史记录报告: {targetRecord.Record.TestName}");
                        OpenHistoryReportAsync(recordId);
                    }
                }
                else
                {
                    LogManager.Warning($"[SelectHistoryRecordById] 未在列表中找到记录 (Id={recordId})");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[SelectHistoryRecordById] 选中记录失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 检查是否是复用的历史记录
        /// </summary>
        private bool IsReusedHistoryRecord(int recordId)
        {
            return _lastReusedRecordId.HasValue && _lastReusedRecordId.Value == recordId;
        }

        /// <summary>
        /// 异步打开历史碰撞报告
        /// </summary>
        private async void OpenHistoryReportAsync(int recordId)
        {
            try
            {
                // 使用检测记录ID查询生成报告，更可靠
                var command = GenerateCollisionReportCommand.CreateComprehensive(hasHighlighted: true);
                command.SetDetectionRecordId(recordId);
                var result = await command.ExecuteAsync();
                
                if (result.IsSuccess && result is PathPlanningResult<CollisionReportResult> reportResult)
                {
                    NavisworksTransport.UI.WPF.Views.CollisionReportDialog.ShowReport(reportResult.Data);
                }
                else
                {
                    LogManager.Warning($"[OpenHistoryReportAsync] 生成报告失败: {result.ErrorMessage}");
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[OpenHistoryReportAsync] 打开历史报告失败: {ex.Message}");
            }
        }

        #endregion

        /// <summary>
        /// 异步预计算碰撞排除列表
        /// </summary>
        /// <param name="animationObject">动画对象</param>
        private async Task PrecomputeCollisionExclusionsAsync(ModelItem animationObject)
        {
            try
            {
                if (animationObject == null)
                {
                    return;
                }

                LogManager.Info($"[缓存预计算] 开始为新选择的动画对象预计算: {animationObject.DisplayName}");

                // 在UI线程中更新开始状态
                await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    UpdateMainStatus("正在分析动画对象...", -1, true);
                }));

                // 在UI线程中执行Navisworks API操作（线程安全）
                bool success;
                try
                {
                    success = _pathAnimationManager.PrecomputeCollisionExclusions(animationObject);
                }
                catch (Exception ex)
                {
                    LogManager.Error($"[缓存预计算] 预计算失败: {ex.Message}", ex);
                    success = false;
                }

                // 异步更新UI状态
                await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    if (success)
                    {
                        var cacheStats = _pathAnimationManager.GetCacheStats();
                        UpdateMainStatus("动画对象分析完成");
                        LogManager.Info($"[缓存预计算] 预计算成功 - {cacheStats}");
                    }
                    else
                    {
                        UpdateMainStatus("动画对象分析失败，无法生成动画");
                        LogManager.Error("[缓存预计算] 预计算失败: 无法构建碰撞排除列表");
                    }
                }));
            }
            catch (Exception ex)
            {
                LogManager.Error($"[缓存预计算] 异步预计算过程异常: {ex.Message}", ex);
                await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    UpdateMainStatus("动画对象分析异常");
                }));
            }
        }

        #endregion

        #region 手工碰撞对象辅助方法

        private void OnManualCollisionTargetsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // 先更新序号
            UpdateManualCollisionTargetIndices();
            
            UpdateManualCollisionTargetSummary();
            OnPropertyChanged(nameof(HasManualCollisionTargets));
            OnPropertyChanged(nameof(IsManualTargetModeActive));
            System.Windows.Input.CommandManager.InvalidateRequerySuggested();
            RefreshManualTargetHighlights();
            SyncManualCollisionTargetsToAnimationManager();
            UpdateCanGenerateAnimation();  // 更新生成动画按钮状态
        }

        private void UpdateManualCollisionTargetSummary()
        {
            if (_manualCollisionTargets == null || _manualCollisionTargets.Count == 0)
            {
                ManualCollisionTargetSummary = "未指定碰撞检测对象";
                return;
            }

            var syncPart = _manualTargetsLastSyncTime.HasValue
                ? $" • 最近同步 {_manualTargetsLastSyncTime.Value:HH:mm:ss}"
                : string.Empty;
            ManualCollisionTargetSummary = $"{_manualCollisionTargets.Count} 个对象{syncPart}";
        }

        /// <summary>
        /// 更新手工碰撞对象的序号
        /// </summary>
        private void UpdateManualCollisionTargetIndices()
        {
            if (_manualCollisionTargets == null)
                return;

            for (int i = 0; i < _manualCollisionTargets.Count; i++)
            {
                _manualCollisionTargets[i].Index = i + 1;  // 序号从1开始
            }
        }

        private string BuildModelPath(ModelItem item)
        {
            try
            {
                var segments = new List<string>();
                var current = item;
                int guard = 0;

                while (current != null && guard < 15)
                {
                    var name = ModelItemAnalysisHelper.GetSafeDisplayName(current);
                    if (!string.IsNullOrWhiteSpace(name))
                    {
                        segments.Add(name.Trim());
                    }
                    current = current.Parent;
                    guard++;
                }

                segments.Reverse();
                return string.Join(" / ", segments);
            }
            catch
            {
                return "路径获取失败";
            }
        }

        /// <summary>
        /// 获取运动物体的 PathId 信息
        /// </summary>
        private (int ModelIndex, string PathId) GetObjectPathIdInfo()
        {
            try
            {
                if (SelectedAnimatedObject == null)
                {
                    return (0, null);
                }

                var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(SelectedAnimatedObject);
                return (pathId.ModelIndex, pathId.PathId);
            }
            catch (Exception ex)
            {
                LogManager.Warning($"[批处理] 无法获取运动对象的 PathId: {SelectedAnimatedObject.DisplayName}, 错误: {ex.Message}");
                return (0, null);
            }
        }

        private List<ModelItem> GetValidManualCollisionTargets(bool pruneInvalidEntries = false)
        {
            var validItems = new List<ModelItem>();
            if (_manualCollisionTargets == null)
            {
                return validItems;
            }

            var invalidItems = new List<ManualCollisionTargetViewModel>();
            foreach (var entry in _manualCollisionTargets)
            {
                if (entry?.ModelItem != null &&
                    ModelItemAnalysisHelper.IsModelItemValid(entry.ModelItem) &&
                    HasGeometryRecursive(entry.ModelItem))
                {
                    validItems.Add(entry.ModelItem);
                }
                else if (pruneInvalidEntries && entry != null)
                {
                    invalidItems.Add(entry);
                }
            }

            if (pruneInvalidEntries && invalidItems.Count > 0)
            {
                foreach (var invalid in invalidItems)
                {
                    _manualCollisionTargets.Remove(invalid);
                }

                if (invalidItems.Count > 0)
                {
                    UpdateMainStatus("部分手工碰撞对象已失效，已自动清理");
                }
            }

            return validItems;
        }

        private void SyncManualCollisionTargetsToAnimationManager(bool pruneInvalidEntries = false)
        {
            if (_pathAnimationManager == null)
                return;

            var targets = GetValidManualCollisionTargets(pruneInvalidEntries);
            if (IsManualCollisionTargetEnabled && targets.Count > 0)
            {
                _pathAnimationManager.SetManualCollisionTargets(targets, true);
            }
            else
            {
                _pathAnimationManager.SetManualCollisionTargets(null, false);
            }
        }

        private bool ManualTargetExists(ModelItem item)
        {
            if (item == null)
            {
                return false;
            }

            if (_manualCollisionTargets == null || _manualCollisionTargets.Count == 0)
            {
                return false;
            }

            foreach (var target in _manualCollisionTargets)
            {
                if (target == null)
                    continue;

                // 使用正确的 ModelItem 比较方法
                if (ModelItemAnalysisHelper.ModelItemEquals(target.ModelItem, item))
                    return true;
            }

            return false;
        }

        private void RefreshManualTargetHighlights(bool forceHighlight = false)
        {
            try
            {
                if (_clashIntegration == null)
                    return;

                var validItems = GetValidManualCollisionTargets();
                if (validItems.Count == 0)
                {
                    ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
                    return;
                }

                if (IsManualTargetModeActive || forceHighlight)
                {
                    ModelHighlightHelper.HighlightItems(ManualTargetsHighlightCategory, validItems);
                }
                else
                {
                    ModelHighlightHelper.ClearCategory(ManualTargetsHighlightCategory);
                }
            }
            catch (Exception ex)
            {
                LogManager.Warning($"[手工碰撞] 更新高亮失败: {ex.Message}");
            }
        }

        private void HandleManualCollisionModeChanged()
        {
            RefreshManualTargetHighlights();
            SyncManualCollisionTargetsToAnimationManager(true);
            UpdateCanGenerateAnimation();  // 更新生成动画按钮状态

            if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets)
            {
                UpdateMainStatus("手工模式已启用，请先选择碰撞检测对象");
            }
            else if (!IsManualCollisionTargetEnabled)
            {
                UpdateMainStatus("已切换回默认碰撞检测对象范围");
            }
        }

        #endregion

        #region 动画辅助方法

        /// <summary>
        /// 执行生成动画命令（同步执行，因为Navisworks API需要STA线程）
        /// </summary>
        private async void ExecuteGenerateAnimation()
        {
            // 保存当前光标
            var oldCursor = Mouse.OverrideCursor;
            
            try
            {
                if (!CanGenerateAnimation)
                {
                    UpdateMainStatus("无法生成动画：缺少必要条件");
                    LogManager.Warning("尝试生成动画但条件不满足");
                    return;
                }

                // 设置等待光标
                Mouse.OverrideCursor = Cursors.Wait;

                UpdateMainStatus("正在生成动画...", -1, true);
                LogManager.Info($"开始生成动画 - 模式: {(UseVirtualObject ? "虚拟物体" : "选择物体")}");

                var cacheStartTime = DateTime.Now;

                var manualModeEnabled = IsManualCollisionTargetEnabled;
                List<ModelItem> manualTargets = null;

                if (manualModeEnabled)
                {
                    manualTargets = GetValidManualCollisionTargets(pruneInvalidEntries: true);
                    if (manualTargets.Count == 0)
                    {
                        UpdateMainStatus("手工模式已启用，但没有可用的碰撞对象");
                        LogManager.Warning("[动画生成] 手工模式启用但没有有效对象");
                        return;
                    }
                }

                // 🔥 预计算动画对象的排除列表（仅在非虚拟物体、非手工模式下）
                if (!manualModeEnabled && !UseVirtualObject)
                {
                    UpdateMainStatus("正在分析动画对象...", -1, true);

                    // 设置检测容差（安全间隙通过CreateAnimation传入）
                    _pathAnimationManager.SetDetectionTolerance(_detectionTolerance);

                    var success = _pathAnimationManager.PrecomputeCollisionExclusions(SelectedAnimatedObject);

                    if (!success)
                    {
                        var msg = "[动画生成] 动画对象分析失败，无法继续生成动画";
                        LogManager.Error(msg);
                        UpdateMainStatus("生成失败：对象分析错误");
                        return;
                    }
                }
                else if (manualModeEnabled)
                {
                    UpdateMainStatus("使用手工碰撞对象，跳过预计算", -1, true);
                    LogManager.Info("[动画生成] 手工碰撞对象模式，已跳过对象预计算");
                }

                UpdateMainStatus("正在生成路径动画...", -1, true);

                // 设置动画参数到PathAnimationManager
                _pathAnimationManager.SetAnimationFrameRate(_animationFrameRate);
                _pathAnimationManager.SetCollisionDetectionAccuracy(CollisionDetectionAccuracy);
                _pathAnimationManager.SetMovementSpeed(MovementSpeed);
                _pathAnimationManager.SetDetectionTolerance(_detectionTolerance);

                // 将PathRouteViewModel的点转换为Point3D列表
                var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();

                ModelItem animatedObject = null;

                if (UseVirtualObject)
                {
                    // 使用虚拟物体：总是调用 ShowVirtualObject 来更新尺寸
                    LogManager.Info("[ExecuteGenerateAnimation] 显示并更新虚拟物体...");
                    UpdateMainStatus("正在显示虚拟物体...", -1, true);

                    VirtualObjectManager.Instance.ShowVirtualObject(
                        VirtualObjectLengthInMeters,
                        VirtualObjectWidthInMeters,
                        VirtualObjectHeightInMeters
                    );
                    animatedObject = VirtualObjectManager.Instance.CurrentVirtualObject;

                    if (animatedObject == null)
                    {
                        LogManager.Error("[动画生成] 虚拟物体显示失败");
                        UpdateMainStatus("生成失败：无法显示虚拟物体");
                        return;
                    }

                    // 注意：虚拟物体在选择时已经通过 MoveAnimatedObjectToPathStart 移动到路径起点
                    // 这里不需要重复移动，因为 CreateAnimation 内部还会再次调用 MoveObjectToPathStart
                    LogManager.Info($"使用虚拟物体: {animatedObject.DisplayName}");
                }
                else
                {
                    animatedObject = SelectedAnimatedObject;
                }

                // 统一准备碰撞检测（根据模式自动决定是否构建全局缓存）
                ClashDetectiveIntegration.PrepareCollisionDetection(animatedObject, manualModeEnabled, manualTargets);

                // 设置碰撞检测目标
                if (manualModeEnabled)
                {
                    _pathAnimationManager.SetManualCollisionTargets(manualTargets, true);
                }
                else
                {
                    _pathAnimationManager.SetManualCollisionTargets(null, false);
                }

                // 【统一使用CreateAnimation，完全复用现有逻辑】
                LogManager.Info($"[ExecuteGenerateAnimation] 准备创建动画: 路径名称='{CurrentPathRoute.Name}', ID='{CurrentPathRoute.Id}', " +
                               $"动画对象='{animatedObject.DisplayName}'");

                // 直接使用 CurrentPathRoute.Route
                var pathRoute = CurrentPathRoute.Route;

                if (pathRoute == null)
                {
                    LogManager.Error($"无法找到路径对象，ID: {CurrentPathRoute.Id}");
                    UpdateMainStatus("生成失败：找不到路径数据");
                    return;
                }

                // 先设置路径到动画管理器
                _pathAnimationManager.SetRoute(pathRoute);

                // 准备物体尺寸参数（从米转换为模型单位）
                var metersToModelUnits = Utils.UnitsConverter.GetMetersToUnitsConversionFactor();
                double vLength = UseVirtualObject ? VirtualObjectLengthInMeters * metersToModelUnits : 0;
                double vWidth = UseVirtualObject ? VirtualObjectWidthInMeters * metersToModelUnits : 0;
                double vHeight = UseVirtualObject ? VirtualObjectHeightInMeters * metersToModelUnits : 0;
                double safetyMarginModelUnits = _safetyMarginInMeters * metersToModelUnits;

                LogManager.Info($"[ExecuteGenerateAnimation] 准备调用CreateAnimation: UseVirtualObject={UseVirtualObject}, 物体尺寸: {vLength:F2}×{vWidth:F2}×{vHeight:F2}模型单位, 安全间隙: {safetyMarginModelUnits:F4}模型单位 (_safetyMarginInMeters={_safetyMarginInMeters:F4}米)");

                _pathAnimationManager.CreateAnimation(animatedObject, pathPoints, AnimationDuration, CurrentPathRoute.Name, CurrentPathRoute.Id, UseVirtualObject,
                    vLength, vWidth, vHeight, safetyMarginModelUnits);

                var totalElapsed = (DateTime.Now - cacheStartTime).TotalMilliseconds;
                LogManager.Info($"[动画生成] 动画生成完成，总耗时: {totalElapsed:F1}ms");
                LogManager.Info($"动画生成成功: 物体={animatedObject.DisplayName}, 路径={CurrentPathRoute.Name}");

                if (UseVirtualObject)
                {
                    LogManager.Info($"虚拟物体尺寸: {VirtualObjectLengthInMeters:F1}×{VirtualObjectWidthInMeters:F1}×{VirtualObjectHeightInMeters:F1}m");
                }

                if (IsManualCollisionTargetEnabled)
                {
                    RefreshManualTargetHighlights(forceHighlight: true);
                }

                // 动画生成完成，先恢复光标再显示分析对话框
                Mouse.OverrideCursor = oldCursor;
                oldCursor = null; // 标记已恢复，避免 finally 再次恢复

                // 🔥 自动生成动画后，自动高亮预计算碰撞结果并显示分析对话框
                await AnalyzeAndHighlightPrecomputedCollisionsAsync();

                // 更新状态
                UpdateAnimationButtonStates();
                UpdateMainStatus("动画生成成功");
            }
            catch (Exception ex)
            {
                UpdateMainStatus("动画生成失败");
                LogManager.Error($"生成动画时发生错误: {ex.Message}");

                LogManager.Error($"动画生成失败，详细错误信息：{ex}");
            }
            finally
            {
                // 恢复光标（如果还没有恢复）
                if (oldCursor != null)
                {
                    Mouse.OverrideCursor = oldCursor;
                }
            }
        }


        /// <summary>
        /// 更新移动物体信息
        /// </summary>
        private void UpdateAnimatedObjectInfo()
        {
            if (SelectedAnimatedObject != null)
            {
                SelectedAnimatedObjectName = SelectedAnimatedObject.DisplayName ?? "未命名物体";
                HasSelectedAnimatedObject = true;
            }
            else
            {
                SelectedAnimatedObjectName = "未选择移动物体";
                HasSelectedAnimatedObject = false;
            }
        }

        /// <summary>
        /// 计算本地三轴预旋转后的有效尺寸
        /// 返回值为模型单位（与Navisworks API一致）
        /// </summary>
        /// <returns>(沿路径, 垂直路径, 法线) - 模型单位</returns>
        private (double effectiveAlongPath, double effectiveAcrossPath, double effectiveNormalToPath) CalculateRotatedDimensions()
        {
            double forwardSize;
            double sideSize;
            double upSize;
            ModelAxisConvention axisConvention;

            if (UseVirtualObject)
            {
                double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
                forwardSize = VirtualObjectLengthInMeters * metersToUnits;
                sideSize = VirtualObjectWidthInMeters * metersToUnits;
                upSize = VirtualObjectHeightInMeters * metersToUnits;
                axisConvention = ModelAxisConvention.CreateVirtualObjectAssetConvention();
            }
            else if (SelectedAnimatedObject != null)
            {
                double metersToUnits = UnitsConverter.GetMetersToUnitsConversionFactor();
                forwardSize = _objectOriginalLength * metersToUnits;
                sideSize = _objectOriginalWidth * metersToUnits;
                upSize = _objectOriginalHeight * metersToUnits;

                var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
                if ((CurrentPathRoute?.PathType == NavisworksTransport.PathType.Ground ||
                     CurrentPathRoute?.PathType == NavisworksTransport.PathType.Hoisting) &&
                    _pathAnimationManager != null &&
                    _pathAnimationManager.TryGetCurrentRouteRealObjectPlanarProjectedExtents(
                        out double resolvedPlanarForward,
                        out double resolvedPlanarSide,
                        out double resolvedPlanarUp))
                {
                    double unitsToMetersForPlanar = UnitsConverter.GetUnitsToMetersConversionFactor();
                    LogManager.Debug(
                        $"[角度修正] 平面真实物体复用最终姿态尺寸: Forward={resolvedPlanarForward * unitsToMetersForPlanar:F2}m, " +
                        $"Side={resolvedPlanarSide * unitsToMetersForPlanar:F2}m, Up={resolvedPlanarUp * unitsToMetersForPlanar:F2}m, 角度={_objectRotationCorrection}");
                    return (resolvedPlanarForward, resolvedPlanarSide, resolvedPlanarUp);
                }
                if (CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail &&
                    _pathAnimationManager != null &&
                    _pathAnimationManager.TryGetCurrentRouteRealObjectRailProjectedExtents(
                        out double resolvedForward,
                        out double resolvedSide,
                        out double resolvedUp))
                {
                    double unitsToMetersForRail = UnitsConverter.GetUnitsToMetersConversionFactor();
                    LogManager.Debug(
                        $"[角度修正] Rail真实物体复用路径姿态尺寸: Forward={resolvedForward * unitsToMetersForRail:F2}m, " +
                        $"Side={resolvedSide * unitsToMetersForRail:F2}m, Up={resolvedUp * unitsToMetersForRail:F2}m, 角度={_objectRotationCorrection}");
                    return (resolvedForward, resolvedSide, resolvedUp);
                }

                axisConvention = CurrentPathRoute?.PathType == NavisworksTransport.PathType.Rail
                    ? ModelAxisConvention.CreateRailAssetConvention()
                    : ModelAxisConvention.CreateDefaultForHost(adapter.HostType);
            }
            else
            {
                return (0, 0, 0);
            }

            var hostAdapter = CoordinateSystemManager.Instance.CreateHostAdapter();
            Quaternion correctionQuaternion = UseVirtualObject
                ? hostAdapter.CreateCanonicalRotationCorrection(_objectRotationCorrection)
                : hostAdapter.CreateHostRotationCorrection(_objectRotationCorrection);
            var result = RotatedObjectExtentHelper.CalculateProjectedSemanticExtents(
                axisConvention,
                forwardSize,
                sideSize,
                upSize,
                correctionQuaternion);
            double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor();
            LogManager.Debug(
                $"[角度修正] 旋转后尺寸: 原始({forwardSize * unitsToMeters:F2}m×{sideSize * unitsToMeters:F2}m×{upSize * unitsToMeters:F2}m) -> " +
                $"有效({result.forwardExtent * unitsToMeters:F2}m×{result.sideExtent * unitsToMeters:F2}m×{result.upExtent * unitsToMeters:F2}m), 角度={_objectRotationCorrection}");

            return result;
        }

        /// <summary>
        /// 暴露实测 AABB 缓存给外部（如 PathEditingViewModel 的物体参数同步），
        /// 避免它们走 correction 投影导致失真。
        /// </summary>
        public bool TryGetCachedAabbSizes(out double forward, out double side, out double up)
        {
            if (_autoAabbSx.HasValue)
            {
                forward = _autoAabbSx.Value;
                side = _autoAabbSz.Value;
                up = _autoAabbSy.Value;
                return true;
            }
            forward = side = up = 0;
            return false;
        }

        /// <summary>
        /// 根据路径类型更新通行空间可视化
        /// 空轨和吊装路径默认打开通行空间（物体通行空间模式），地面路径不打开
        /// 根据选择的物体或虚拟物体尺寸更新通行空间渲染
        /// </summary>
        public void UpdatePassageSpaceVisualization()
        {
            try
            {
                if (CurrentPathRoute == null)
                {
                    LogManager.Debug("[通行空间可视化] 没有选择路径，跳过更新");
                    return;
                }

                // 计算旋转后的有效尺寸（考虑角度修正）
                double effectiveLength, effectiveWidth, effectiveHeight;
                if (_autoAabbSx.HasValue)
                {
                    // 自动调整/贴合地面后实测 AABB：物体已在最终姿态，直接使用世界 AABB 尺寸。
                    // 不清空：贴地后 correction 固定，后续刷新也应保持实测值，
                    // 避免 CalculateRotatedDimensions 用包含 CAD yaw 补偿的 correction 反推失真。
                    effectiveLength = _autoAabbSx.Value;
                    effectiveWidth  = _autoAabbSz.Value;
                    effectiveHeight = _autoAabbSy.Value;
                }
                else
                {
                    (effectiveLength, effectiveWidth, 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 + Math.Max(0, _objectGroundLiftHeightInMeters * metersToUnitsPassage); // 负lift不压缩高度
                        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 + Math.Max(0, _objectGroundLiftHeightInMeters * metersToUnitsPassage);  // 物体高度 + 上方间隙（负lift不压缩高度）
                        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, 上下偏移={_objectGroundLiftHeightInMeters:F2}m, 安全间隙={_safetyMarginInMeters:F2}m");

                        // 根据路径类型决定是否切换到物体通行空间模式
                        bool enableObjectSpace = false;
                        switch (CurrentPathRoute.PathType)
                        {
                            case NavisworksTransport.PathType.Rail:
                            case NavisworksTransport.PathType.Hoisting:
                                enableObjectSpace = true;
                                LogManager.Debug($"[通行空间可视化] 路径类型={CurrentPathRoute.PathType}（空中路径），切换到物体通行空间模式");
                                break;
                            case NavisworksTransport.PathType.Ground:
                                enableObjectSpace = false;
                                LogManager.Debug($"[通行空间可视化] 路径类型={CurrentPathRoute.PathType}，不切换到物体通行空间模式");
                                break;
                            default:
                                LogManager.Warning($"[通行空间可视化] 未知的路径类型: {CurrentPathRoute.PathType}");
                                return;
                        }

                        // 切换到物体通行空间模式（如果需要）
                        if (enableObjectSpace)
                        {
                            renderPlugin.VisualizationMode = PathVisualizationMode.ObjectSpace;
                            LogManager.Debug("[通行空间可视化] 已切换到物体通行空间模式");
                        }

                        // 刷新路径可视化以应用新的尺寸
                        var coreRoute = CurrentPathRoute.Route;
                        if (coreRoute != null)
                        {
                            PathPlanningManager.Instance.DrawRouteVisualization(coreRoute, isAutoPath: false);
                            LogManager.Debug("[通行空间可视化] 已刷新路径可视化");
                        }
            }
            catch (Exception ex)
            {
                LogManager.Error($"[通行空间可视化] 更新失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 递归检查ModelItem是否包含几何体
        /// </summary>
        private bool HasGeometryRecursive(ModelItem item)
        {
            if (item == null) return false;

            // 如果当前项有几何体，直接返回true
            if (item.HasGeometry) return true;

            // 递归检查子项
            foreach (ModelItem child in item.Children)
            {
                if (HasGeometryRecursive(child))
                    return true;
            }

            return false;
        }

        /// <summary>
        /// 更新是否可以生成动画的状态
        /// </summary>
        private void UpdateCanGenerateAnimation()
        {
            var hasPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;

            bool hasValidObject;
            if (UseVirtualObject)
            {
                // 使用虚拟物体时，只需要有效的物体尺寸
                hasValidObject = VirtualObjectLengthInMeters > 0 &&
                                VirtualObjectWidthInMeters > 0 &&
                                VirtualObjectHeightInMeters > 0;
            }
            else
            {
                // 使用选择物体时，需要已选择物体
                hasValidObject = SelectedAnimatedObject != null;
            }

            // 启用手工指定模式时，必须指定碰撞检测对象
            bool hasManualTargetsIfRequired = !IsManualCollisionTargetEnabled || HasManualCollisionTargets;
            
            CanGenerateAnimation = hasValidObject && hasPath && hasManualTargetsIfRequired;

            // 更新状态提示
            if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets)
            {
                UpdateMainStatus("手工模式已启用，请先选择碰撞检测对象");
            }
            else if (!hasPath && !hasValidObject)
            {
                UpdateMainStatus(UseVirtualObject ? "请选择动画路径" : "请选择移动物体和动画路径");
            }
            else if (!hasValidObject)
            {
                UpdateMainStatus(UseVirtualObject ? "虚拟物体参数无效" : "请选择移动物体");
            }
            else if (!hasPath)
            {
                UpdateMainStatus("请选择有效的动画路径");
            }
            else
            {
                UpdateMainStatus("可以生成动画");
            }

            // 同时更新动画按钮状态，因为对象或路径的变化会影响"开始动画"按钮的可用性
            UpdateAnimationButtonStates();
        }


        /// <summary>
        /// 计算当前路径的总长度
        /// </summary>
        private double GetPathLength()
        {
            if (CurrentPathRoute == null || CurrentPathRoute.Points.Count < 2)
            {
                return 0.0;
            }

            double totalLength = 0.0;
            var points = CurrentPathRoute.Points;

            for (int i = 0; i < points.Count - 1; i++)
            {
                var p1 = points[i];
                var p2 = points[i + 1];

                // 计算两点之间的欧几里得距离（模型单位）
                double dx = p2.X - p1.X;
                double dy = p2.Y - p1.Y;
                double dz = p2.Z - p1.Z;

                double segmentLengthInModelUnits = Math.Sqrt(dx * dx + dy * dy + dz * dz);

                // 转换为米单位
                double segmentLengthInMeters = NavisworksTransport.Utils.UnitsConverter.ConvertToMeters(segmentLengthInModelUnits);
                totalLength += segmentLengthInMeters;
            }

            return totalLength;
        }

        /// <summary>
        /// 统一更新动画按钮状态逻辑
        /// 根据当前的路径、动画对象和动画管理器状态来决定按钮的可用性
        /// </summary>
        private void UpdateAnimationButtonStates()
        {
            try
            {
                // 检查是否有有效的动画可以播放
                var hasValidPath = CurrentPathRoute != null && CurrentPathRoute.Points.Count >= 2;
                var hasValidAnimatedObject = SelectedAnimatedObject != null;
                var hasValidAnimation = hasValidPath && hasValidAnimatedObject;

                // 检查动画管理器当前状态
                var animationState = _pathAnimationManager?.CurrentState ?? NavisworksTransport.Core.Animation.AnimationState.Stopped;
                var isAnimating = _pathAnimationManager?.IsAnimating ?? false;

                // 检查动画是否已经生成（Ready状态表示动画已生成但未播放）
                var isAnimationReady = animationState == NavisworksTransport.Core.Animation.AnimationState.Ready ||
                                      animationState == NavisworksTransport.Core.Animation.AnimationState.Finished;

                // 根据动画状态和条件更新按钮状态
                switch (animationState)
                {
                    case NavisworksTransport.Core.Animation.AnimationState.Playing:
                        // 播放中：开始按钮禁用，暂停和停止按钮可用
                        CanStartAnimation = false;
                        CanPauseAnimation = true;
                        CanStopAnimation = true;
                        UpdateMainStatus("动画播放中");
                        break;

                    case NavisworksTransport.Core.Animation.AnimationState.Paused:
                        // 暂停中：开始按钮可用（显示为继续），暂停按钮禁用，停止按钮可用
                        CanStartAnimation = hasValidAnimation && isAnimationReady;
                        CanPauseAnimation = false;
                        CanStopAnimation = true;
                        UpdateMainStatus("动画已暂停");
                        break;

                    default:
                        // 停止或其他状态：需要同时满足有效动画条件和动画已生成条件
                        CanStartAnimation = hasValidAnimation && isAnimationReady;
                        CanPauseAnimation = false;
                        CanStopAnimation = false;

                        // 更新状态文本
                        if (IsManualCollisionTargetEnabled && !HasManualCollisionTargets)
                        {
                            UpdateMainStatus("手工模式已启用，请先选择碰撞检测对象");
                        }
                        else if (!hasValidPath && !hasValidAnimatedObject)
                        {
                            UpdateMainStatus("请选择路径和移动物体");
                        }
                        else if (!hasValidPath)
                        {
                            UpdateMainStatus("请选择有效路径（至少2个点）");
                        }
                        else if (!hasValidAnimatedObject)
                        {
                            UpdateMainStatus("请选择移动物体");
                        }
                        else if (!isAnimationReady)
                        {
                            UpdateMainStatus("请点击'生成动画'");
                        }
                        else
                        {
                            UpdateMainStatus("动画就绪");
                        }
                        break;
                }

                LogManager.Debug($"按钮状态已更新: CanStart={CanStartAnimation}, CanPause={CanPauseAnimation}, CanStop={CanStopAnimation}");
            }
            catch (Exception ex)
            {
                LogManager.Error($"更新动画按钮状态时发生错误: {ex.Message}", ex);
            }
        }


        /// <summary>
        /// 生成碰撞检测报告数据
        /// </summary>
        private CollisionReportData GenerateCollisionReport()
        {
            try
            {
                LogManager.Info("开始从Clash Detective获取碰撞检测结果");

                var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
                var documentClash = doc.GetClash();

                if (documentClash == null)
                {
                    LogManager.Warning("无法获取Clash Detective文档");
                    return new CollisionReportData { IsValid = false, ErrorMessage = "Clash Detective不可用" };
                }

                var reportData = new CollisionReportData
                {
                    IsValid = true,
                    GeneratedTime = DateTime.Now,
                    AnimatedObjectName = SelectedAnimatedObject?.DisplayName ?? "未知对象",
                    PathName = CurrentPathRoute?.Name ?? "未知路径"
                };

                // 获取所有动画相关的碰撞测试（包括新的分组测试）
                var animationTests = documentClash.TestsData.Tests.Cast<ClashTest>()
                    .Where(t => t.DisplayName.Contains("碰撞检测") )
                    .ToList();

                LogManager.Info($"找到 {animationTests.Count} 个碰撞检测");

                foreach (var test in animationTests)
                {
                    // 递归统计测试中的碰撞总数（包括分组内的）
                    var totalCollisionCount = GetTotalClashResultCountForReport(test);

                    var testInfo = new CollisionTestInfo
                    {
                        TestName = test.DisplayName,
                        TestType = test.TestType.ToString(),
                        Tolerance = test.Tolerance,
                        CollisionCount = totalCollisionCount,
                        Status = test.Status.ToString()
                    };

                    // 递归获取每个碰撞的详细信息（包括分组内的）
                    var allClashResults = GetAllClashResultsFromTest(test);
                    foreach (var clashResult in allClashResults)
                    {
                        var collision = new CollisionDetailInfo
                        {
                            CollisionId = clashResult.Guid.ToString(),
                            Distance = clashResult.Distance,
                            Status = clashResult.Status.ToString()
                        };

                        // 获取碰撞对象信息
                        if (clashResult.CompositeItem1 != null)
                        {
                            collision.Object1Name = clashResult.CompositeItem1.DisplayName;
                            var point = clashResult.CompositeItem1.BoundingBox();
                            collision.Object1Position = $"({point.Center.X:F2}, {point.Center.Y:F2}, {point.Center.Z:F2})";
                        }

                        if (clashResult.CompositeItem2 != null)
                        {
                            collision.Object2Name = clashResult.CompositeItem2.DisplayName;
                            var point = clashResult.CompositeItem2.BoundingBox();
                            collision.Object2Position = $"({point.Center.X:F2}, {point.Center.Y:F2}, {point.Center.Z:F2})";
                        }

                        // 获取碰撞中心点
                        if (clashResult.Center != null)
                        {
                            collision.CollisionCenter = $"({clashResult.Center.X:F2}, {clashResult.Center.Y:F2}, {clashResult.Center.Z:F2})";
                        }

                        testInfo.Collisions.Add(collision);
                    }

                    reportData.Tests.Add(testInfo);
                }

                // 统计总体信息
                reportData.TotalTests = reportData.Tests.Count;
                reportData.TotalCollisions = reportData.Tests.Sum(t => t.CollisionCount);

                LogManager.Info($"碰撞报告生成完成：{reportData.TotalTests}个测试，{reportData.TotalCollisions}个碰撞");
                return reportData;
            }
            catch (Exception ex)
            {
                LogManager.Error($"生成碰撞报告异常: {ex.Message}");
                return new CollisionReportData { IsValid = false, ErrorMessage = ex.Message };
            }
        }

        /// <summary>
        /// 显示碰撞报告
        /// </summary>
        private async Task ShowCollisionReport(CollisionReportData reportData)
        {
            try
            {
                // 更新UI摘要信息
                await _uiStateManager.ExecuteUIUpdateAsync(() =>
                {
                    if (reportData.IsValid)
                    {
                        // 碰撞报告已生成，显示 Clash Detective 权威结果
                        UpdateMainStatus("碰撞报告已生成");
                    }
                    else
                    {
                        UpdateMainStatus($"报告生成失败：{reportData.ErrorMessage}");
                    }
                });

                // 如果报告有效且有碰撞数据，用户可通过"查看碰撞报告"按钮查看详细信息
                if (reportData.IsValid && reportData.TotalCollisions > 0)
                {
                    LogManager.Info($"碰撞检测完成，发现 {reportData.TotalCollisions} 个碰撞问题。用户可通过报告按钮查看详细信息");
                }
                else if (reportData.IsValid && reportData.TotalCollisions == 0)
                {
                    // 没有碰撞时显示简单提示
                    LogManager.Info("未发现碰撞问题，路径规划良好");
                    System.Windows.MessageBox.Show(
                        "未发现碰撞问题，路径规划良好！",
                        "碰撞检测结果",
                        System.Windows.MessageBoxButton.OK,
                        System.Windows.MessageBoxImage.Information);
                }
                else
                {
                    // 报告无效时的错误处理
                    LogManager.Error($"碰撞报告数据无效: {reportData.ErrorMessage}");
                    System.Windows.MessageBox.Show(
                        $"碰撞报告生成失败：{reportData.ErrorMessage}",
                        "错误",
                        System.Windows.MessageBoxButton.OK,
                        System.Windows.MessageBoxImage.Error);
                }
            }
            catch (Exception ex)
            {
                LogManager.Error($"显示碰撞报告异常: {ex.Message}");
                System.Windows.MessageBox.Show(
                    $"显示碰撞报告时发生异常：{ex.Message}",
                    "错误",
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Error);
            }
        }

        #endregion


        #region 文档管理

        /// <summary>
        /// 获取单例实例
        /// </summary>
        private static AnimationControlViewModel _instance;
        public static AnimationControlViewModel Instance
        {
            get { return _instance; }
            set { _instance = value; }
        }

        /// <summary>
        /// 文档失效时的处理
        /// </summary>
        private void OnDocumentInvalidated(object sender, EventArgs e)
        {
            try
            {
                LogManager.Info("[AnimationControlViewModel] 文档失效，重置状态");
                Reset();
            }
            catch (Exception ex)
            {
                LogManager.Error($"[AnimationControlViewModel] 处理文档失效失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 文档就绪时的处理
        /// </summary>
        private void OnDocumentReady(object sender, EventArgs e)
        {
            try
            {
                LogManager.Info("[AnimationControlViewModel] 文档就绪，启用功能");

                // 重新启用UI
                CanStartAnimation = true;
                CanGenerateAnimation = true;

                // 🔥 文档就绪时刷新碰撞检测历史列表
                RefreshClashDetectiveResultsList();

                UpdateMainStatus("文档已加载，就绪");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[AnimationControlViewModel] 处理文档就绪失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 重置视图模型状态
        /// </summary>
        public void Reset()
        {
            try
            {
                LogManager.Info("[AnimationControlViewModel] 重置状态");

                // 停止动画
                if (_pathAnimationManager != null && _pathAnimationManager.IsAnimating)
                {
                    _pathAnimationManager.CancelAnimation();
                }

                // 重置角度修正值为0
                ObjectRotationCorrection = LocalEulerRotationCorrection.Zero;
                LogManager.Debug("[重置状态] 已重置角度修正值为0");

                // 清空选中对象
                SelectedAnimatedObject = null;
                SelectedAnimatedObjectName = string.Empty;
                CurrentPathRoute = null;

                // 禁用按钮
                CanStartAnimation = false;
                CanPauseAnimation = false;
                CanStopAnimation = false;
                CanGenerateAnimation = false;
                HasSelectedAnimatedObject = false;

                // 清空碰撞结果
                HasClashDetectiveResults = false;
                ModelHighlightHelper.ClearCollisionHighlights();

                UpdateMainStatus("已重置");
            }
            catch (Exception ex)
            {
                LogManager.Error($"[AnimationControlViewModel] 重置失败: {ex.Message}");
            }
        }

        #endregion

        #region 资源清理

        private bool _disposed = false;

        /// <summary>
        /// 清理资源
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 清理资源
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    try
                    {
                        LogManager.Info("开始清理AnimationControlViewModel资源");

                        // 1. 取消事件订阅（在停止动画之前，避免事件处理中访问已释放的对象）

                        // 取消配置变更事件订阅
                        UnsubscribeFromConfigChanges();

                        // 取消DocumentStateManager事件订阅
                        try
                        {
                            DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated;
                            DocumentStateManager.Instance.DocumentReady -= OnDocumentReady;
                            LogManager.Debug("DocumentStateManager事件订阅取消完成");
                        }
                        catch (Exception docEx)
                        {
                            LogManager.Warning($"取消DocumentStateManager事件订阅时出现警告: {docEx.Message}");
                        }

                        if (_pathAnimationManager != null)
                        {
                            try
                            {
                                _pathAnimationManager.ProgressChanged -= OnAnimationProgressChanged;
                                _pathAnimationManager.StateChanged -= OnAnimationStateChanged;
                                LogManager.Debug("PathAnimationManager事件订阅取消完成");
                            }
                            catch (Exception eventEx)
                            {
                                LogManager.Warning($"取消PathAnimationManager事件订阅时出现警告: {eventEx.Message}");
                            }
                        }

                        if (_clashIntegration != null)
                        {
                            try
                            {
                                _clashIntegration.CollisionDetected -= OnCollisionDetected;
                                _clashIntegration.ClashDetectiveResultSaved -= OnClashDetectiveResultSaved;
                                LogManager.Debug("碰撞检测事件订阅取消完成");
                            }
                            catch (Exception eventEx)
                            {
                                LogManager.Warning($"取消碰撞检测事件订阅时出现警告: {eventEx.Message}");
                            }
                        }

                        if (_manualCollisionTargets != null)
                        {
                            _manualCollisionTargets.CollectionChanged -= OnManualCollisionTargetsChanged;
                            _manualCollisionTargets.Clear();
                        }


                        // 2. 不再清理PathAnimationManager - 现在使用单例模式，由应用程序生命周期管理
                        // 注意：PathAnimationManager.GetInstance()返回的是单例实例，
                        // 不应该在这里Dispose，否则会影响其他使用该单例的地方
                        if (_pathAnimationManager != null)
                        {
                            try
                            {
                                // 只清理事件订阅和停止动画，不调用Dispose，也不重置位置(避免访问已释放对象)
                                _pathAnimationManager.ShutdownAnimation();
                                LogManager.Debug("PathAnimationManager已执行关闭清理");
                            }
                            catch (Exception resetEx)
                            {
                                LogManager.Warning($"执行PathAnimationManager关闭清理时出现警告: {resetEx.Message}");
                            }
                        }

                        // 3. 清理PathAnimationManager缓存（缓存功能已迁移到PathAnimationManager）
                        // PathAnimationManager的Dispose已经包含了ClearExclusionCache的调用

                        LogManager.Info("AnimationControlViewModel资源清理完成");
                    }
                    catch (Exception ex)
                    {
                        LogManager.Error($"AnimationControlViewModel资源清理过程中发生异常: {ex.Message}");
                        // 即使清理过程中发生异常，也不应该抛出，因为这会干扰应用程序的正常关闭
                    }
                }

                _disposed = true;
            }
        }

        /// <summary>
        /// 递归统计测试中的碰撞结果总数（包括分组内的结果）- 用于报告
        /// </summary>
        private int GetTotalClashResultCountForReport(ClashTest test)
        {
            if (test == null) return 0;

            int totalCount = 0;
            foreach (var child in test.Children)
            {
                if (child is ClashResult)
                {
                    totalCount++;
                }
                else if (child is ClashResultGroup group)
                {
                    // 递归统计分组内的结果
                    totalCount += CountClashResultsInGroupForReport(group);
                }
            }

            return totalCount;
        }

        /// <summary>
        /// 递归统计分组内的碰撞结果数量 - 用于报告
        /// </summary>
        private int CountClashResultsInGroupForReport(ClashResultGroup group)
        {
            if (group == null) return 0;

            int count = 0;
            foreach (var child in group.Children)
            {
                if (child is ClashResult)
                {
                    count++;
                }
                else if (child is ClashResultGroup nestedGroup)
                {
                    // 递归处理嵌套分组
                    count += CountClashResultsInGroupForReport(nestedGroup);
                }
            }

            return count;
        }

        /// <summary>
        /// 递归获取测试中的所有碰撞结果（包括分组内的）- 用于报告
        /// </summary>
        private List<ClashResult> GetAllClashResultsFromTest(ClashTest test)
        {
            var results = new List<ClashResult>();
            if (test == null) return results;

            foreach (var child in test.Children)
            {
                if (child is ClashResult result)
                {
                    results.Add(result);
                }
                else if (child is ClashResultGroup group)
                {
                    // 递归获取分组内的结果
                    results.AddRange(GetAllClashResultsFromGroup(group));
                }
            }

            return results;
        }

        /// <summary>
        /// 递归获取分组内的所有碰撞结果 - 用于报告
        /// </summary>
        private List<ClashResult> GetAllClashResultsFromGroup(ClashResultGroup group)
        {
            var results = new List<ClashResult>();
            if (group == null) return results;

            foreach (var child in group.Children)
            {
                if (child is ClashResult result)
                {
                    results.Add(result);
                }
                else if (child is ClashResultGroup nestedGroup)
                {
                    // 递归处理嵌套分组
                    results.AddRange(GetAllClashResultsFromGroup(nestedGroup));
                }
            }

            return results;
        }

        /// <summary>
        /// 添加到批处理队列
        /// </summary>
        private async void ExecuteAddToBatchQueue()
        {
            try
            {
                if (!CanGenerateAnimation)
                {
                    UpdateMainStatus("无法添加到批处理：缺少必要条件");
                    LogManager.Warning("尝试添加到批处理但条件不满足");
                    System.Windows.MessageBox.Show(
                        "无法添加到批处理：缺少必要条件（路径未选择或路径点不足）",
                        "添加失败",
                        System.Windows.MessageBoxButton.OK,
                        System.Windows.MessageBoxImage.Warning);
                    return;
                }

                // 🔥 检查排除列表是否已设置
                var excludedObjects = _pathAnimationManager?.GetExcludedObjects();
                if (excludedObjects == null || excludedObjects.Count == 0)
                {
                    LogManager.Warning("[批处理] 排除列表为空，提示用户");
                    var result = System.Windows.MessageBox.Show(
                        "排除列表为空。对于复杂路径，建议先运行\"生成动画\"进行预计算碰撞分析，设置排除列表后再添加到批处理。\n\n" +
                        "• 是：继续添加（不推荐，可能检测时间过长）\n" +
                        "• 否：取消添加，先去设置排除列表\n\n" +
                        "提示：排除列表可以显著减少复杂路径的碰撞检测时间。",
                        "添加到批处理确认",
                        System.Windows.MessageBoxButton.YesNo,
                        System.Windows.MessageBoxImage.Warning
                    );
                    
                    if (result == System.Windows.MessageBoxResult.No)
                    {
                        LogManager.Info("[批处理] 用户选择取消添加，去设置排除列表");
                        return;
                    }
                    
                    LogManager.Info("[批处理] 用户选择继续添加（不使用排除列表）");
                }

                LogManager.Info($"[批处理] 添加路径到批处理队列: {CurrentPathRoute.Name}");

                // 为批处理创建独立的检测记录快照，保存当前配置、排除列表和手工目标
                int detectionRecordId = CreateCollisionDetectionRecordSnapshot("批处理任务创建", bindToAnimationManager: false);
                LogManager.Info($"[批处理] 已创建批处理检测记录 (Id={detectionRecordId})");

                // 创建批处理队列项
                // 注意：虚拟物体尺寸需要从米转换为模型单位存入数据库
                double metersToUnitsForQueue = UnitsConverter.GetMetersToUnitsConversionFactor();
                var queueItem = new BatchQueueItem
                {
                    RouteId = CurrentPathRoute.Id,
                    Status = BatchQueueStatus.Pending,
                    CreatedTime = DateTime.Now,

                    // 动画配置
                    FrameRate = _animationFrameRate,
                    DurationSeconds = AnimationDuration,

                    // 碰撞检测配置（米 → 模型单位）
                    DetectionTolerance = _detectionTolerance * metersToUnitsForQueue,

                    // 运动物体配置（米 → 模型单位，数据库存储模型单位）
                    IsVirtualObject = UseVirtualObject,
                    VirtualObjectLength = VirtualObjectLengthInMeters * metersToUnitsForQueue,
                    VirtualObjectWidth = VirtualObjectWidthInMeters * metersToUnitsForQueue,
                    VirtualObjectHeight = VirtualObjectHeightInMeters * metersToUnitsForQueue,

                    // 被检测项配置
                    DetectAllObjects = !IsManualCollisionTargetEnabled,

                    // 角度修正配置
                    ObjectRotationCorrection = _objectRotationCorrection.ZDegrees,

                    // 🔥 关联的检测记录ID
                    DetectionRecordId = detectionRecordId
                };

                // 调用批处理管理器添加到队列
                var batchQueueManager = Core.BatchQueueManager.Instance;

                // 确保BatchQueueManager有PathPlanningManager（用于获取数据库）
                if (_pathPlanningManager != null)
                {
                    batchQueueManager.SetPathPlanningManager(_pathPlanningManager);
                }

                var itemId = await batchQueueManager.AddQueueItemAsync(queueItem);

                // 存储运动物体到 ModelItemReferences 表
                if (!UseVirtualObject && SelectedAnimatedObject != null)
                {
                    LogManager.Info($"[批处理] 准备存储运动物体: {SelectedAnimatedObject.DisplayName}");
                    var pathIdInfo = GetObjectPathIdInfo();
                    LogManager.Info($"[批处理] 运动物体 PathId: ModelIndex={pathIdInfo.ModelIndex}, PathId={pathIdInfo.PathId}");

                    // 只要 PathId 不为空就认为是有效的（ModelIndex=0 表示主模型，也是有效的）
                    if (!string.IsNullOrEmpty(pathIdInfo.PathId))
                    {
                        var db = _pathPlanningManager?.GetPathDatabase();
                        LogManager.Info($"[批处理] PathDatabase 是否为 null: {db == null}");

                        if (db != null)
                        {
                            await db.AddModelItemReferenceAsync(
                                itemId,
                                "BatchQueueItem",
                                pathIdInfo.ModelIndex,
                                pathIdInfo.PathId,
                                SelectedAnimatedObject.DisplayName,
                                SelectedAnimatedObject.DisplayName,
                                "MovingObject"
                            );
                            LogManager.Info($"[批处理] 已存储运动物体: {SelectedAnimatedObject.DisplayName}");
                        }
                    }
                    else
                    {
                        LogManager.Warning($"[批处理] 运动物体 PathId 无效，跳过存储");
                    }
                }
                else
                {
                    LogManager.Info($"[批处理] 跳过存储运动物体: UseVirtualObject={UseVirtualObject}, SelectedAnimatedObject 是否为 null={SelectedAnimatedObject == null}");
                }

                // 存储手工指定的碰撞检测目标到 ModelItemReferences 表
                if (IsManualCollisionTargetEnabled)
                {
                    var db = _pathPlanningManager?.GetPathDatabase();
                    if (db != null)
                    {
                        var validItems = GetValidManualCollisionTargets(pruneInvalidEntries: true);
                        foreach (var item in validItems)
                        {
                            var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(item);
                            await db.AddModelItemReferenceAsync(
                                itemId,
                                "BatchQueueItem",
                                pathId.ModelIndex,
                                pathId.PathId,
                                item.DisplayName,
                                item.DisplayName,
                                "CollisionTarget"
                            );
                        }
                        LogManager.Info($"[批处理] 已存储 {validItems.Count} 个手工指定碰撞目标");
                    }
                }

                // 🔥 注意：排除列表和手工目标已通过 DetectionRecordId 关联到 CollisionDetectionRecords 表
                // 不需要再保存到 ModelItemReferences 表

                LogManager.Info($"[批处理] 已创建队列项: {CurrentPathRoute.Name}, " +
                                $"虚拟物体: {queueItem.IsVirtualObject}, " +
                                $"帧率: {queueItem.FrameRate}, " +
                                $"时长: {queueItem.DurationSeconds:F2}秒, " +
                                $"角度修正: {_objectRotationCorrection}, " +
                                $"ID: {itemId}");

                UpdateMainStatus($"已添加到批处理队列: {CurrentPathRoute.Name}");

                // 显示成功提示
                System.Windows.MessageBox.Show(
                    $"已成功添加到批处理队列：{CurrentPathRoute.Name}\n" +
                    $"队列项ID: {itemId}",
                    "添加成功",
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Information);
            }
            catch (Exception ex)
            {
                LogManager.Error($"添加到批处理失败: {ex.Message}", ex);
                UpdateMainStatus($"添加失败: {ex.Message}");

                // 显示失败提示
                System.Windows.MessageBox.Show(
                    $"添加到批处理队列失败：{ex.Message}",
                    "添加失败",
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Error);
            }
        }

        #endregion
    }
}
