using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Threading; using Autodesk.Navisworks.Api; using Autodesk.Navisworks.Api.Clash; using NavisworksTransport.Core.Config; using NavisworksTransport.Core.Spatial; using NavisworksTransport.Utils; using NavisApplication = Autodesk.Navisworks.Api.Application; namespace NavisworksTransport.Core.Animation { /// /// 定义动画播放的状态 /// public enum AnimationState { Idle, // 空闲,未生成动画 Ready, // 已就绪,动画已生成但未播放 Playing, // 播放中 Paused, // 暂停 Stopped, // 已停止 Finished // 已完成 } /// /// 碰撞对象对,用于去重 /// public class CollisionPair : IEquatable { public ModelItem AnimatedObject { get; set; } public ModelItem CollisionObject { get; set; } public bool Equals(CollisionPair other) { if (other == null) return false; return ReferenceEquals(AnimatedObject, other.AnimatedObject) && ReferenceEquals(CollisionObject, other.CollisionObject); } public override bool Equals(object obj) { return Equals(obj as CollisionPair); } public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 31 + (AnimatedObject?.GetHashCode() ?? 0); hash = hash * 31 + (CollisionObject?.GetHashCode() ?? 0); return hash; } } } /// /// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果 /// 注意:由于Navisworks API限制,无法直接使用Animator API,因此使用OverridePermanentTransform实现动画 /// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务 /// /// /// 动画帧数据(包含位置、朝向和碰撞信息) /// public class AnimationFrame { public int Index { get; set; } // 帧索引 public double Progress { get; set; } // 进度(0-1) public Point3D Position { get; set; } // 该帧的位置 public double YawRadians { get; set; } // 绕Z轴的偏航角(弧度) public List Collisions { get; set; } // 该帧的碰撞结果 public bool HasCollision => Collisions?.Count > 0; public AnimationFrame() { Collisions = new List(); YawRadians = 0.0; } } public class PathAnimationManager { private static PathAnimationManager _instance; private static readonly HashSet _completedCollisionTests = new HashSet(); // 记录已完成碰撞检测的动画配置 private static readonly Dictionary> _animationFrameCache = new Dictionary>(); // 动画帧缓存 private static readonly Dictionary> _collisionResultCache = new Dictionary>(); // 碰撞结果缓存 private ModelItem _animatedObject; private bool _isVirtualVehicle = false; // 是否使用虚拟车辆 private double _virtualVehicleLength = 0; // 虚拟车辆长度(米) private double _virtualVehicleWidth = 0; // 虚拟车辆宽度(米) private double _virtualVehicleHeight = 0; // 虚拟车辆高度(米) private List _pathPoints; private List _manualCollisionTargets = new List(); private bool _manualCollisionOverrideEnabled = false; // === 角度修正 === private double _objectRotationCorrection = 0.0; // 物体角度修正值(度,顺时针) // === 碰撞排除列表缓存管理(从LogisticsAnimationManager迁移)=== private ModelItem _currentCachedAnimationObject; private List _cachedExclusionList; private DateTime _cacheCreatedTime; private readonly TimeSpan _cacheValidDuration = TimeSpan.FromMinutes(30); // 缓存有效期30分钟 // === 预计算动画系统 === private List _animationFrames; // 所有帧数据 private int _currentFrameIndex = 0; // 当前帧索引 private List _allCollisionResults; // 所有碰撞结果(不去重) // === Human-in-the-Loop 排除列表 === // 注意:排除列表是实时管理的,不与动画缓存绑定。用户选择排除哪些物体后, // 这些物体会一直生效直到被清除或手动移除,不受动画缓存影响。 private HashSet _excludedObjects = new HashSet(); // 用户排除的物体列表 private bool _lastHighlightState = false; // 上一帧的高亮状态 private HashSet _lastCollisionObjects = new HashSet(); // 上一帧碰撞对象的集合 // === 路径相关 === private PathRoute _route = null; // 路径引用 // === 碰撞测试优化 === private string _currentAnimationHash; // 当前动画配置的哈希值 // === 动画播放机制 === private double _frameInterval; // 帧间隔(毫秒) private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间 private DispatcherTimer _animationTimer; // 备用DispatcherTimer定时器 // === 动画参数 === private double _animationDuration; // 动画总时长(秒) private DateTime _animationStartTime; private int _animationFrameRate; // 动画帧率(默认30FPS) private double _collisionDetectionAccuracy; // 检测精度(内部存储:模型单位/帧) private double _movementSpeed; // 运动速度(仅用于显示,单位:米/秒) private double _detectionTolerance; // 检测容差(米,从UI设置) private double _safetyMargin; // 安全间隙(模型单位,从CreateAnimation传入) private string _pathName; // 路径名称 private string _currentRouteId; // 当前路由ID // === 双向播放和步进控制 === private int _playbackDirection = 1; // 播放方向:1=正向,-1=反向 private double _playbackSpeed = 1.0; // 播放速度倍率 // === 性能监控 === private int _fpsFrameCount = 0; private DateTime _fpsCounterStart = DateTime.Now; private double _actualFPS = 0; private Transform3D _originalTransform; private Point3D _originalCenter; // 存储部件的原始中心位置 private Point3D _currentPosition; // 存储部件的当前位置 private AnimationState _currentState = AnimationState.Idle; private double _pausedProgress = 0.0; // 暂停时的进度(0-1之间) private double _currentYaw = 0.0; // 当前偏航角(弧度) // TimeLiner 集成 private TimeLinerIntegrationManager _timeLinerManager; private string _currentTaskId; // === 调试辅助 === private static HashSet _arcInfoPrinted = new HashSet(); // 记录已打印信息的圆弧段索引 // === 物体状态保存和恢复 === private Point3D _savedObjectPosition; private double _savedObjectYaw; private bool _hasSavedObjectState = false; /// /// 当前正在进行动画的对象 /// public ModelItem AnimatedObject => _animatedObject; // --- 新增事件 --- /// /// 当动画状态发生改变时触发 /// public event EventHandler StateChanged; /// /// 当动画进度更新时触发 (0.0-100.0) /// public event EventHandler ProgressChanged; public PathAnimationManager() { _pathPoints = new List(); _animationFrames = new List(); _allCollisionResults = new List(); // 🔥 从配置初始化动画参数 var config = ConfigManager.Instance.Current; _animationFrameRate = config.Animation.FrameRate; _animationDuration = config.Animation.DurationSeconds; // 初始化动画模式 _frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔 _lastFrameTime = DateTime.MinValue; _fpsCounterStart = DateTime.Now; // 初始化备用DispatcherTimer _animationTimer = new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(_frameInterval) }; _animationTimer.Tick += OnTimerTick; // 初始化 TimeLiner 集成 try { _timeLinerManager = new TimeLinerIntegrationManager(); LogManager.Info($"PathAnimationManager 初始化完成 - DispatcherTimer模式, 目标FPS: {_animationFrameRate}"); } catch (Exception ex) { LogManager.Warning($"TimeLiner 集成初始化失败,将使用基础动画功能: {ex.Message}"); _timeLinerManager = null; } // 订阅文档状态事件 DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated; // 订阅配置变更事件 SubscribeToConfigChanges(); } #region 配置变更处理 /// /// 订阅配置变更事件 /// private void SubscribeToConfigChanges() { try { ConfigManager.Instance.CategoryConfigurationChanged += OnCategoryConfigurationChanged; LogManager.Info("PathAnimationManager 已订阅配置变更事件"); } catch (Exception ex) { LogManager.Error($"订阅配置变更事件失败: {ex.Message}", ex); } } /// /// 取消订阅配置变更事件 /// private void UnsubscribeFromConfigChanges() { try { ConfigManager.Instance.CategoryConfigurationChanged -= OnCategoryConfigurationChanged; LogManager.Info("PathAnimationManager 已取消订阅配置变更事件"); } catch (Exception ex) { LogManager.Warning($"取消订阅配置变更事件时发生异常: {ex.Message}"); } } /// /// 配置变更事件处理 /// private void OnCategoryConfigurationChanged(object sender, Tuple args) { try { var category = args.Item1; var eventArgs = args.Item2; // 处理 Animation 类别变更 if (category == ConfigCategory.Animation || category == ConfigCategory.All) { LogManager.Info("收到 Animation 配置变更通知,正在更新动画参数..."); var config = ConfigManager.Instance.Current; bool needReset = false; // 检查帧率是否变更 if (_animationFrameRate != config.Animation.FrameRate) { _animationFrameRate = config.Animation.FrameRate; _frameInterval = 1000.0 / _animationFrameRate; // 更新定时器间隔 if (_animationTimer != null) { _animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval); } needReset = true; LogManager.Info($"动画帧率已更新: {_animationFrameRate} fps, 帧间隔: {_frameInterval:F1} ms"); } // 检查持续时间是否变更 if (_animationDuration != config.Animation.DurationSeconds) { _animationDuration = config.Animation.DurationSeconds; needReset = true; LogManager.Info($"动画持续时间已更新: {_animationDuration} 秒"); } // 如果动画正在运行,需要重置 if (needReset && (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)) { // 停止当前动画,让用户重新生成 ShutdownAnimation(); LogManager.Info("动画参数已变更,当前动画已停止,请重新生成动画"); } } } catch (Exception ex) { LogManager.Error($"处理配置变更事件失败: {ex.Message}", ex); } } #endregion /// /// 获取单例实例 /// public static PathAnimationManager GetInstance() { if (_instance == null) { _instance = new PathAnimationManager(); } return _instance; } /// /// 清理当前所有的动画生成结果(帧列表、碰撞结果、状态) /// public void ClearAnimationResults() { _animationFrames = null; _allCollisionResults = null; _arcInfoPrinted.Clear(); // 清除圆弧段信息打印记录 SetState(AnimationState.Idle); LogManager.Info("[PathAnimationManager] 动画数据和状态已重置为 Idle"); } /// /// 仅仅将物理模型同步到路径起点(不清理数据,不重置状态) /// 用于加载碰撞检测结果时 /// public void SyncToPathStart(ModelItem item, List points) { MoveVehicleToPathStart(item, points); } /// /// 将动画对象彻底恢复到 CAD 原始位置(清除所有覆盖变换) /// 常用于模式切换或彻底清除动画干扰 /// public void RestoreObjectToCADPosition() { try { var doc = NavisApplication.ActiveDocument; if (doc == null) return; // 🔥 支持虚拟车辆的恢复 ModelItem objectToRestore = null; bool isVirtual = _isVirtualVehicle; if (isVirtual) { objectToRestore = VirtualVehicleManager.Instance.CurrentVirtualVehicle; if (objectToRestore == null) { LogManager.Warning("[归位] 虚拟车辆未激活,跳过恢复"); return; } } else { objectToRestore = _animatedObject; if (objectToRestore == null) { LogManager.Warning("[归位] 没有动画对象,跳过恢复"); return; } } var modelItems = new ModelItemCollection { objectToRestore }; // 1. 彻底清除 Navisworks 中的所有覆盖变换,强制恢复到设计文件定义的原始状态 doc.Models.ResetPermanentTransform(modelItems); // 2. 重置内部跟踪变量(同步到原始状态) // 注意:ResetPermanentTransform 后物体的 Transform 属性会自动变回原始值 _currentYaw = ModelItemTransformHelper.GetYawFromTransform(objectToRestore.Transform); var originalBoundingBox = objectToRestore.BoundingBox(); _currentPosition = new Point3D( originalBoundingBox.Center.X, originalBoundingBox.Center.Y, originalBoundingBox.Min.Z ); string objectName = isVirtual ? "虚拟车辆" : objectToRestore.DisplayName; LogManager.Info($"[归位] {objectName} 已彻底恢复到原始位置, yaw={_currentYaw:F3}"); } catch (Exception ex) { LogManager.Error($"[归位] 恢复物体到原始位置失败: {ex.Message}"); } } /// /// 生成动画帧 /// /// 要动画化的模型对象 /// 路径点列表 /// 动画持续时间(秒) public void SetRoute(PathRoute route) { _route = route; } /// /// 设置动画对象(批处理专用) /// public void SetAnimatedObject(ModelItem animatedObject) { _animatedObject = animatedObject; _isVirtualVehicle = (animatedObject == null); } /// /// 设置虚拟车辆参数(批处理专用) /// public void SetVirtualVehicleParameters(bool isVirtualVehicle, double length, double width, double height) { _isVirtualVehicle = isVirtualVehicle; _virtualVehicleLength = length; _virtualVehicleWidth = width; _virtualVehicleHeight = height; } /// /// 设置动画参数(批处理专用) /// public void SetAnimationParameters(int frameRate, double duration, double detectionTolerance) { _animationFrameRate = frameRate; _animationDuration = duration; // 注意:预计算使用安全间隙,不使用检测容差 } /// /// 检测容差(米)- 用于ClashDetective精确检测 /// public double DetectionTolerance { get { var config = ConfigManager.Instance.Current; return config.Animation.DetectionToleranceMeters; } } /// /// 设置手工碰撞目标(批处理专用) /// public void SetManualCollisionTargets(List targets) { if (targets != null && targets.Count > 0) { _manualCollisionTargets = targets; _manualCollisionOverrideEnabled = true; } else { _manualCollisionTargets = new List(); _manualCollisionOverrideEnabled = false; } } /// /// 预计算动画帧(批处理专用内部方法) /// public List PrecomputeAnimationFramesInternal() { PrecomputeAnimationFrames(); return _animationFrames; } public void SetupAnimation( ModelItem animatedObject, double durationSeconds, PathRoute route) { try { if (route == null) throw new ArgumentNullException(nameof(route), "必须提供路径"); _animatedObject = animatedObject; _animationDuration = durationSeconds; _route = route; LogManager.Info($"使用路径,边数:{route.Edges.Count},总长度:{route.TotalLength:F2}米,动画时长:{durationSeconds:F1}秒"); // 计算动画配置哈希(基于路径边,可以区分不同的曲线化路径) _currentAnimationHash = ComputeAnimationConfigHashFromRoute(animatedObject, route); LogManager.Info($"动画配置哈希: {_currentAnimationHash}"); // 检查动画帧缓存 if (_animationFrameCache.TryGetValue(_currentAnimationHash, out var cachedFrames)) { _animationFrames = cachedFrames; // 同时加载缓存的碰撞结果 if (_collisionResultCache.TryGetValue(_currentAnimationHash, out var cachedCollisions)) { _allCollisionResults = cachedCollisions; LogManager.Info($"使用缓存的动画帧和碰撞结果,帧数: {_animationFrames.Count}, 碰撞数: {_allCollisionResults.Count}"); } else { _allCollisionResults = new List(); LogManager.Info($"使用缓存的动画帧,但碰撞结果缓存缺失,帧数: {_animationFrames.Count}"); } } else { // 缓存未命中,重新计算动画帧 LogManager.Info("缓存未命中,重新计算动画帧和碰撞结果"); PrecomputeAnimationFrames(); // 缓存动画帧和碰撞结果 if (_animationFrames != null && _animationFrames.Count > 0) { _animationFrameCache[_currentAnimationHash] = _animationFrames; if (_allCollisionResults != null) { _collisionResultCache[_currentAnimationHash] = _allCollisionResults; } LogManager.Info($"动画帧和碰撞结果已缓存,帧数: {_animationFrames.Count}, 碰撞数: {_allCollisionResults?.Count ?? 0}"); } } // 验证预计算结果 if (_animationFrames == null || _animationFrames.Count == 0) { throw new InvalidOperationException("动画帧预计算失败,无法创建动画"); } LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 时长={_animationDuration}秒"); } catch (Exception ex) { LogManager.Error($"设置动画失败: {ex.Message}"); throw; } } /// /// 将车辆移动到路径起点 /// /// 动画对象(可选,如果不提供则使用当前的_animatedObject) /// 路径点(可选,如果不提供则使用当前的_pathPoints) public void MoveVehicleToPathStart(ModelItem animatedObject = null, List pathPoints = null) { try { // 🔥 重要:先恢复物体到原始状态(CAD位置和原始朝向),确保每次计算都从相同的状态开始 // 这样可以避免之前旋转导致的包围盒尺寸计算不准确的问题 // 注意:也要处理虚拟车辆(_isVirtualVehicle为true时_animatedObject可能为null) if (_animatedObject != null || _isVirtualVehicle) { RestoreObjectToCADPosition(); LogManager.Info($"[移动到起点] 已恢复物体到原始状态, _currentYaw={_currentYaw * 180 / Math.PI:F2}°"); } // 如果提供了参数,更新内部状态 if (animatedObject != null) { _animatedObject = animatedObject; _originalTransform = animatedObject.Transform; _originalCenter = animatedObject.BoundingBox().Center; _currentPosition = new Point3D(_originalCenter.X, _originalCenter.Y, animatedObject.BoundingBox().Min.Z); // 统一逻辑:从当前 Transform 中提取实际朝向(无论虚拟车辆还是普通物体) _currentYaw = ModelItemTransformHelper.GetYawFromTransform(_originalTransform); } if (pathPoints != null) { _pathPoints = pathPoints; } // 检查路径点 if (_pathPoints == null || _pathPoints.Count < 2) { LogManager.Warning("[移动到起点] 没有可用的路径点"); return; } // 计算朝向(使用前两个路径点的方向) double pathDirectionYaw = Math.Atan2(_pathPoints[1].Y - _pathPoints[0].Y, _pathPoints[1].X - _pathPoints[0].X); double yaw; LogManager.Info($"[移动到起点] 路径方向yaw: {pathDirectionYaw * 180 / Math.PI:F2}度, 角度修正: {_objectRotationCorrection:F1}度"); // 根据路径类型调整朝向 if (_route?.PathType == PathType.Hoisting) { // 吊装路径:使用水平吊运方向(与通行空间方向一致) // 从第2个路径点(提升点)到第3个路径点(平移终点)的水平方向 yaw = Math.Atan2(_pathPoints[2].Y - _pathPoints[1].Y, _pathPoints[2].X - _pathPoints[1].X); LogManager.Debug($"[移动到起点] 吊装路径使用水平吊运方向: {yaw * 180 / Math.PI:F2}度"); } else { // 地面路径和空轨路径:直接使用路径方向 yaw = pathDirectionYaw; LogManager.Debug($"[移动到起点] 地面/空轨路径使用路径方向: {yaw * 180 / Math.PI:F2}度"); } // 应用角度修正值(顺时针,转换为弧度) if (_objectRotationCorrection != 0.0) { double correctionRad = _objectRotationCorrection * Math.PI / 180.0; yaw += correctionRad; LogManager.Debug($"[移动到起点] 应用角度修正: {_objectRotationCorrection:F1}°, 修正后yaw: {yaw * 180 / Math.PI:F2}度"); } // 根据路径类型调整起点位置 Point3D startPosition = _pathPoints[0]; if (_route?.PathType == PathType.Hoisting) { // 吊装路径:第一个路径点(起吊点)是地面位置,物体底面应该在这里 // 不需要向下移动车辆高度 LogManager.Debug($"[移动到起点] 吊装路径:起吊点是地面位置,物体底面Z={startPosition.Z:F2}"); } else if (_route?.PathType == PathType.Rail) { // 空轨路径:路径点是悬挂点,物体悬挂在下方 double vehicleHeight = _animatedObject.BoundingBox().Max.Z - _animatedObject.BoundingBox().Min.Z; startPosition = new Point3D(startPosition.X, startPosition.Y, startPosition.Z - vehicleHeight); LogManager.Debug($"[移动到起点] 空轨路径调整: 悬挂点Z={_pathPoints[0].Z:F2}, 物体底面Z={startPosition.Z:F2}, 物体高度={vehicleHeight:F2}"); } else { // 地面路径:points[0] 是地面位置,车辆底面应该在这里 LogManager.Debug($"[移动到起点] 地面路径: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})"); } // 使用 UpdateObjectPosition 统一处理移动和旋转 UpdateObjectPosition(startPosition, yaw); string pathTypeName; if (_route?.PathType == PathType.Rail) { pathTypeName = "空轨"; } else if (_route?.PathType == PathType.Hoisting) { pathTypeName = "吊装"; } else { pathTypeName = "地面"; } LogManager.Info($"物体已初始化到路径起点并对齐朝向: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), yaw={yaw:F3}rad, 路径类型={pathTypeName}"); // 打印实际物体的位置和方向 if (_animatedObject != null) { var bbox = _animatedObject.BoundingBox(); var actualCenter = bbox.Center; var actualYaw = ModelItemTransformHelper.GetYawFromTransform(_animatedObject.Transform); LogManager.Info($"实际物体位置: X={actualCenter.X:F2}, Y={actualCenter.Y:F2}, Z={actualCenter.Z:F2}"); LogManager.Info($"实际物体朝向: {actualYaw * 180 / Math.PI:F2}度"); } else if (_isVirtualVehicle) { var virtualVehicle = VirtualVehicleManager.Instance.CurrentVirtualVehicle; if (virtualVehicle != null) { var bbox = virtualVehicle.BoundingBox(); var actualCenter = bbox.Center; var actualYaw = ModelItemTransformHelper.GetYawFromTransform(virtualVehicle.Transform); LogManager.Info($"虚拟车辆位置: X={actualCenter.X:F2}, Y={actualCenter.Y:F2}, Z={actualCenter.Z:F2}"); LogManager.Info($"虚拟车辆朝向: {actualYaw * 180 / Math.PI:F2}度"); } } } catch (Exception ex) { LogManager.Error($"初始化物体到路径起点失败: {ex.Message}"); } } /// /// 预计算所有动画帧和碰撞信息 /// private void PrecomputeAnimationFrames() { try { LogManager.Info("=== 使用路径预计算动画帧 ==="); // 🔥 重要:预计算前先将物体移动到起点位置 if (_animatedObject != null && _route != null && _route.Points != null && _route.Points.Count > 0) { var pathPoints = _route.Points.Select(p => p.Position).ToList(); MoveVehicleToPathStart(_animatedObject, pathPoints); LogManager.Info("[预计算] 物体已移动到路径起点"); } int totalFrames = (int)(_animationDuration * _animationFrameRate); // 🔥 判断路径类型:空中路径使用Points,地面路径使用Edges bool isAerialPath = _route.PathType == PathType.Rail || _route.PathType == PathType.Hoisting; List allSampledPoints = new List(); List segmentLengths = new List(); if (isAerialPath) { // === 空中路径:直接使用路径点进行线性插值 === string aerialSubTypeName = _route.PathType == PathType.Rail ? "空轨" : "吊装"; LogManager.Info($"[{aerialSubTypeName}路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}"); // 将路径点转换为Point3D列表 foreach (var point in _route.Points) { allSampledPoints.Add(point.Position); } // 计算每段长度(内部计算统一使用模型单位) for (int i = 0; i < allSampledPoints.Count - 1; i++) { double lengthInModelUnits = GeometryHelper.CalculatePointDistance(allSampledPoints[i], allSampledPoints[i + 1]); segmentLengths.Add(lengthInModelUnits); } } else { // === 地面路径:使用Edges进行曲线化插值 === var edgeStartIndices = new List(); foreach (var edge in _route.Edges) { if (edge.SampledPoints == null || edge.SampledPoints.Count == 0) { var samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep; edge.SampledPoints = PathCurveEngine.GenerateSampledPoints(edge, samplingStep); } edgeStartIndices.Add(allSampledPoints.Count); allSampledPoints.AddRange(edge.SampledPoints); segmentLengths.Add(edge.PhysicalLength); } } // 🔥 内部计算统一使用模型单位 double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(); double totalLengthInModelUnits = _route.TotalLength * metersToModelUnits; string pathTypeName; if (_route.PathType == PathType.Rail) { pathTypeName = "空轨"; } else if (_route.PathType == PathType.Hoisting) { pathTypeName = "吊装"; } else { pathTypeName = "地面"; } LogManager.Info($"路径总长度: {totalLengthInModelUnits / metersToModelUnits:F2}米, 采样点数: {allSampledPoints.Count}, 路径类型: {pathTypeName}"); // 2. 按帧数采样生成动画帧 _animationFrames = new List(); _allCollisionResults = new List(); _currentFrameIndex = 0; // 获取动画对象的包围盒信息 var originalBoundingBox = _animatedObject.BoundingBox(); var boundingBoxSize = new Vector3D( originalBoundingBox.Max.X - originalBoundingBox.Min.X, originalBoundingBox.Max.Y - originalBoundingBox.Min.Y, originalBoundingBox.Max.Z - originalBoundingBox.Min.Z ); // 空间索引设置 SpatialIndexManager spatialIndexManager = null; var manualTargetsSnapshot = new List(); bool manualOverrideActive = _manualCollisionOverrideEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0; // 获取配置 var config = ConfigManager.Instance.Current; if (manualOverrideActive) { manualTargetsSnapshot = _manualCollisionTargets .Where(item => item != null && ModelItemAnalysisHelper.IsModelItemValid(item) && !ReferenceEquals(item, _animatedObject)) .Distinct() .ToList(); if (manualTargetsSnapshot.Count == 0) { manualOverrideActive = false; LogManager.Warning("[手工碰撞] 手工碰撞对象列表为空或无效,自动回退到空间索引模式"); } else { LogManager.Info($"[手工碰撞] 启用手工碰撞对象模式,数量: {manualTargetsSnapshot.Count},跳过空间索引构建"); } } if (!manualOverrideActive) { LogManager.Info("=== 构建全局空间索引 ==="); spatialIndexManager = SpatialIndexManager.Instance; // 使用动画专用的空间索引格子大小配置 - 使用模型单位接口 double cellSizeInModelUnits = config.Animation.SpatialIndexCellSize; LogManager.Info($"[空间索引] 格子大小: {cellSizeInModelUnits:F2}模型单位 (动画配置)"); // 检查空间索引是否需要重新构建(格子大小变化或未初始化) bool needRebuild = !spatialIndexManager.IsInitialized || Math.Abs(spatialIndexManager.CellSize - cellSizeInModelUnits) > 0.0001; if (needRebuild) { LogManager.Info("[空间索引] 空间索引未初始化或格子大小已改变,开始重新构建..."); spatialIndexManager.BuildGlobalIndex(cellSizeInModelUnits); } else { LogManager.Info("[空间索引] 使用已缓存的空间索引"); } LogManager.Info(spatialIndexManager.GetStatistics()); // 🔥 优化:使用AABB查询代替球形查询,查询范围更精确 // 不再预计算球形半径,改为每帧计算车辆AABB + 安全间隙的搜索AABB LogManager.Info($"空间查询使用AABB模式,安全间隙: {_safetyMargin:F4}模型单位"); } // 3. 生成每一帧 double distancePerFrameInModelUnits = totalLengthInModelUnits / totalFrames; LogManager.Info($"总帧数: {totalFrames}, 总长度: {totalLengthInModelUnits / metersToModelUnits:F2}米, 每帧移动距离: {distancePerFrameInModelUnits / metersToModelUnits:F4}米"); for (int i = 0; i < totalFrames; i++) { double targetDistance = i * distancePerFrameInModelUnits; // 按距离采样(模型单位),不是按进度 Point3D framePosition; double yawRadians; if (isAerialPath) { // === 空中路径:在路径点之间进行线性插值 === int segmentIndex = FindSegmentForDistance(targetDistance, segmentLengths); if (segmentIndex < 0 || segmentIndex >= segmentLengths.Count) { string subTypeName = _route.PathType == PathType.Rail ? "空轨" : "吊装"; LogManager.Warning($"[{subTypeName}路径] 无法找到线段,目标距离:{targetDistance / metersToModelUnits:F2}米"); continue; } double accumulatedLength = segmentLengths.Take(segmentIndex).Sum(); double segmentProgress = (targetDistance - accumulatedLength) / segmentLengths[segmentIndex]; // 线性插值 Point3D p1 = allSampledPoints[segmentIndex]; Point3D p2 = allSampledPoints[segmentIndex + 1]; framePosition = new Point3D( p1.X + segmentProgress * (p2.X - p1.X), p1.Y + segmentProgress * (p2.Y - p1.Y), p1.Z + segmentProgress * (p2.Z - p1.Z) ); // 计算朝向(使用当前线段的方向) yawRadians = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); // 吊装路径:所有线段都使用水平吊运方向(与MoveVehicleToPathStart保持一致) if (_route.PathType == PathType.Hoisting) { yawRadians = Math.Atan2(_pathPoints[2].Y - _pathPoints[1].Y, _pathPoints[2].X - _pathPoints[1].X); } // 🔥 空中路径:根据路径类型和线段索引调整物体位置 double vehicleHeight = _animatedObject.BoundingBox().Max.Z - _animatedObject.BoundingBox().Min.Z; if (_route.PathType == PathType.Hoisting) { // 吊装路径:支持动态数量的路径点 // 路径点结构: // 点0(起吊点):地面位置 // 点1(提升点):悬挂点 // 点2...点N-2:中间悬挂点(用户添加的转向点) // 点N-1(下降点):悬挂点 // 点N(落地点):地面位置 // // 线段结构: // 线段0:起吊段(地面→悬挂点)- 垂直 // 线段1...线段N-2:平移段(悬挂点→悬挂点)- 水平 // 线段N-1:下降段(悬挂点→地面)- 垂直 int totalSegments = _pathPoints.Count - 1; int firstSegment = 0; // 起吊段 int lastSegment = totalSegments - 1; // 下降段 if (segmentIndex == firstSegment) { // 起吊段:从地面逐渐上升到悬挂点-车辆高度 // 进度0时(地面):物体底面在地面,不向下移动 // 进度1时(悬挂点):物体顶面在悬挂点,物体底面=悬挂点-车辆高度 double heightOffset = segmentProgress * vehicleHeight; framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - heightOffset); LogManager.Debug($"[吊装路径-起吊段] 进度={segmentProgress:F2}, 高度偏移={heightOffset:F2}, 物体底面Z={framePosition.Z:F2}"); } else if (segmentIndex > firstSegment && segmentIndex < lastSegment) { // 平移段(中间的所有线段):全程都是悬挂点,物体顶面在悬挂点 framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - vehicleHeight); LogManager.Debug($"[吊装路径-平移段] 线段{segmentIndex}, 进度={segmentProgress:F2}, 物体底面Z={framePosition.Z:F2}"); } else if (segmentIndex == lastSegment) { // 下降段:从悬挂点-车辆高度逐渐下降到地面 // 进度0时(悬挂点):物体顶面在悬挂点,物体底面=悬挂点-车辆高度 // 进度1时(地面):物体底面在地面,不向下移动 double heightOffset = (1 - segmentProgress) * vehicleHeight; framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - heightOffset); LogManager.Debug($"[吊装路径-下降段] 进度={segmentProgress:F2}, 高度偏移={heightOffset:F2}, 物体底面Z={framePosition.Z:F2}"); } } else if (_route.PathType == PathType.Rail) { // 空轨路径:路径点是悬挂点,物体悬挂在下方 // 物体顶面应该在路径点,所以物体底面 = 路径点 - 物体高度 framePosition = new Point3D(framePosition.X, framePosition.Y, framePosition.Z - vehicleHeight); LogManager.Debug($"[空轨路径] 调整物体位置: 悬挂点Z={framePosition.Z + vehicleHeight:F2}, 物体底面Z={framePosition.Z:F2}"); } } else { // === 地面路径:在边内插值 === int edgeIndex = FindEdgeForDistance(targetDistance, segmentLengths); if (edgeIndex < 0 || edgeIndex >= _route.Edges.Count) { LogManager.Warning($"无法找到边,目标距离:{targetDistance / metersToModelUnits:F2}米"); continue; } var edge = _route.Edges[edgeIndex]; double accumulatedLength = segmentLengths.Take(edgeIndex).Sum(); double edgeProgress = (targetDistance - accumulatedLength) / edge.PhysicalLength; // 在边内插值位置 if (edge.SegmentType == PathSegmentType.Straight) { framePosition = InterpolateOnStraightEdge(edge, edgeProgress); } else // Arc { framePosition = InterpolateOnArcEdge(edge, edgeProgress); } yawRadians = ComputeYawOnPath(i, allSampledPoints, edgeIndex, edgeProgress); } // 创建帧并检测碰撞 var frame = new AnimationFrame { Index = i, Progress = (double)i / (totalFrames - 1), Position = framePosition, YawRadians = yawRadians, Collisions = new List() }; // 记录第一帧的角度(用于调试) if (i == 0) { LogManager.Debug($"[预计算] 第0帧: pos=({framePosition.X:F2},{framePosition.Y:F2}), yaw={yawRadians * 180 / Math.PI:F2}度"); } // 虚拟碰撞检测 // 计算车辆包围盒相对于framePosition的偏移 var currentVehicleBoundingBox = _animatedObject.BoundingBox(); var originalCenter = new Point3D( (currentVehicleBoundingBox.Min.X + currentVehicleBoundingBox.Max.X) / 2, (currentVehicleBoundingBox.Min.Y + currentVehicleBoundingBox.Max.Y) / 2, (currentVehicleBoundingBox.Min.Z + currentVehicleBoundingBox.Max.Z) / 2 ); // 计算偏移量(当前包围盒中心到framePosition的偏移) var offsetX = framePosition.X - originalCenter.X; var offsetY = framePosition.Y - originalCenter.Y; var offsetZ = framePosition.Z - currentVehicleBoundingBox.Min.Z; // Z方向:framePosition是底面,originalCenter是中心 // 创建新的包围盒(将当前包围盒移动到framePosition) var virtualBoundingBox = new BoundingBox3D( new Point3D( currentVehicleBoundingBox.Min.X + offsetX, currentVehicleBoundingBox.Min.Y + offsetY, framePosition.Z // 底面Z坐标 ), new Point3D( currentVehicleBoundingBox.Max.X + offsetX, currentVehicleBoundingBox.Max.Y + offsetY, framePosition.Z + (currentVehicleBoundingBox.Max.Z - currentVehicleBoundingBox.Min.Z) // 保持高度 ) ); IEnumerable nearbyObjects; if (manualOverrideActive) { // 🔥 Human-in-the-Loop: 手工模式下也要应用排除列表 nearbyObjects = manualTargetsSnapshot; if (_excludedObjects.Count > 0) { nearbyObjects = nearbyObjects.Where(obj => !_excludedObjects.Contains(obj)); } } else { // 🔥 优化:使用AABB查询代替球形查询,避免球体扩大的无效范围 // 查询AABB = 车辆AABB + 安全间隙扩展 var searchBounds = new BoundingBox3D( new Point3D( virtualBoundingBox.Min.X - _safetyMargin, virtualBoundingBox.Min.Y - _safetyMargin, virtualBoundingBox.Min.Z - _safetyMargin ), new Point3D( virtualBoundingBox.Max.X + _safetyMargin, virtualBoundingBox.Max.Y + _safetyMargin, virtualBoundingBox.Max.Z + _safetyMargin ) ); nearbyObjects = spatialIndexManager.FindInAABB( searchBounds, excludeObject: _animatedObject ); // 🔥 Human-in-the-Loop: 应用用户排除列表过滤 if (_excludedObjects.Count > 0) { nearbyObjects = nearbyObjects.Where(obj => !_excludedObjects.Contains(obj)); } } foreach (var collider in nearbyObjects) { var colliderBox = collider.BoundingBox(); // 使用包围盒距离检测方法 double distance = BoundingBoxGeometryUtils.CalculateDistance(virtualBoundingBox, colliderBox); // _safetyMargin 已经是模型单位(在CreateAnimation中已转换),直接使用 bool intersects = distance <= _safetyMargin; if (intersects) { LogManager.Debug($"帧 {i} 检测到碰撞: {_animatedObject.DisplayName} <-> {collider.DisplayName}, 距离: {distance:F4},阈值: {_safetyMargin:F4}"); LogManager.Debug($"移动物体位置: {framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}"); LogManager.Debug($"被撞物体位置:{GetObjectPosition(collider).X:F2},{GetObjectPosition(collider).Y:F2},{GetObjectPosition(collider).Z:F2}"); var collisionResult = new CollisionResult { ClashGuid = Guid.NewGuid(), DisplayName = $"预计算碰撞: {_animatedObject.DisplayName} <-> {collider.DisplayName}", Status = ClashResultStatus.New, Item1 = _animatedObject, Item2 = collider, CreatedTime = DateTime.Now, Distance = BoundingBoxGeometryUtils.CalculateCenterDistance(virtualBoundingBox, colliderBox), Center = new Point3D( (virtualBoundingBox.Min.X + virtualBoundingBox.Max.X + colliderBox.Min.X + colliderBox.Max.X) / 4, (virtualBoundingBox.Min.Y + virtualBoundingBox.Max.Y + colliderBox.Min.Y + colliderBox.Max.Y) / 4, (virtualBoundingBox.Min.Z + virtualBoundingBox.Max.Z + colliderBox.Min.Z + colliderBox.Max.Z) / 4 ), // 🔥 重要:记录虚拟车辆的包围盒中心,而不是底面中心 // 原因: // 1. framePosition是路径点,代表车辆在地面上的位置(底面中心) // 2. ClashDetective移动逻辑使用包围盒中心进行定位 // 3. 如果记录底面中心,ClashDetective会把包围盒中心移动到底面位置,导致车辆下沉半个高度 // 4. 记录包围盒中心可以确保ClashDetective移动后的位置与预计算时的虚拟车辆位置一致 Item1Position = new Point3D( framePosition.X, framePosition.Y, framePosition.Z + (virtualBoundingBox.Max.Z - virtualBoundingBox.Min.Z) / 2 ), Item2Position = GetObjectPosition(collider), Item1YawRadians = yawRadians, // 记录运动物体朝向 HasPositionInfo = true }; frame.Collisions.Add(collisionResult); _allCollisionResults.Add(collisionResult); } } _animationFrames.Add(frame); } // 统计碰撞信息 var framesWithCollision = _animationFrames.Count(f => f.HasCollision); var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count); // 检查碰撞结果是否包含位置信息 var collisionsWithPosition = _allCollisionResults.Count(c => c.HasPositionInfo && c.Item1Position != null); LogManager.Info($"=== 预计算完成 ==="); LogManager.Info($"总帧数: {_animationFrames.Count}"); LogManager.Info($"包含碰撞的帧: {framesWithCollision}"); LogManager.Info($"总碰撞次数: {totalCollisions}"); LogManager.Info($"记录的碰撞结果总数: {_allCollisionResults.Count} 个"); LogManager.Info($"包含位置信息的碰撞: {collisionsWithPosition}/{_allCollisionResults.Count}"); // 🔥 Human-in-the-Loop: 记录排除统计 if (_excludedObjects.Count > 0) { LogManager.Info($"[排除列表] 本次预计算排除了 {_excludedObjects.Count} 个物体"); } // 🔥 清除移动物体集合 ClashDetectiveIntegration.ClearAnimatedObject(); } catch (Exception ex) { LogManager.Error($"预计算动画帧失败: {ex.Message}"); _animationFrames = new List(); } } /// /// 根据路径计算指定帧的yaw角度 /// /// /// 计算路径上的朝向(改进版) /// private double ComputeYawOnPath( int frameIndex, List allSampledPoints, int currentEdgeIndex, double edgeProgress) { var edge = _route.Edges[currentEdgeIndex]; if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null) { // 圆弧段: 使用切线法计算精确朝向 Vector3D startVec = (edge.Trajectory.Ts - edge.Trajectory.ArcCenter).Normalize(); double signedAngle = edge.Trajectory.DeflectionAngle; double currentAngle = edgeProgress * signedAngle; // 计算切点Ts的朝向(进入切点的切线方向) Vector3D tsRadiusVec = (edge.Trajectory.Ts - edge.Trajectory.ArcCenter).Normalize(); Vector3D rotationAxis = GetArcRotationAxis(edge.Trajectory); Point3D tsTangent; if (signedAngle >= 0) { tsTangent = GeometryHelper.CrossProduct( new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z), new Point3D(tsRadiusVec.X, tsRadiusVec.Y, tsRadiusVec.Z) ); } else { tsTangent = GeometryHelper.CrossProduct( new Point3D(tsRadiusVec.X, tsRadiusVec.Y, tsRadiusVec.Z), new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z) ); } double tsYaw = Math.Atan2(tsTangent.Y, tsTangent.X); // 计算切点Te的朝向(退出切点的切线方向) Vector3D teRadiusVec = (edge.Trajectory.Te - edge.Trajectory.ArcCenter).Normalize(); Point3D teTangent; if (signedAngle >= 0) { teTangent = GeometryHelper.CrossProduct( new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z), new Point3D(teRadiusVec.X, teRadiusVec.Y, teRadiusVec.Z) ); } else { teTangent = GeometryHelper.CrossProduct( new Point3D(teRadiusVec.X, teRadiusVec.Y, teRadiusVec.Z), new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z) ); } double teYaw = Math.Atan2(teTangent.Y, teTangent.X); // 计算当前帧的朝向 Point3D rotatedPoint = GeometryHelper.RotatePointAroundAxis( edge.Trajectory.Ts, edge.Trajectory.ArcCenter, rotationAxis, currentAngle ); Vector3D radiusVec = (rotatedPoint - edge.Trajectory.ArcCenter).Normalize(); // 计算切线方向:根据旋转方向确定切线顺序 Point3D tangentPoint; if (signedAngle >= 0) { tangentPoint = GeometryHelper.CrossProduct( new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z), new Point3D(radiusVec.X, radiusVec.Y, radiusVec.Z) ); } else { tangentPoint = GeometryHelper.CrossProduct( new Point3D(radiusVec.X, radiusVec.Y, radiusVec.Z), new Point3D(rotationAxis.X, rotationAxis.Y, rotationAxis.Z) ); } double currentYaw = Math.Atan2(tangentPoint.Y, tangentPoint.X); double yawChange = teYaw - tsYaw; // 计算圆弧段前后直线段的朝向 double prevStraightYaw = 0; double nextStraightYaw = 0; // 圆弧段前的直线段朝向 if (currentEdgeIndex > 0 && _route.Edges[currentEdgeIndex - 1].SegmentType == PathSegmentType.Straight) { var prevEdge = _route.Edges[currentEdgeIndex - 1]; if (prevEdge.SampledPoints != null && prevEdge.SampledPoints.Count >= 2) { Point3D p1 = prevEdge.SampledPoints[0]; Point3D p2 = prevEdge.SampledPoints[prevEdge.SampledPoints.Count - 1]; prevStraightYaw = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); } } // 圆弧段后的直线段朝向 if (currentEdgeIndex < _route.Edges.Count - 1 && _route.Edges[currentEdgeIndex + 1].SegmentType == PathSegmentType.Straight) { var nextEdge = _route.Edges[currentEdgeIndex + 1]; if (nextEdge.SampledPoints != null && nextEdge.SampledPoints.Count >= 2) { Point3D p1 = nextEdge.SampledPoints[0]; Point3D p2 = nextEdge.SampledPoints[nextEdge.SampledPoints.Count - 1]; nextStraightYaw = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); } } // 在第一帧打印完整信息(使用静态变量确保每个圆弧段只打印一次) if (!_arcInfoPrinted.Contains(currentEdgeIndex)) { LogManager.Debug($"圆弧段[{currentEdgeIndex}]朝向信息:"); LogManager.Debug($" 前直线段朝向: {prevStraightYaw * 180.0 / Math.PI:F2}度"); LogManager.Debug($" 进入切点(Ts)朝向: {tsYaw * 180.0 / Math.PI:F2}度"); LogManager.Debug($" 圆弧偏转角度: {signedAngle * 180.0 / Math.PI:F2}度"); LogManager.Debug($" 退出切点(Te)朝向: {teYaw * 180.0 / Math.PI:F2}度"); LogManager.Debug($" 后直线段朝向: {nextStraightYaw * 180.0 / Math.PI:F2}度"); LogManager.Debug($" 前后直线段朝向差: {(nextStraightYaw - prevStraightYaw) * 180.0 / Math.PI:F2}度"); LogManager.Debug($" 圆弧段朝向变化: {yawChange * 180.0 / Math.PI:F2}度"); _arcInfoPrinted.Add(currentEdgeIndex); } // 每一帧都打印当前朝向 // LogManager.Debug($"帧[{frameIndex}] 圆弧段[{currentEdgeIndex}]: 进度={edgeProgress:F4}, 当前朝向={currentYaw * 180.0 / Math.PI:F2}度"); return currentYaw; } else { // 直线段: 使用直线方向向量,避免边界处的前后帧差分问题 Point3D startPos = edge.SampledPoints[0]; Point3D endPos = edge.SampledPoints[edge.SampledPoints.Count - 1]; double dx = endPos.X - startPos.X; double dy = endPos.Y - startPos.Y; double length = Math.Sqrt(dx * dx + dy * dy); if (length < 1e-6) { // 直线段长度太短,尝试使用前后帧差分 Point3D currentPos = allSampledPoints.Count > 0 ? allSampledPoints[Math.Min(frameIndex, allSampledPoints.Count - 1)] : new Point3D(); int nextIndex = Math.Min(frameIndex + 1, allSampledPoints.Count - 1); Point3D nextPos = allSampledPoints[nextIndex]; dx = nextPos.X - currentPos.X; dy = nextPos.Y - currentPos.Y; length = Math.Sqrt(dx * dx + dy * dy); if (length < 1e-6) { return 0.0; } } return Math.Atan2(dy, dx); } } /// /// 获取圆弧旋转轴 /// private Vector3D GetArcRotationAxis(ArcTrajectory trajectory) { Vector3D startVec = (trajectory.Ts - trajectory.ArcCenter).Normalize(); Vector3D endVec = (trajectory.Te - trajectory.ArcCenter).Normalize(); Point3D cross = GeometryHelper.CrossProduct( new Point3D(startVec.X, startVec.Y, startVec.Z), new Point3D(endVec.X, endVec.Y, endVec.Z) ); return new Vector3D(cross.X, cross.Y, cross.Z).Normalize(); } /// /// 开始播放动画 /// public void StartAnimation() { try { if (_animatedObject == null || _pathPoints.Count < 2) { throw new InvalidOperationException("请先调用SetupAnimation设置动画参数"); } // 🔥 关键:动画开始前确保物体在起点位置和朝向 // 动画结束时物体在终点,重新播放前必须重置 if (_animationFrames != null && _animationFrames.Count > 0) { var firstFrame = _animationFrames[0]; var currentPos = _animatedObject.BoundingBox().Center; var distanceToStart = Math.Sqrt( Math.Pow(currentPos.X - firstFrame.Position.X, 2) + Math.Pow(currentPos.Y - firstFrame.Position.Y, 2) ); // 如果距离起点超过0.1米,或者朝向不一致,需要重置 var yawDiff = Math.Abs(_currentYaw - firstFrame.YawRadians); if (yawDiff > Math.PI) yawDiff = 2 * Math.PI - yawDiff; // 处理角度环绕 if (distanceToStart > 0.1 || yawDiff > 0.01) { LogManager.Info($"[动画开始] 物体不在起点,重置到起点: 距离={distanceToStart:F2}m, 角度差={yawDiff * 180 / Math.PI:F2}°"); MoveVehicleToPathStart(); } } // 创建 TimeLiner 任务(如果可用且未创建过) if (_timeLinerManager != null && _timeLinerManager.IsTimeLinerAvailable) { string objectName = "UnknownObject"; try { objectName = _animatedObject?.DisplayName ?? "UnknownObject"; } catch (Exception) { objectName = "DisposedObject"; } // 使用动画配置hash作为任务标识 var hashPrefix = _currentAnimationHash.Substring(0, Math.Min(8, _currentAnimationHash.Length)); var taskName = $"{objectName}_运输_{hashPrefix}"; // 检查是否已存在相同配置的任务 var existingTaskId = _timeLinerManager.FindTaskByName(taskName); if (!string.IsNullOrEmpty(existingTaskId)) { _currentTaskId = existingTaskId; LogManager.Info($"复用现有 TimeLiner 任务: {taskName} (ID: {_currentTaskId})"); } else { // 创建新任务 var duration = TimeSpan.FromSeconds(_animationDuration); LogManager.Info($"创建 TimeLiner 任务: {taskName}"); _currentTaskId = _timeLinerManager.CreateTransportTask( taskName, _pathPoints, duration, _animatedObject); if (!string.IsNullOrEmpty(_currentTaskId)) { LogManager.Info($"✓ TimeLiner 任务创建成功: {taskName} (ID: {_currentTaskId})"); } else { LogManager.Warning("TimeLiner 任务创建失败,继续使用基础动画功能"); } } } else { LogManager.Info("TimeLiner 不可用,使用基础动画功能"); } // 初始化动画状态 _animationStartTime = DateTime.Now; _currentFrameIndex = 0; // 重置当前帧索引,确保从第一帧开始 _pausedProgress = 0.0; // 重置暂停进度 // 初始化动画状态时不再强行覆盖 _currentYaw 为第一帧的 yaw // 这样在 UpdateObjectPosition(firstFrame.Position, firstFrame.YawRadians) 时 // deltaYaw = firstFrame.YawRadians - 物体初始Yaw,从而产生旋转增量让物体转向 if (_animationFrames != null && _animationFrames.Count > 0) { var firstFrame = _animationFrames[0]; LogManager.Debug($"[动画开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, _isVirtualVehicle={_isVirtualVehicle}"); // 🔥 确保物体在起点位置和朝向 // 注意:MoveVehicleToPathStart 已在 StartAnimation 中调用,这里只是日志记录 LogManager.Debug($"[动画开始] 物体应在起点: pos=({firstFrame.Position.X:F2},{firstFrame.Position.Y:F2}), yaw={firstFrame.YawRadians:F3}rad"); } else { _currentYaw = 0.0; } // 重置动画状态 _lastFrameTime = DateTime.MinValue; _fpsFrameCount = 0; _fpsCounterStart = DateTime.Now; _frameInterval = 1000.0 / _animationFrameRate; _lastHighlightState = false; // 重置高亮状态 _lastCollisionObjects.Clear(); // 重置碰撞对象集合 // 清除所有碰撞高亮 ModelHighlightHelper.ClearCollisionHighlights(); LogManager.Debug("[动画开始] 已清除所有碰撞高亮"); // 不再高亮车辆对象,保持原始外观(客户要求) // if (_animatedObject != null) // { // var vehicleItems = new List { _animatedObject }; // ModelHighlightHelper.HighlightItems(ModelHighlightHelper.AnimatedObjectCategory, vehicleItems); // LogManager.Debug("[动画开始] 已高亮车辆对象为绿色"); // } // 启动动画播放 StartAnimationPlayback(); SetState(AnimationState.Playing); LogManager.Info($"动画开始播放 - DispatcherTimer模式"); } catch (Exception ex) { LogManager.Error($"启动动画失败: {ex.Message}"); throw; } } /// /// 启动动画播放(DispatcherTimer) /// private void StartAnimationPlayback() { try { // 停止之前的播放 StopAnimationPlayback(); // 使用DispatcherTimer模式 _animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval); _animationTimer.Start(); LogManager.Debug("[播放模式] 启动DispatcherTimer模式"); } catch (Exception ex) { LogManager.Error($"启动动画播放失败: {ex.Message}"); } } /// /// 停止动画播放(停止DispatcherTimer) /// private void StopAnimationPlayback() { try { // 停止DispatcherTimer if (_animationTimer != null) { _animationTimer.Stop(); } LogManager.Debug("[播放模式] 已停止DispatcherTimer"); } catch (Exception ex) { LogManager.Warning($"停止动画播放时出现警告: {ex.Message}"); } } /// /// DispatcherTimer事件处理器 - 主要动画机制 /// private void OnTimerTick(object sender, EventArgs e) { try { // 处理动画帧 ProcessAnimationFrame(); // 关键改进:显式请求重绘,解决卡顿 NavisApplication.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.All); } catch (Exception ex) { LogManager.Error($"[DispatcherTimer模式] 动画更新异常: {ex.Message}"); // 发生异常时停止动画 ShutdownAnimation(); } } /// /// 停止动画 /// public void CancelAnimation() { try { // 检查动画状态,避免重复停止 if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle) { LogManager.Debug($"动画已处于{_currentState}状态,跳过重复停止操作"); return; } // 停止播放机制 StopAnimationPlayback(); // 收集整个路径上的所有碰撞对象 var allCollisionResults = new List(); var uniqueCollidedItems = new HashSet(); foreach (var frame in _animationFrames) { if (frame.HasCollision) { foreach (var collision in frame.Collisions) { // 收集唯一的碰撞对象 if (!uniqueCollidedItems.Contains(collision.Item2)) { uniqueCollidedItems.Add(collision.Item2); allCollisionResults.Add(collision); } } } } SetState(AnimationState.Stopped); _pausedProgress = 0.0; // 重置暂停进度 _currentFrameIndex = 0; // 重置帧索引 // 清除所有高亮(包括车辆和碰撞) ModelHighlightHelper.ClearAllHighlights(); LogManager.Info("动画停止:已清除所有高亮"); // 动画停止时保持物体在当前位置(不移回起点) LogManager.Info("动画已停止,物体保持在当前位置"); // 动画停止时不创建碰撞测试汇总,由动画完成事件统一处理 LogManager.Info("动画停止,等待动画完成事件统一处理碰撞测试..."); // 更新 TimeLiner 任务状态 if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) { _timeLinerManager.UpdateTaskProgress(_currentTaskId, 0.0, AnimationState.Stopped); } } catch (Exception ex) { LogManager.Error($"停止动画失败: {ex.Message}"); } } /// /// 程序关闭时的动画清理(不执行UI操作) /// public void ShutdownAnimation() { try { LogManager.Info("[PathAnimationManager] 程序关闭,执行动画清理"); // 检查动画状态,避免重复停止 if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle) { LogManager.Debug($"动画已处于{_currentState}状态,跳过关闭清理"); return; } // 停止播放机制(关闭时不记录详细日志) try { if (_animationTimer != null) _animationTimer.Stop(); } catch (Exception stopEx) { LogManager.Debug($"关闭清理时停止播放机制异常: {stopEx.Message}"); } // 直接设置状态为停止,不执行任何UI操作 SetState(AnimationState.Stopped); _pausedProgress = 0.0; // 重置暂停进度 _currentFrameIndex = 0; // 重置帧索引 LogManager.Info("[PathAnimationManager] 动画关闭清理完成"); } catch (Exception ex) { LogManager.Error($"[PathAnimationManager] 关闭动画清理失败: {ex.Message}"); } } /// /// 完成动画(当动画自然播放结束时调用)- 优化版本,使用预计算碰撞结果 /// private void FinishAnimation() { try { // 停止播放机制 StopAnimationPlayback(); // 重置暂停进度 _pausedProgress = 0.0; // 使用预计算的所有碰撞结果进行高亮显示(需要去重以避免重复高亮) var allCollisionResults = _allCollisionResults .GroupBy(c => new { c.Item1, c.Item2 }) .Select(g => g.First()) .ToList(); // 动画自然结束时保持物体在最终位置(不移回起点) LogManager.Info("动画播放自然结束,物体保持在最终位置"); // 直接设置为完成状态,避免中间状态切换 SetState(AnimationState.Finished); // 清除所有高亮(包括车辆和碰撞) ModelHighlightHelper.ClearAllHighlights(); LogManager.Info("动画完成:已清除所有高亮"); // 修复:显示正确的总帧数,而不是最后一帧的索引 var actualTotalFrames = _animationFrames?.Count ?? 0; LogManager.Info($"动画播放完成,总帧数: {actualTotalFrames}, 平均FPS: {_actualFPS:F1}"); // 更新 TimeLiner 任务状态 if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) { _timeLinerManager.UpdateTaskProgress(_currentTaskId, 1.0, AnimationState.Finished); } // 检查此动画配置是否已经进行过碰撞检测 if (!_completedCollisionTests.Contains(_currentAnimationHash)) { LogManager.Info($"此动画配置首次完成,开始创建碰撞测试汇总(基于 {_allCollisionResults.Count} 个预计算碰撞记录)..."); // 🔥 设置等待光标,避免进度条关闭后的无响应 var oldCursor = System.Windows.Input.Mouse.OverrideCursor; System.Windows.Input.Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait; bool testCompleted = false; try { // 🔥 使用用户设置的检测容差(米) testCompleted = ClashDetectiveIntegration.Instance.CreateAllAnimationCollisionTests( _allCollisionResults, _detectionTolerance, _currentRouteId, _animatedObject, _isVirtualVehicle, _animationFrameRate, _animationDuration, _virtualVehicleLength, _virtualVehicleWidth, _virtualVehicleHeight, _pathPoints ); } finally { // 恢复光标 System.Windows.Input.Mouse.OverrideCursor = oldCursor; } // 只有在检测成功完成(未被取消)的情况下才记录为已完成 if (testCompleted) { _completedCollisionTests.Add(_currentAnimationHash); // 记录此配置已完成碰撞检测 LogManager.Info($"碰撞测试汇总已创建并记录,此配置后续播放将跳过碰撞检测"); } else { LogManager.Info("碰撞检测被取消,不记录为已完成,允许下次重新检测"); } } else if (_completedCollisionTests.Contains(_currentAnimationHash)) { LogManager.Info("此动画配置的碰撞测试已存在,跳过重复创建"); } } catch (Exception ex) { LogManager.Error($"完成动画失败: {ex.Message}"); } } /// /// 暂停动画 /// public void PauseAnimation() { try { if (_currentState == AnimationState.Playing) { // 计算并保存当前进度 double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds; _pausedProgress = Math.Min(elapsedSeconds / _animationDuration, 1.0); // 停止播放机制 StopAnimationPlayback(); SetState(AnimationState.Paused); LogManager.Info($"动画已暂停,当前进度: {_pausedProgress:F3}"); // 更新 TimeLiner 任务状态 if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) { _timeLinerManager.UpdateTaskProgress(_currentTaskId, _pausedProgress, AnimationState.Paused); } } } catch (Exception ex) { LogManager.Error($"暂停动画失败: {ex.Message}"); } } /// /// 恢复动画 /// public void ResumeAnimation() { try { if (_currentState == AnimationState.Paused) { // 使用保存的暂停进度重新设置动画开始时间 // 设置开始时间,使得当前时间对应保存的暂停进度 _animationStartTime = DateTime.Now.AddSeconds(-(_pausedProgress * _animationDuration)); // 重新启动播放机制 _lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行 StartAnimationPlayback(); SetState(AnimationState.Playing); LogManager.Info($"动画已恢复,从进度 {_pausedProgress:F3} 继续播放"); // 更新 TimeLiner 任务状态 if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) { _timeLinerManager.UpdateTaskProgress(_currentTaskId, _pausedProgress, AnimationState.Playing); } } } catch (Exception ex) { LogManager.Error($"恢复动画失败: {ex.Message}"); } } /// /// 重置动画对象到原始位置 /// public void ResetAnimation() { try { // 只在需要时停止动画,避免重复调用 if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused) { CancelAnimation(); // 停止当前动画 } // 恢复对象的原始变换(安全检查Navisworks对象可用性) if (_animatedObject != null) { try { // 检查Navisworks应用程序和文档是否仍然可用 var activeDoc = NavisApplication.ActiveDocument; if (activeDoc != null && activeDoc.Models != null) { // 第一步:仅仅彻底归位到 CAD 原始位置(不涉及路径起点),这是“清除”按钮的逻辑 RestoreObjectToCADPosition(); // 第二步:如果已经生成了动画路径,则重新对齐到起点(带旋转) if (_animationFrames != null && _animationFrames.Count > 0) { MoveVehicleToPathStart(); LogManager.Info("已重新对齐到路径起点"); } } else { LogManager.Info("Navisworks文档已不可用,跳回空闲状态"); } } catch (Exception resetEx) { LogManager.Warning($"重置对象位置时出现警告: {resetEx.Message}"); // 不再抛出异常,因为在资源清理阶段这是正常的 } } // 重置UI状态(这些操作不依赖Navisworks对象) try { ProgressChanged?.Invoke(this, 0); // 重置进度条 SetState(AnimationState.Ready); // 重置后回到就绪状态 } catch (Exception uiEx) { LogManager.Warning($"重置UI状态时出现警告: {uiEx.Message}"); } } catch (Exception ex) { LogManager.Error($"重置动画过程中发生异常: {ex.Message}"); // 在资源清理阶段不再抛出异常,避免影响程序正常关闭 } } /// /// 安全获取ModelItem的DisplayName,避免访问已释放对象的警告 /// /// ModelItem对象 /// 安全的显示名称 private string GetSafeObjectName(ModelItem item) { try { if (item == null) { return "NULL"; } // 尝试访问DisplayName,如果对象已被释放会抛出异常 return item.DisplayName ?? "未命名对象"; } catch (System.ObjectDisposedException) { return "已释放对象"; } catch (System.Runtime.InteropServices.COMException) { return "COM对象已释放"; } catch (Exception ex) { return $"访问失败({ex.GetType().Name})"; } } /// /// 根据进度插值计算当前位置 /// private Point3D InterpolatePosition(double progress) { if (_pathPoints.Count < 2) return _pathPoints[0]; // 确保进度在0-1范围内 progress = Math.Max(0.0, Math.Min(1.0, progress)); // 如果进度达到100%,直接返回终点 if (progress >= 1.0) { return _pathPoints[_pathPoints.Count - 1]; } // 计算总路径长度 var totalDistance = CalculateTotalPathDistance(); // 检查总距离是否有效 if (totalDistance <= 0.0 || double.IsNaN(totalDistance) || double.IsInfinity(totalDistance)) { LogManager.Error($"路径总长度无效: {totalDistance},返回起点坐标"); return _pathPoints[0]; } var targetDistance = totalDistance * progress; // 找到当前应该在哪两个点之间 var accumulatedDistance = 0.0; for (int i = 0; i < _pathPoints.Count - 1; i++) { var segmentDistance = CalculateDistance(_pathPoints[i], _pathPoints[i + 1]); // 检查段距离是否有效 if (double.IsNaN(segmentDistance) || double.IsInfinity(segmentDistance)) { LogManager.Error($"路径段[{i}-{i + 1}]距离无效: {segmentDistance},跳过此段"); continue; } if (accumulatedDistance + segmentDistance > targetDistance || i == _pathPoints.Count - 2) { // 在这个线段内,或者是最后一个线段 var segmentProgress = segmentDistance > 0 ? (targetDistance - accumulatedDistance) / segmentDistance : 0.0; // 确保段内进度在0-1范围内 segmentProgress = Math.Max(0.0, Math.Min(1.0, segmentProgress)); var interpolatedPoint = InterpolatePoints(_pathPoints[i], _pathPoints[i + 1], segmentProgress); // 验证插值结果 if (double.IsNaN(interpolatedPoint.X) || double.IsNaN(interpolatedPoint.Y) || double.IsNaN(interpolatedPoint.Z)) { LogManager.Error($"插值计算结果无效: ({interpolatedPoint.X},{interpolatedPoint.Y},{interpolatedPoint.Z}),返回起点"); return _pathPoints[0]; } return interpolatedPoint; } accumulatedDistance += segmentDistance; } // 如果到达这里,返回最后一个点 return _pathPoints[_pathPoints.Count - 1]; } /// /// 在两点间插值 /// private Point3D InterpolatePoints(Point3D point1, Point3D point2, double t) { return new Point3D( point1.X + (point2.X - point1.X) * t, point1.Y + (point2.Y - point1.Y) * t, point1.Z + (point2.Z - point1.Z) * t ); } /// /// 在直线边上插值位置 /// private Point3D InterpolateOnStraightEdge(PathEdge edge, double progress) { if (edge.SampledPoints == null || edge.SampledPoints.Count < 2) return edge.Trajectory?.Ts ?? new Point3D(); int pointIndex = (int)(progress * (edge.SampledPoints.Count - 1)); pointIndex = Math.Max(0, Math.Min(pointIndex, edge.SampledPoints.Count - 1)); return edge.SampledPoints[pointIndex]; } /// /// 在圆弧边上插值位置 /// private Point3D InterpolateOnArcEdge(PathEdge edge, double progress) { if (edge.Trajectory == null || edge.SampledPoints == null || edge.SampledPoints.Count < 2) return edge.Trajectory?.Ts ?? new Point3D(); int pointIndex = (int)(progress * (edge.SampledPoints.Count - 1)); pointIndex = Math.Max(0, Math.Min(pointIndex, edge.SampledPoints.Count - 1)); return edge.SampledPoints[pointIndex]; } /// /// 查找指定距离对应的边索引 /// private int FindEdgeForDistance(double targetDistance, List edgeLengths) { double accumulatedLength = 0.0; for (int i = 0; i < edgeLengths.Count; i++) { if (accumulatedLength + edgeLengths[i] >= targetDistance) { return i; } accumulatedLength += edgeLengths[i]; } return edgeLengths.Count - 1; } /// /// 找到目标距离所在的线段索引(用于空轨路径的线性插值) /// /// 目标距离 /// 线段长度列表 /// 线段索引 private int FindSegmentForDistance(double targetDistance, List segmentLengths) { double accumulatedLength = 0.0; for (int i = 0; i < segmentLengths.Count; i++) { if (accumulatedLength + segmentLengths[i] >= targetDistance) { return i; } accumulatedLength += segmentLengths[i]; } return segmentLengths.Count - 1; } /// /// 计算路径总长度 /// private double CalculateTotalPathDistance() { var totalDistance = 0.0; for (int i = 0; i < _pathPoints.Count - 1; i++) { totalDistance += CalculateDistance(_pathPoints[i], _pathPoints[i + 1]); } return totalDistance; } /// /// 获取对象当前位置 /// private Point3D GetObjectPosition(ModelItem item) { try { if (item == null) return new Point3D(0, 0, 0); var bounds = item.BoundingBox(); if (bounds != null) { return new Point3D( (bounds.Min.X + bounds.Max.X) / 2, (bounds.Min.Y + bounds.Max.Y) / 2, (bounds.Min.Z + bounds.Max.Z) / 2 ); } return new Point3D(0, 0, 0); } catch (Exception ex) { LogManager.Error($"获取对象位置失败: {ex.Message}"); return new Point3D(0, 0, 0); } } /// /// 计算两点间距离 /// private double CalculateDistance(Point3D point1, Point3D point2) { var dx = point2.X - point1.X; var dy = point2.Y - point1.Y; var dz = point2.Z - point1.Z; return Math.Sqrt(dx * dx + dy * dy + dz * dz); } /// /// 更新对象位置和朝向(支持绕物体中心旋转) /// private void UpdateObjectPosition(Point3D newPosition, double newYaw = double.NaN) { try { var doc = NavisApplication.ActiveDocument; var modelItems = new ModelItemCollection { _animatedObject }; // 计算平移和旋转的增量 var deltaPos = new Vector3D( newPosition.X - _currentPosition.X, newPosition.Y - _currentPosition.Y, newPosition.Z - _currentPosition.Z ); Transform3D incrementalTransform; if (!double.IsNaN(newYaw)) { // 有旋转:需要实现"绕物体当前位置自转" // 由于Transform3DComponents.Rotation总是绕世界原点旋转 // 我们需要手动计算旋转导致的位置偏移,并补偿 double deltaYaw = newYaw - _currentYaw; //LogManager.Debug($"[UpdateObjectPosition] 当前yaw={_currentYaw * 180 / Math.PI:F2}度, 目标yaw={newYaw * 180 / Math.PI:F2}度, 旋转增量deltaYaw={deltaYaw * 180 / Math.PI:F2}度"); // 计算绕当前位置旋转的等效变换: // 1. 如果绕原点旋转deltaYaw,当前位置_currentPosition会移动到哪里? double cos = Math.Cos(deltaYaw); double sin = Math.Sin(deltaYaw); double rotatedX = _currentPosition.X * cos - _currentPosition.Y * sin; double rotatedY = _currentPosition.X * sin + _currentPosition.Y * cos; // 2. 但我们希望物体绕自己旋转,位置移动到newPosition // 所以需要的平移 = newPosition - (旋转后的位置) var compensatedTranslation = new Vector3D( newPosition.X - rotatedX, newPosition.Y - rotatedY, newPosition.Z - _currentPosition.Z // Z保持deltaPos ); // 3. 组合:先旋转(绕原点),再平移(补偿+目标位置) var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)); var components = identity.Factor(); components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw); components.Translation = compensatedTranslation; incrementalTransform = components.Combine(); _currentYaw = newYaw; } else { // 纯平移 incrementalTransform = Transform3D.CreateTranslation(deltaPos); LogManager.Debug($"[UpdateObjectPosition] 纯平移: ({deltaPos.X:F2},{deltaPos.Y:F2},{deltaPos.Z:F2})"); } // 应用增量变换(false = 增量模式) doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false); // 更新当前位置 _currentPosition = newPosition; } catch (Exception ex) { LogManager.Error($"更新部件位置失败: {ex.Message}"); } } /// /// 获取文档单位信息(用于日志记录) /// private string GetDocumentUnitsInfo() { try { var doc = NavisApplication.ActiveDocument; var units = doc.Units; LogManager.Debug($"[单位检测] 文档单位: {units}"); return units.ToString(); } catch (Exception ex) { LogManager.Error($"获取文档单位信息失败: {ex.Message}"); return "Unknown"; } } /// /// 获取模型当前变换 /// private Transform3D GetCurrentTransform(ModelItem item) { // 获取包围盒中心作为参考点 var boundingBox = item.BoundingBox(); var center = boundingBox.Center; // 创建基于中心点的单位变换 return Transform3D.CreateTranslation(new Vector3D(center.X, center.Y, center.Z)); } /// /// 设置动画持续时间 /// public void SetAnimationDuration(double durationSeconds) { _animationDuration = Math.Max(1.0, durationSeconds); } /// /// 获取动画状态 /// public bool IsAnimating => _currentState == AnimationState.Playing; /// /// 获取 TimeLiner 是否可用 /// public bool IsTimeLinerAvailable => _timeLinerManager?.IsTimeLinerAvailable ?? false; /// /// 获取当前动画状态 /// public AnimationState CurrentState => _currentState; /// /// 获取当前 TimeLiner 任务ID /// public string CurrentTaskId => _currentTaskId; /// /// 获取 TimeLiner 集成管理器 /// public TimeLinerIntegrationManager TimeLinerManager => _timeLinerManager; #region 帧控制功能 /// /// 获取当前帧索引 /// public int CurrentFrame => _currentFrameIndex; /// /// 获取总帧数 /// public int TotalFrames => _animationFrames?.Count ?? 0; /// /// 获取物体当前位置和朝向 /// 🔥 关键:返回实际当前朝向(_currentYaw),而不是 Transform 的 CAD 原始朝向 /// public (Point3D Position, double Yaw) GetObjectCurrentPosition(ModelItem obj) { if (obj == null) { LogManager.Warning("GetObjectCurrentPosition: 对象为空"); return (null, 0); } var bbox = obj.BoundingBox(); var position = new Point3D( bbox.Center.X, bbox.Center.Y, bbox.Min.Z ); // 🔥 关键:使用 _currentYaw(实际当前朝向),而不是 Transform 的 CAD 原始朝向 // Transform 返回的是 CAD 设计时的原始朝向,不是动画后的实际朝向 return (position, _currentYaw); } /// /// 保存物体当前位置和朝向 /// public void SaveObjectState(ModelItem obj) { if (obj == null) { LogManager.Warning("SaveObjectState: 对象为空"); return; } try { var (position, yaw) = GetObjectCurrentPosition(obj); _savedObjectPosition = position; _savedObjectYaw = yaw; _hasSavedObjectState = true; LogManager.Info($"已保存物体状态: pos=({position.X:F2},{position.Y:F2},{position.Z:F2}), yaw={yaw * 180 / Math.PI:F2}度"); } catch (Exception ex) { LogManager.Error($"SaveObjectState: 保存物体状态失败: {ex.Message}"); } } /// /// 恢复动画物体到保存的位置和朝向 /// 此方法会更新 PathAnimationManager 的内部状态(_currentYaw等) /// public void RestoreAnimatedObjectState(ModelItem obj) { if (obj == null) { LogManager.Warning("RestoreAnimatedObjectState: 对象为空"); return; } if (!_hasSavedObjectState) { LogManager.Warning("RestoreAnimatedObjectState: 没有保存的物体状态"); return; } try { var originalAnimatedObject = _animatedObject; _animatedObject = obj; var bbox = obj.BoundingBox(); _currentPosition = new Point3D(bbox.Center.X, bbox.Center.Y, bbox.Min.Z); // 使用保存的朝向作为当前朝向,避免UpdateObjectPosition计算不必要的大角度差 // 这样能确保物体直接从当前动画位置平滑恢复到保存位置,不会突然转动 _currentYaw = _savedObjectYaw; UpdateObjectPosition(_savedObjectPosition, _savedObjectYaw); _animatedObject = originalAnimatedObject; LogManager.Info($"已恢复物体状态: pos=({_savedObjectPosition.X:F2},{_savedObjectPosition.Y:F2},{_savedObjectPosition.Z:F2}), yaw={_savedObjectYaw * 180 / Math.PI:F2}度"); } catch (Exception ex) { LogManager.Error($"RestoreAnimatedObjectState: 恢复动画物体状态失败: {ex.Message}"); } } /// /// 获取播放方向(1=正向,-1=反向) /// public int PlaybackDirection => _playbackDirection; /// /// 获取播放速度倍率 /// public double PlaybackSpeed => _playbackSpeed; /// /// 获取是否正在反向播放 /// public bool IsPlayingReverse => _playbackDirection == -1; /// /// 设置播放方向 /// /// true=反向播放,false=正向播放 public void SetPlaybackDirection(bool reverse) { _playbackDirection = reverse ? -1 : 1; LogManager.Info($"播放方向设置为: {(reverse ? "反向" : "正向")}"); } /// /// 设置播放速度 /// /// 播放速度倍率,负值表示反向 public void SetPlaybackSpeed(double speed) { _playbackSpeed = Math.Abs(speed); _playbackDirection = speed >= 0 ? 1 : -1; // 更新帧间隔 _frameInterval = 1000.0 / (_animationFrameRate * _playbackSpeed); LogManager.Info($"播放速度设置为: {speed}x,方向: {(speed >= 0 ? "正向" : "反向")}"); } /// /// 跳转到指定帧 /// /// 目标帧索引 public void SeekToFrame(int frameIndex) { try { if (_animationFrames == null || _animationFrames.Count == 0) { LogManager.Warning("无动画帧数据,无法跳转"); return; } // 限制帧索引范围 frameIndex = Math.Max(0, Math.Min(frameIndex, _animationFrames.Count - 1)); if (frameIndex == _currentFrameIndex) { LogManager.Debug($"已处于目标帧 {frameIndex},无需跳转"); return; } _currentFrameIndex = frameIndex; // 更新对象位置和朝向 var frameData = _animationFrames[_currentFrameIndex]; UpdateObjectPosition(frameData.Position, frameData.YawRadians); // 更新碰撞高亮 UpdateCollisionHighlightFromFrame(); // 计算并触发进度事件 double progress = TotalFrames > 1 ? (double)_currentFrameIndex / (TotalFrames - 1) * 100 : 0; ProgressChanged?.Invoke(this, progress); } catch (Exception ex) { LogManager.Error($"跳转到帧失败: {ex.Message}"); } } /// /// 跳转到指定进度 /// /// 进度值(0.0-1.0) public void SeekToProgress(double progress) { try { progress = Math.Max(0.0, Math.Min(1.0, progress)); int targetFrame = TotalFrames > 1 ? (int)(progress * (TotalFrames - 1)) : 0; SeekToFrame(targetFrame); } catch (Exception ex) { LogManager.Error($"跳转到进度失败: {ex.Message}"); } } /// /// 单帧前进 /// public void StepForward() { StepForward(1); } /// /// 前进指定帧数 /// /// 帧数 public void StepForward(int frames) { try { if (frames <= 0) return; int targetFrame = _currentFrameIndex + frames; targetFrame = Math.Min(targetFrame, TotalFrames - 1); SeekToFrame(targetFrame); } catch (Exception ex) { LogManager.Error($"前进步进失败: {ex.Message}"); } } /// /// 单帧后退 /// public void StepBackward() { StepBackward(1); } /// /// 后退指定帧数 /// /// 帧数 public void StepBackward(int frames) { try { if (frames <= 0) return; int targetFrame = _currentFrameIndex - frames; targetFrame = Math.Max(targetFrame, 0); SeekToFrame(targetFrame); } catch (Exception ex) { LogManager.Error($"后退步进失败: {ex.Message}"); } } /// /// 快进10帧 /// public void FastForward() { StepForward(10); } /// /// 快退10帧 /// public void FastBackward() { StepBackward(10); } /// /// 开始正向播放 /// public void PlayForward() { try { SetPlaybackDirection(false); if (_currentState == AnimationState.Paused) { ResumeAnimation(); // 从暂停位置继续 } else if (_currentState != AnimationState.Playing) { StartAnimation(); // 只有非播放和非暂停状态才从头开始 } LogManager.Info("开始正向播放"); } catch (Exception ex) { LogManager.Error($"正向播放失败: {ex.Message}"); } } /// /// 开始反向播放 /// public void PlayReverse() { try { SetPlaybackDirection(true); if (_currentState == AnimationState.Paused) { ResumeAnimation(); // 从暂停位置继续 } else if (_currentState != AnimationState.Playing) { StartAnimation(); // 只有非播放和非暂停状态才从头开始 } LogManager.Info("开始反向播放"); } catch (Exception ex) { LogManager.Error($"反向播放失败: {ex.Message}"); } } #endregion /// /// 清理对象引用 /// public void ClearReferences() { try { LogManager.Info("[PathAnimationManager] 清理对象引用"); // 停止动画 if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused) { ShutdownAnimation(); } // 清空对象引用 _animatedObject = null; _pathPoints?.Clear(); _currentPosition = new Point3D(0, 0, 0); _originalCenter = new Point3D(0, 0, 0); LogManager.Info("[PathAnimationManager] 对象引用已清理"); } catch (Exception ex) { LogManager.Error($"[PathAnimationManager] 清理对象引用失败: {ex.Message}"); } } /// /// 文档失效事件处理 /// private void OnDocumentInvalidated(object sender, EventArgs e) { try { LogManager.Info("[PathAnimationManager] 文档失效,停止动画并清理"); ClearReferences(); } catch (Exception ex) { LogManager.Error($"[PathAnimationManager] 处理文档失效事件失败: {ex.Message}"); } } /// /// 资源清理 /// public void Dispose() { try { LogManager.Info("开始清理PathAnimationManager资源"); // 0. 取消配置变更事件订阅 UnsubscribeFromConfigChanges(); // 1. 优先停止动画(最小化的操作,不触发复杂的重置逻辑) // 只有在动画运行时才停止,避免重复调用 try { if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused) { ShutdownAnimation(); } } catch (Exception stopEx) { LogManager.Warning($"停止动画时出现警告: {stopEx.Message},继续清理其他资源"); } // 2. 在Dispose中不进行重置操作,避免访问已释放的对象 // 程序关闭时,对象位置重置是不必要的 LogManager.Info("跳过动画重置操作(程序关闭时不需要恢复对象位置)"); // 3. 清理 TimeLiner 资源 if (_timeLinerManager != null) { try { _timeLinerManager.Dispose(); _timeLinerManager = null; LogManager.Info("TimeLiner 集成资源已清理"); } catch (Exception ex) { LogManager.Warning($"清理 TimeLiner 资源时出现警告: {ex.Message}"); } } // 4. 清理DispatcherTimer资源 if (_animationTimer != null) { try { _animationTimer.Stop(); // DispatcherTimer不需要Dispose _animationTimer = null; LogManager.Info("DispatcherTimer资源已清理"); } catch (Exception ex) { LogManager.Warning($"清理DispatcherTimer资源时出现警告: {ex.Message}"); } } // 5. 取消订阅文档事件 DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated; LogManager.Info("PathAnimationManager资源清理完成"); } catch (Exception ex) { LogManager.Error($"PathAnimationManager资源清理过程中发生异常: {ex.Message}"); // 不抛出异常,避免影响应用程序正常关闭 } } /// /// 设置动画帧率 /// public void SetAnimationFrameRate(int frameRate) { _animationFrameRate = Math.Max(10, Math.Min(60, frameRate)); // 限制在10-60FPS之间 _frameInterval = 1000.0 / _animationFrameRate; // 重新计算帧间隔 // 动态更新DispatcherTimer间隔 if (_animationTimer != null) { _animationTimer.Interval = TimeSpan.FromMilliseconds(_frameInterval); LogManager.Info($"DispatcherTimer间隔已更新: {_frameInterval:F1}ms"); } LogManager.Info($"动画帧率设置为: {_animationFrameRate}FPS (间隔: {_frameInterval:F1}ms)"); } /// /// 设置碰撞检测精度(参数单位:米) /// public void SetCollisionDetectionAccuracy(double accuracyInMeters) { // 转换为模型单位存储 var accuracyInModelUnits = UnitsConverter.ConvertFromMeters(Math.Max(0.01, accuracyInMeters)); _collisionDetectionAccuracy = accuracyInModelUnits; LogManager.Info($"碰撞检测精度设置为: {accuracyInMeters:F3}米"); } /// /// 设置运动速度 /// public void SetMovementSpeed(double speed) { _movementSpeed = Math.Max(0.1, speed); // 最小0.1米/秒 LogManager.Info($"运动速度设置为: {_movementSpeed}m/s"); } /// /// 设置检测容差(参数单位:米) /// public void SetDetectionTolerance(double toleranceInMeters) { _detectionTolerance = Math.Max(0.0, toleranceInMeters); LogManager.Info($"检测容差设置为: {_detectionTolerance:F2}米(仅用于ClashDetective)"); } /// /// 设置手工碰撞对象覆写列表 /// public void SetManualCollisionTargets(IEnumerable targets, bool enabled) { if (!enabled) { _manualCollisionOverrideEnabled = false; _manualCollisionTargets = new List(); LogManager.Info("[PathAnimationManager] 手工碰撞对象覆写已关闭"); return; } if (targets == null) { _manualCollisionOverrideEnabled = false; _manualCollisionTargets = new List(); LogManager.Warning("[PathAnimationManager] 手工碰撞对象覆写启用但目标为空,已回退到默认模式"); return; } var validTargets = targets .Where(item => item != null && ModelItemAnalysisHelper.IsModelItemValid(item)) .Where(item => !ReferenceEquals(item, _animatedObject)) .Distinct() .ToList(); if (validTargets.Count == 0) { _manualCollisionOverrideEnabled = false; _manualCollisionTargets = new List(); LogManager.Warning("[PathAnimationManager] 手工碰撞对象覆写启用但均无效,已回退到默认模式"); return; } _manualCollisionOverrideEnabled = true; _manualCollisionTargets = validTargets; LogManager.Info($"[PathAnimationManager] 启用手工碰撞对象覆写,共 {validTargets.Count} 个对象"); } /// /// 获取当前动画帧率 /// public int AnimationFrameRate => _animationFrameRate; /// /// 获取所有碰撞结果(不去重) /// public List AllCollisionResults => _allCollisionResults; /// /// 获取当前物体的朝向(弧度) /// 用于保存物体当前状态(因为Transform返回的是CAD原始值) /// public double CurrentYaw => _currentYaw; /// /// 获取当前碰撞检测精度 /// public double CollisionDetectionAccuracy => _collisionDetectionAccuracy; /// /// 获取当前运动速度 /// public double MovementSpeed => _movementSpeed; /// /// 获取当前检测间隙(单位:米) /// public double DetectionGap { get { return _detectionTolerance; } } /// /// 获取路径名称 /// public string PathName => _pathName; /// /// 获取动画时长 /// public double AnimationDuration => _animationDuration; /// /// 设置并触发状态变更事件 /// /// 新的动画状态 private void SetState(AnimationState newState) { if (_currentState != newState) { _currentState = newState; StateChanged?.Invoke(this, _currentState); } } /// /// 创建动画 /// /// 要动画化的模型对象 /// 路径点列表 /// 动画持续时间(秒) /// 路径名称 /// 路由ID public void CreateAnimation(ModelItem animatedObject, List pathPoints, double durationSeconds = 10.0, string pathName = "未知路径", string routeId = null, bool isVirtualVehicle = false, double virtualVehicleLength = 0, double virtualVehicleWidth = 0, double virtualVehicleHeight = 0, double safetyMargin = 0) { _pathName = pathName; _currentRouteId = routeId; _isVirtualVehicle = isVirtualVehicle; // 设置是否使用虚拟车辆 _virtualVehicleLength = virtualVehicleLength; // 模型单位 _virtualVehicleWidth = virtualVehicleWidth; // 模型单位 _virtualVehicleHeight = virtualVehicleHeight; // 模型单位 _safetyMargin = safetyMargin; // 模型单位 _pathPoints = pathPoints; _animatedObject = animatedObject; // 初始化物体位置和朝向 if (animatedObject != null) { _originalTransform = animatedObject.Transform; _originalCenter = animatedObject.BoundingBox().Center; _currentPosition = new Point3D(_originalCenter.X, _originalCenter.Y, animatedObject.BoundingBox().Min.Z); // 保持当前的 _currentYaw(因为物体可能已经被 MoveVehicleToPathStart 旋转) // 不要从 Transform 中提取,因为 Transform 返回的是原始值,不是当前值 LogManager.Debug($"[CreateAnimation] 保持_currentYaw={_currentYaw * 180 / Math.PI:F2}度不变, _isVirtualVehicle={_isVirtualVehicle}"); } // 设置动画参数并预计算动画帧 // 注意:物体应该在调用CreateAnimation之前已经通过MoveVehicleToPathStart旋转到起点 LogManager.Debug($"[CreateAnimation开始] _currentYaw之前={_currentYaw * 180 / Math.PI:F2}度, _isVirtualVehicle={_isVirtualVehicle}"); SetupAnimation(animatedObject, durationSeconds, _route); LogManager.Debug($"[CreateAnimation结束] _currentYaw之后={_currentYaw * 180 / Math.PI:F2}度"); // 设置动画状态为Ready(动画已生成,可以播放) SetState(AnimationState.Ready); LogManager.Info($"[CreateAnimation] 动画已创建,状态设置为Ready"); } /// /// 设置物体角度修正值(度,顺时针) /// /// 角度修正值(度) public void SetObjectRotationCorrection(double rotationCorrection) { _objectRotationCorrection = rotationCorrection; // 如果动画已创建,更新物体到起点的朝向 if (_animatedObject != null && _pathPoints != null && _pathPoints.Count > 0) { try { // 重新计算并应用朝向 MoveVehicleToPathStart(); LogManager.Info($"[角度修正] 物体角度已更新: {_objectRotationCorrection:F1}°"); } catch (Exception ex) { LogManager.Error($"[角度修正] 更新物体角度失败: {ex.Message}"); } } } /// /// 直接设置物体角度修正值(不触发物体旋转) /// /// 角度修正值(度) public void SetObjectRotationCorrectionDirect(double rotationCorrection) { _objectRotationCorrection = rotationCorrection; LogManager.Debug($"[角度修正] 直接设置角度修正值: {_objectRotationCorrection:F1}°(不触发旋转)"); } #region 动画实现方法 /// /// DispatcherTimer动画处理核心 /// /// /// 根据当前帧索引更新碰撞高亮(状态跟踪优化版本) /// private void UpdateCollisionHighlightFromFrame() { try { // 确保帧索引有效 if (_currentFrameIndex < 0 || _currentFrameIndex >= _animationFrames.Count) { return; } // 获取当前帧数据 var currentFrame = _animationFrames[_currentFrameIndex]; bool currentHasCollision = currentFrame.HasCollision; // 收集当前帧碰撞对象的集合 var currentCollisionObjects = new HashSet(); if (currentHasCollision && currentFrame.Collisions != null) { foreach (var collision in currentFrame.Collisions) { if (collision.Item1 != null) currentCollisionObjects.Add(collision.Item1); if (collision.Item2 != null) currentCollisionObjects.Add(collision.Item2); } } // 检查是否需要更新高亮: // 1. 碰撞状态改变(有碰撞 vs 无碰撞) // 2. 碰撞对象集合改变(碰撞的物体变了) bool stateChanged = currentHasCollision != _lastHighlightState; bool objectsChanged = !currentCollisionObjects.SetEquals(_lastCollisionObjects); if (stateChanged || objectsChanged) { if (currentHasCollision) { // 有碰撞:高亮当前帧的碰撞对象(使用CollisionResultsCategory的默认颜色) ModelHighlightHelper.ManageCollisionHighlightsByCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory, currentFrame.Collisions, clearOtherCategories: true); LogManager.Debug($"[高亮状态] 帧{_currentFrameIndex}: 高亮碰撞 ({currentFrame.Collisions.Count}个, 对象{currentCollisionObjects.Count}个)"); } else { // 无碰撞:清除碰撞高亮(保留车辆高亮) ModelHighlightHelper.ClearCategory(ModelHighlightHelper.PrecomputeCollisionResultsCategory); LogManager.Debug($"[高亮状态] 帧{_currentFrameIndex}: 清除碰撞高亮"); } // 更新状态记录 _lastHighlightState = currentHasCollision; _lastCollisionObjects = currentCollisionObjects; } } catch (Exception ex) { LogManager.Error($"更新碰撞高亮失败: {ex.Message}"); } } /// /// 统一的动画帧处理逻辑(DispatcherTimer专用) /// private void ProcessAnimationFrame() { // 检查动画状态 if (_currentState != AnimationState.Playing) { return; // 非播放状态,跳过更新 } // DispatcherTimer已经控制了调用间隔,无需手动帧率控制 var now = DateTime.Now; // 计算下一帧索引(考虑播放方向和速度) int nextFrameIndex = _currentFrameIndex; if (_playbackDirection == 1) // 正向播放 { nextFrameIndex = _currentFrameIndex + 1; if (nextFrameIndex >= _animationFrames.Count) { // 正向播放到达终点 nextFrameIndex = _animationFrames.Count - 1; StopAnimationPlayback(); FinishAnimation(); return; } } else // 反向播放 { nextFrameIndex = _currentFrameIndex - 1; if (nextFrameIndex < 0) { // 反向播放到达起点 nextFrameIndex = 0; StopAnimationPlayback(); FinishAnimation(); return; } } // 更新帧索引和位置 if (nextFrameIndex != _currentFrameIndex) { _currentFrameIndex = nextFrameIndex; // 使用预计算的帧位置和朝向 if (_currentFrameIndex < _animationFrames.Count) { var frameData = _animationFrames[_currentFrameIndex]; // 计算实际朝向 = 路径方向 + 角度修正 double actualYaw = frameData.YawRadians; if (_objectRotationCorrection != 0.0) { double correctionRad = _objectRotationCorrection * Math.PI / 180.0; actualYaw += correctionRad; } UpdateObjectPosition(frameData.Position, actualYaw); // 更新碰撞高亮(基于预计算结果) UpdateCollisionHighlightFromFrame(); // 计算当前进度(基于帧索引) double progress = _animationFrames.Count > 1 ? (double)_currentFrameIndex / (_animationFrames.Count - 1) : 0; // 触发进度事件 var progressPercent = progress * 100; ProgressChanged?.Invoke(this, progressPercent); // 同步进度到 TimeLiner(如果可用) if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) { _timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState); } } } // 记录帧时间和统计 _lastFrameTime = now; // 性能监控和自动切换 UpdateFPSCounterAndCheckSwitch(); } /// /// 更新FPS计数器(DispatcherTimer模式) /// private void UpdateFPSCounterAndCheckSwitch() { _fpsFrameCount++; var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds; // 每秒更新一次FPS统计 if (elapsed >= 1.0) { _actualFPS = _fpsFrameCount / elapsed; LogManager.Debug($"[性能监控] 实际FPS: {_actualFPS:F1} (目标: {_animationFrameRate}) - DispatcherTimer模式"); // 重置计数器 _fpsFrameCount = 0; _fpsCounterStart = DateTime.Now; } } #region 碰撞测试优化方法 /// /// 计算动画配置的哈希值(基于路径点和对象) /// private string ComputeAnimationConfigHash(ModelItem animatedObject, List pathPoints) { try { var sb = new StringBuilder(); // 包含对象名称 sb.Append(animatedObject?.DisplayName ?? "null"); sb.Append("|"); // 包含路径点信息 if (pathPoints != null) { foreach (var point in pathPoints) { sb.Append($"{point.X:F2},{point.Y:F2},{point.Z:F2};"); } } // 计算哈希 using (var md5 = System.Security.Cryptography.MD5.Create()) { var inputBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString()); var hashBytes = md5.ComputeHash(inputBytes); return BitConverter.ToString(hashBytes).Replace("-", ""); } } catch (Exception ex) { LogManager.Warning($"计算动画配置哈希失败: {ex.Message}"); return DateTime.Now.Ticks.ToString(); // 回退到时间戳 } } /// /// 计算动画配置的哈希值(基于PathRoute的Edges,可以区分不同的曲线化路径) /// private string ComputeAnimationConfigHashFromRoute(ModelItem animatedObject, PathRoute route) { try { var sb = new StringBuilder(); // 包含对象的唯一标识(使用PathId而不是DisplayName) if (animatedObject != null) { try { var pathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(animatedObject); sb.Append($"{pathId.ModelIndex}:{pathId.PathId}"); } catch { sb.Append(animatedObject.DisplayName ?? "null"); } } else { sb.Append("null"); } sb.Append("|"); // 包含路径ID(区分不同的路径) sb.Append(route?.Id ?? "null"); sb.Append("|"); // 包含动画参数(确保参数改变时重新检测) sb.Append($"Duration:{_animationDuration:F2}s"); sb.Append($"|FrameRate:{_animationFrameRate}"); sb.Append($"|DetectionTolerance:{_detectionTolerance:F4}"); sb.Append("|"); // 包含虚拟车辆尺寸(确保车辆尺寸改变时重新检测) // 使用传入的车辆尺寸参数,而不是从ConfigManager读取 var vehicleSizeString = $"Vehicle:{_virtualVehicleLength:F2}x{_virtualVehicleWidth:F2}x{_virtualVehicleHeight:F2}"; sb.Append(vehicleSizeString); sb.Append("|"); // 包含角度修正(确保角度修正改变时重新检测) sb.Append($"RotationCorrection:{_objectRotationCorrection:F2}deg"); sb.Append("|"); // 包含手工检测对象列表(确保手工指定模式的目标变化时重新检测) // 使用PathId作为唯一标识,而不是DisplayName if (_manualCollisionOverrideEnabled && _manualCollisionTargets != null && _manualCollisionTargets.Count > 0) { sb.Append("ManualTargets:"); foreach (var target in _manualCollisionTargets.OrderBy(t => t.DisplayName)) { try { var targetPathId = Autodesk.Navisworks.Api.Application.ActiveDocument.Models.CreatePathId(target); sb.Append($"{targetPathId.ModelIndex}:{targetPathId.PathId}"); sb.Append(","); } catch (Exception) { // 忽略无效对象 } } sb.Append("|"); } // 包含路径边的摘要信息(区分不同的曲线化结果) if (route?.Edges != null && route.Edges.Count > 0) { foreach (var edge in route.Edges) { sb.Append(edge.SegmentType.ToString()); sb.Append($"({edge.PhysicalLength:F2})"); if (edge.SegmentType == PathSegmentType.Arc && edge.Trajectory != null) { sb.Append($"_R{edge.Trajectory.ActualRadius:F2}"); sb.Append($"_A{edge.Trajectory.DeflectionAngle:F2}"); } sb.Append(";"); } } // 包含路径最后修改时间(确保路径修改后重新生成动画) sb.Append($"LastModified:{route?.LastModified:yyyyMMddHHmmss}"); // 计算哈希 using (var md5 = System.Security.Cryptography.MD5.Create()) { var inputBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString()); var hashBytes = md5.ComputeHash(inputBytes); return BitConverter.ToString(hashBytes).Replace("-", ""); } } catch (Exception ex) { LogManager.Warning($"计算动画配置哈希失败: {ex.Message}"); return DateTime.Now.Ticks.ToString(); // 回退到时间戳 } } /// /// 强制重新创建碰撞测试(清除指定配置的碰撞检测记录) /// public void ForceRecreateCollisionTest() { if (!string.IsNullOrEmpty(_currentAnimationHash)) { _completedCollisionTests.Remove(_currentAnimationHash); LogManager.Info($"已清除动画配置 {_currentAnimationHash} 的碰撞检测记录,下次播放时将重新创建"); } } /// /// 清除所有碰撞测试记录 /// public static void ClearAllCollisionTestRecords() { _completedCollisionTests.Clear(); LogManager.Info("已清除所有碰撞测试记录"); } #endregion #region 碰撞排除列表缓存管理(从LogisticsAnimationManager迁移) /// /// 为动画对象预计算并缓存排除列表 /// /// 动画对象 /// 是否成功缓存 public bool PrecomputeCollisionExclusions(ModelItem animationObject) { try { LogManager.Info($"[缓存管理] 开始为动画对象预计算排除列表: {animationObject?.DisplayName ?? "NULL"}"); if (animationObject == null) { LogManager.Warning("[缓存管理] 动画对象为空,清除缓存"); ClearExclusionCache(); return false; } // 检查是否需要重新计算 if (IsCacheValid(animationObject)) { LogManager.Info($"[缓存管理] 缓存仍然有效,跳过重新计算"); return true; } var startTime = DateTime.Now; // 只构建特定于动画对象的逻辑排除列表 LogManager.Info("[缓存管理] 构建逻辑排除列表..."); var exclusionList = ModelItemAnalysisHelper.CollectRelatedNodes(animationObject); // 更新缓存 _currentCachedAnimationObject = animationObject; _cachedExclusionList = exclusionList; _cacheCreatedTime = DateTime.Now; var elapsedMs = (DateTime.Now - startTime).TotalMilliseconds; LogManager.Info($"[缓存管理] 预计算完成,耗时 {elapsedMs:F1}ms,缓存 {exclusionList.Count} 个排除对象"); return true; } catch (Exception ex) { LogManager.Error($"[缓存管理] 预计算排除列表失败: {ex.Message}", ex); ClearExclusionCache(); return false; } } /// /// 获取缓存的排除列表 /// /// 动画对象 /// 排除列表,如果缓存无效则返回null public List GetCachedExclusionList(ModelItem animationObject) { try { if (!IsCacheValid(animationObject)) { LogManager.Debug($"[缓存管理] 缓存无效,建议重新预计算"); return null; } LogManager.Debug($"[缓存管理] 返回缓存的排除列表: {_cachedExclusionList?.Count ?? 0} 个对象"); return _cachedExclusionList; } catch (Exception ex) { LogManager.Warning($"[缓存管理] 获取缓存失败: {ex.Message}"); return null; } } /// /// 检查缓存是否有效 /// /// 当前动画对象 /// 缓存是否有效 private bool IsCacheValid(ModelItem animationObject) { try { // 检查是否有缓存 if (_currentCachedAnimationObject == null || _cachedExclusionList == null) { return false; } // 检查对象是否相同 if (!_currentCachedAnimationObject.Equals(animationObject)) { string cachedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); string currentObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animationObject); LogManager.Debug($"[缓存验证] 动画对象已变更: '{cachedObjectName}' -> '{currentObjectName}'"); return false; } // 检查缓存时间是否过期 if (DateTime.Now - _cacheCreatedTime > _cacheValidDuration) { LogManager.Debug($"[缓存验证] 缓存已过期: {(DateTime.Now - _cacheCreatedTime).TotalMinutes:F1} 分钟"); return false; } return true; } catch (Exception ex) { LogManager.Warning($"[缓存验证] 验证过程异常: {ex.Message}"); return false; } } /// /// 清除排除列表缓存 /// public void ClearExclusionCache() { try { if (_currentCachedAnimationObject != null) { try { // 安全访问DisplayName,防止WeakRef已释放的警告 string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); LogManager.Debug($"[缓存管理] 清除缓存: {objectName}"); } catch (Exception ex) { LogManager.Debug($"[缓存管理] 清除缓存时访问对象名称失败: {ex.Message}"); } } _currentCachedAnimationObject = null; _cachedExclusionList = null; _cacheCreatedTime = DateTime.MinValue; } catch (Exception ex) { LogManager.Warning($"[缓存管理] 清除缓存失败: {ex.Message}"); } } /// /// 获取缓存统计信息 /// /// 缓存统计信息 public string GetCacheStats() { try { if (_currentCachedAnimationObject == null) { return "缓存状态: 无缓存"; } var age = DateTime.Now - _cacheCreatedTime; string objectName = ModelItemAnalysisHelper.GetSafeDisplayName(_currentCachedAnimationObject); var count = _cachedExclusionList?.Count ?? 0; return $"缓存状态: 对象='{objectName}', 排除数={count}, 年龄={age.TotalSeconds:F1}秒"; } catch (Exception ex) { return $"缓存状态: 获取失败 - {ex.Message}"; } } #endregion #region Human-in-the-Loop 排除列表管理 /// /// 添加排除物体 /// /// 要排除的物体 public void AddExcludedObject(ModelItem obj) { if (obj != null) { _excludedObjects.Add(obj); LogManager.Debug($"[排除列表] 添加排除物体: {ModelItemAnalysisHelper.GetSafeDisplayName(obj)}, 当前共 {_excludedObjects.Count} 个"); } } /// /// 批量添加排除物体 /// /// 要排除的物体列表 public void AddExcludedObjects(IEnumerable objects) { if (objects != null) { foreach (var obj in objects) { if (obj != null) _excludedObjects.Add(obj); } LogManager.Info($"[排除列表] 批量添加 {objects.Count()} 个排除物体, 当前共 {_excludedObjects.Count} 个"); } } /// /// 移除排除物体 /// /// 要移除的物体 public void RemoveExcludedObject(ModelItem obj) { if (obj != null && _excludedObjects.Remove(obj)) { LogManager.Debug($"[排除列表] 移除排除物体: {ModelItemAnalysisHelper.GetSafeDisplayName(obj)}, 当前共 {_excludedObjects.Count} 个"); } } /// /// 清除所有排除物体 /// public void ClearExcludedObjects() { var count = _excludedObjects.Count; _excludedObjects.Clear(); LogManager.Info($"[排除列表] 清除所有 {count} 个排除物体"); } /// /// 获取当前排除物体列表 /// /// 排除物体列表 public IReadOnlyCollection GetExcludedObjects() { return _excludedObjects.ToList().AsReadOnly(); } /// /// 检查物体是否在排除列表中 /// /// 要检查的物体 /// 是否被排除 public bool IsObjectExcluded(ModelItem obj) { return obj != null && _excludedObjects.Contains(obj); } /// /// 获取排除物体数量 /// public int ExcludedObjectCount => _excludedObjects.Count; /// /// 【已废弃】排除列表现在是实时管理的,不绑定到动画缓存 /// [Obsolete("排除列表现在是实时管理的,此方法不再执行任何操作", false)] public void SaveExclusionsToCache() { // 排除列表是实时管理的,不保存到动画缓存 LogManager.Debug($"[排除列表] 实时管理模式,当前共 {_excludedObjects.Count} 个排除物体"); } /// /// 【已废弃】排除列表现在是实时管理的,不绑定到动画缓存 /// [Obsolete("排除列表现在是实时管理的,此方法始终返回false", false)] public bool LoadExclusionsFromCache() { // 排除列表是实时管理的,不从缓存加载 LogManager.Debug("[排除列表] 实时管理模式,不从缓存加载"); return false; } /// /// 【已废弃】排除列表现在是实时管理的,不绑定到动画缓存 /// [Obsolete("排除列表现在是实时管理的,此方法不再执行任何操作", false)] public static void ClearAllExclusionCaches() { // 排除列表是实时管理的,没有缓存需要清除 LogManager.Debug("[排除列表] 实时管理模式,无缓存需要清除"); } /// /// 设置排除物体并清除当前动画缓存(用于重新生成) /// /// 新的排除物体列表 public void SetExcludedObjectsAndClearCache(IEnumerable objects) { // 1. 更新排除列表 _excludedObjects.Clear(); if (objects != null) { foreach (var obj in objects) { if (obj != null) _excludedObjects.Add(obj); } } // 2. 清除当前动画缓存(强制重新预计算) if (!string.IsNullOrEmpty(_currentAnimationHash)) { _animationFrameCache.Remove(_currentAnimationHash); _collisionResultCache.Remove(_currentAnimationHash); LogManager.Info($"[排除列表] 已更新排除列表({_excludedObjects.Count}个)并清除当前动画缓存(排除列表实时生效)"); } else { LogManager.Info($"[排除列表] 已更新排除列表({_excludedObjects.Count}个)(排除列表实时生效)"); } } /// /// 从数据库加载排除对象 /// public void LoadExcludedObjectsFromDatabase(PathDatabase database, string routeId = null) { try { if (database == null) { LogManager.Warning("[排除列表] 数据库为空,无法加载排除对象"); return; } // 从数据库获取排除对象记录 var records = database.GetExcludedObjects(routeId, includeGlobal: true); if (records == null || records.Count == 0) { LogManager.Info($"[排除列表] 数据库中没有排除对象记录"); return; } // 将记录转换为 ModelItem var doc = Autodesk.Navisworks.Api.Application.ActiveDocument; if (doc == null || doc.IsClear) { LogManager.Warning("[排除列表] 没有活动文档,无法加载排除对象"); return; } var loadedObjects = new List(); int notFoundCount = 0; foreach (var record in records) { try { // 尝试通过 PathId 查找对象 var modelItem = FindModelItemByPathId(doc, record.PathId); if (modelItem != null) { loadedObjects.Add(modelItem); } else { notFoundCount++; LogManager.Debug($"[排除列表] 未找到排除对象: PathId={record.PathId}, Name={record.DisplayName}"); } } catch (Exception ex) { LogManager.Error($"[排除列表] 加载排除对象失败: {ex.Message}"); } } // 添加到排除列表 foreach (var obj in loadedObjects) { _excludedObjects.Add(obj); } LogManager.Info($"[排除列表] 从数据库加载 {loadedObjects.Count} 个排除对象,{notFoundCount} 个未找到"); } catch (Exception ex) { LogManager.Error($"[排除列表] 从数据库加载排除对象失败: {ex.Message}", ex); } } /// /// 保存排除对象到数据库 /// public void SaveExcludedObjectsToDatabase(PathDatabase database, string routeId = null) { try { if (database == null) { LogManager.Warning("[排除列表] 数据库为空,无法保存排除对象"); return; } // 清除该路径的旧排除对象记录 database.ClearExcludedObjects(routeId); // 保存当前排除对象 var records = new List(); foreach (var obj in _excludedObjects) { try { var record = new ExcludedObjectRecord { RouteId = routeId, ModelIndex = GetModelIndex(obj), PathId = GetModelItemPathId(obj), DisplayName = obj.DisplayName, ObjectName = GetModelItemObjectName(obj), ExcludedTime = DateTime.Now, Reason = "用户排除", IsGlobal = string.IsNullOrEmpty(routeId) }; records.Add(record); } catch (Exception ex) { LogManager.Error($"[排除列表] 转换排除对象记录失败: {ex.Message}"); } } // 批量保存到数据库 database.SaveExcludedObjects(records); LogManager.Info($"[排除列表] 已保存 {records.Count} 个排除对象到数据库"); } catch (Exception ex) { LogManager.Error($"[排除列表] 保存排除对象到数据库失败: {ex.Message}", ex); } } /// /// 根据 PathId 查找 ModelItem /// private ModelItem FindModelItemByPathId(Document doc, string pathId) { try { // 解析 PathId (格式: "模型索引:路径") var parts = pathId.Split(':'); if (parts.Length < 2) return null; if (!int.TryParse(parts[0], out int modelIndex)) return null; // 获取模型 var model = doc.Models[modelIndex]; if (model == null) return null; // 解析路径索引 var pathIndices = parts[1].Split(',') .Select(p => int.TryParse(p, out int idx) ? idx : -1) .Where(idx => idx >= 0) .ToArray(); if (pathIndices.Length == 0) return null; // 从根节点开始遍历 ModelItem current = model.RootItem; foreach (var index in pathIndices) { if (current == null) return null; // 将 Children 转换为列表以便索引访问 var childrenList = current.Children.ToList(); if (index >= childrenList.Count) return null; current = childrenList[index]; } return current; } catch (Exception ex) { LogManager.Error($"[排除列表] 查找 ModelItem 失败: {ex.Message}"); return null; } } /// /// 获取 ModelItem 的 PathId /// private string GetModelItemPathId(ModelItem item) { try { if (item == null) return string.Empty; // 构建路径索引数组 var indices = new List(); var current = item; while (current != null && current.Parent != null) { var parent = current.Parent; // 在父节点的子节点中查找当前节点的索引 var childrenList = parent.Children.ToList(); int index = childrenList.FindIndex(c => c == current); if (index < 0) break; indices.Insert(0, index); current = parent; } // 获取模型索引 int modelIndex = GetModelIndex(item); return $"{modelIndex}:{string.Join(",", indices)}"; } catch (Exception ex) { LogManager.Error($"[排除列表] 获取 PathId 失败: {ex.Message}"); return string.Empty; } } /// /// 获取 ModelItem 所在的模型索引 /// private int GetModelIndex(ModelItem item) { try { var doc = Autodesk.Navisworks.Api.Application.ActiveDocument; if (doc == null) return -1; // 找到根节点 var root = item; while (root.Parent != null) root = root.Parent; // 在模型列表中查找 for (int i = 0; i < doc.Models.Count; i++) { if (doc.Models[i].RootItem == root) return i; } return -1; } catch { return -1; } } /// /// 获取 ModelItem 的对象名称 /// private string GetModelItemObjectName(ModelItem item) { try { return item.ClassName ?? item.DisplayName ?? "Unknown"; } catch { return "Unknown"; } } #endregion #endregion } }