using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using Autodesk.Navisworks.Api; using NavisApplication = Autodesk.Navisworks.Api.Application; namespace NavisworksTransport { /// /// 定义动画播放的状态 /// public enum AnimationState { Idle, // 空闲,未生成动画 Ready, // 已就绪,动画已生成但未播放 Playing, // 播放中 Paused, // 暂停 Stopped, // 已停止 Finished // 已完成 } /// /// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果 /// 注意:由于Navisworks API限制,无法直接使用Animator API,因此使用OverridePermanentTransform实现动画 /// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务 /// public class PathAnimationManager { private ModelItem _animatedObject; private List _pathPoints; private Timer _animationTimer; private double _animationDuration = 10.0; // 动画总时长(秒) private DateTime _animationStartTime; private Transform3D _originalTransform; private Point3D _originalCenter; // 存储部件的原始中心位置 private Point3D _currentPosition; // 存储部件的当前位置 private AnimationState _currentState = AnimationState.Idle; // TimeLiner 集成 private TimeLinerIntegrationManager _timeLinerManager; private string _currentTaskId; // --- 新增事件 --- /// /// 当动画状态发生改变时触发 /// public event EventHandler StateChanged; /// /// 当动画进度更新时触发 (0-100) /// public event EventHandler ProgressChanged; // 动画完成事件 (旧版,保留兼容性) public event EventHandler AnimationCompleted; public PathAnimationManager() { _pathPoints = new List(); // 初始化 TimeLiner 集成 try { _timeLinerManager = new TimeLinerIntegrationManager(); LogManager.Info("PathAnimationManager 已集成 TimeLiner 功能"); } catch (Exception ex) { LogManager.Warning($"TimeLiner 集成初始化失败,将使用基础动画功能: {ex.Message}"); _timeLinerManager = null; } } /// /// 设置动画参数 /// /// 要动画化的模型对象 /// 路径点列表 /// 动画持续时间(秒) public void SetupAnimation(ModelItem animatedObject, List pathPoints, double durationSeconds = 10.0) { try { if (animatedObject == null) throw new ArgumentNullException(nameof(animatedObject)); if (pathPoints == null || pathPoints.Count < 2) throw new ArgumentException("路径点数量必须至少为2个", nameof(pathPoints)); // 添加路径点坐标有效性验证 LogManager.Info("=== 动画管理器坐标验证 ==="); bool hasInvalidCoordinates = false; for (int i = 0; i < pathPoints.Count; i++) { var point = pathPoints[i]; bool isValid = !double.IsNaN(point.X) && !double.IsNaN(point.Y) && !double.IsNaN(point.Z) && !double.IsInfinity(point.X) && !double.IsInfinity(point.Y) && !double.IsInfinity(point.Z); LogManager.Info($"路径点[{i}]坐标验证: ({point.X:F6},{point.Y:F6},{point.Z:F6}) - {(isValid ? "有效" : "无效")}"); if (!isValid) { hasInvalidCoordinates = true; LogManager.Error($"检测到无效坐标: 路径点[{i}] = ({point.X},{point.Y},{point.Z})"); } // 检查是否为零坐标(可能表示数据丢失) if (isValid && point.X == 0.0 && point.Y == 0.0 && point.Z == 0.0) { LogManager.Warning($"路径点[{i}]坐标为零,可能存在数据丢失问题"); } } if (hasInvalidCoordinates) { throw new ArgumentException("路径点包含无效坐标(NaN或无穷大),无法创建动画"); } LogManager.Info("=== 坐标验证完成 ==="); _animatedObject = animatedObject; _pathPoints = new List(pathPoints); _animationDuration = durationSeconds; // 保存原始变换以便重置 _originalTransform = GetCurrentTransform(_animatedObject); // 保存车辆的原始中心位置 var originalBoundingBox = animatedObject.BoundingBox(); _originalCenter = originalBoundingBox.Center; // 关键修复:将车辆立即移动到路径起点 // 这样动画就从路径起点开始,而不是从车辆当前位置开始 MoveVehicleToPathStart(); // 记录文档单位信息 var documentUnits = GetDocumentUnitsInfo(); // 添加详细的调试信息 LogManager.Info($"动画设置完成:对象={_animatedObject.DisplayName}, 路径点数={_pathPoints.Count}, 时长={_animationDuration}秒"); LogManager.Info($"文档单位: {documentUnits}"); LogManager.Info($"路径起点: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})"); LogManager.Info($"路径终点: ({_pathPoints[_pathPoints.Count-1].X:F2},{_pathPoints[_pathPoints.Count-1].Y:F2},{_pathPoints[_pathPoints.Count-1].Z:F2})"); var totalDist = CalculateTotalPathDistance(); LogManager.Info($"路径总长度: {totalDist:F2}"); LogManager.Info($"车辆已移动到路径起点,动画将从起点开始"); LogManager.Info($"=== 调试信息结束 ==="); } catch (Exception ex) { LogManager.Error($"设置动画失败: {ex.Message}"); throw; } } /// /// 将车辆移动到路径起点 /// private void MoveVehicleToPathStart() { try { if (_pathPoints.Count == 0) return; var doc = NavisApplication.ActiveDocument; var modelItems = new ModelItemCollection { _animatedObject }; // 计算从部件原始中心到路径起点的偏移 var startOffset = new Vector3D( _pathPoints[0].X - _originalCenter.X, _pathPoints[0].Y - _originalCenter.Y, _pathPoints[0].Z - _originalCenter.Z ); // 创建变换并应用 var startTransform = Transform3D.CreateTranslation(startOffset); doc.Models.OverridePermanentTransform(modelItems, startTransform, false); // 更新当前位置为路径起点 _currentPosition = _pathPoints[0]; LogManager.Info($"部件已移动到路径起点,偏移: ({startOffset.X:F2},{startOffset.Y:F2},{startOffset.Z:F2})"); } catch (Exception ex) { LogManager.Error($"移动部件到路径起点失败: {ex.Message}"); } } /// /// 开始播放动画 /// public void StartAnimation() { try { if (_animatedObject == null || _pathPoints.Count < 2) { throw new InvalidOperationException("请先调用SetupAnimation设置动画参数"); } // 停止之前的动画 StopAnimation(); // 创建 TimeLiner 任务(如果可用) if (_timeLinerManager != null && _timeLinerManager.IsTimeLinerAvailable) { var taskName = $"{_animatedObject.DisplayName}_运输_{DateTime.Now:HHmmss}"; 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 不可用,使用基础动画功能"); } // 设置动态碰撞检测(简化版本) SetupDynamicClashDetection(); // 初始化动画状态 _animationStartTime = DateTime.Now; // 创建并启动定时器(每50ms更新一次,实现流畅动画) _animationTimer = new Timer(); _animationTimer.Interval = 50; // 20 FPS _animationTimer.Tick += AnimationTimer_Tick; _animationTimer.Start(); SetState(AnimationState.Playing); LogManager.Info("动画开始播放"); } catch (Exception ex) { LogManager.Error($"启动动画失败: {ex.Message}"); throw; } } /// /// 停止动画 /// public void StopAnimation() { try { if (_animationTimer != null) { _animationTimer.Stop(); _animationTimer.Dispose(); _animationTimer = null; SetState(AnimationState.Stopped); LogManager.Info("动画已停止"); } // 更新 TimeLiner 任务状态 if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) { _timeLinerManager.UpdateTaskProgress(_currentTaskId, 0.0, AnimationState.Stopped); } } catch (Exception ex) { LogManager.Error($"停止动画失败: {ex.Message}"); } } /// /// 重置动画对象到原始位置 /// public void ResetAnimation() { try { StopAnimation(); // 停止当前动画 // 恢复对象的原始变换 if (_animatedObject != null) { var modelItems = new ModelItemCollection { _animatedObject }; NavisApplication.ActiveDocument.Models.OverridePermanentTransform(modelItems, _originalTransform, false); LogManager.Info($"部件 {_animatedObject.DisplayName} 已重置到原始位置"); } ProgressChanged?.Invoke(this, 0); // 重置进度条 SetState(AnimationState.Ready); // 重置后回到就绪状态 } catch (Exception ex) { LogManager.Error($"重置动画失败: {ex.Message}"); throw; } } /// /// 动画定时器事件处理 /// private void AnimationTimer_Tick(object sender, EventArgs e) { try { double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds; double progress = Math.Min(elapsedSeconds / _animationDuration, 1.0); // 更新UI进度条 ProgressChanged?.Invoke(this, (int)(progress * 100)); // 同步进度到 TimeLiner(如果可用) if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) { _timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState); } Point3D newPosition = InterpolatePosition(progress); UpdateObjectPosition(newPosition); // 检查碰撞 CheckAndHighlightCollisions(); if (progress >= 1.0) { StopAnimation(); SetState(AnimationState.Finished); // 标记为完成 AnimationCompleted?.Invoke(this, EventArgs.Empty); // 触发旧版完成事件 LogManager.Info("动画播放完成"); } } catch (Exception ex) { LogManager.Error($"动画帧更新失败: {ex.Message}"); StopAnimation(); // 发生错误时停止动画 } } /// /// 根据进度插值计算当前位置 /// 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) { // 在这个线段内 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 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 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) { try { var doc = NavisApplication.ActiveDocument; var modelItems = new ModelItemCollection { _animatedObject }; // 正确的增量变换:计算从当前位置到新位置的偏移 var incrementalOffset = new Vector3D( newPosition.X - _currentPosition.X, newPosition.Y - _currentPosition.Y, newPosition.Z - _currentPosition.Z ); // 创建增量变换 var incrementalTransform = Transform3D.CreateTranslation(incrementalOffset); // 应用增量变换(不重置之前的变换) doc.Models.OverridePermanentTransform(modelItems, incrementalTransform, false); LogManager.Debug($"部件位置更新:从({_currentPosition.X:F2},{_currentPosition.Y:F2},{_currentPosition.Z:F2})到({newPosition.X:F2},{newPosition.Y:F2},{newPosition.Z:F2}),增量偏移({incrementalOffset.X:F2},{incrementalOffset.Y:F2},{incrementalOffset.Z:F2})"); // 更新当前位置 _currentPosition = newPosition; } catch (Exception ex) { LogManager.Error($"更新部件位置失败: {ex.Message}"); } } /// /// 从车辆变换矩阵中获取真实的缩放系数 /// 注意:此方法已废弃但保留,用于车辆模型的缩放处理。 /// 现在使用场馆部件进行动画,通常不需要复杂的缩放计算。 /// private double GetVehicleScaleFactor() { try { var transform = _animatedObject.Transform; var linear = transform.Linear; // 获取变换矩阵的第一行的长度作为缩放系数 // 变换矩阵: (a, b, c) // (d, e, f) // (g, h, i) // 缩放系数 ≈ sqrt(a² + b² + c²) var scaleX = Math.Sqrt( linear.Get(0, 0) * linear.Get(0, 0) + linear.Get(0, 1) * linear.Get(0, 1) + linear.Get(0, 2) * linear.Get(0, 2) ); LogManager.Debug($"车辆缩放系数计算: {scaleX:F2} (变换矩阵第一行模长)"); // 如果缩放系数接近1550(39.37²),说明是米到英寸的平方缩放 if (Math.Abs(scaleX - 1550.0) < 10.0) { var linearScale = Math.Sqrt(scaleX); LogManager.Info($"检测到车辆使用平方缩放系数: {scaleX:F2},已修正为线性缩放系数: {linearScale:F2}"); return linearScale; } // 如果缩放系数接近39.37,说明是简单的米到英寸缩放 else if (Math.Abs(scaleX - 39.37) < 1.0) { LogManager.Info($"检测到车辆使用线性缩放系数: {scaleX:F2}"); return scaleX; } // 如果接近1,说明没有缩放 else if (Math.Abs(scaleX - 1.0) < 0.1) { LogManager.Info($"车辆无缩放: {scaleX:F2}"); return 1.0; } else { LogManager.Warning($"未知的车辆缩放系数: {scaleX:F2},使用原值"); return scaleX; } } catch (Exception ex) { LogManager.Error($"获取车辆缩放系数失败: {ex.Message},使用默认值1.0"); return 1.0; } } /// /// 获取文档单位信息(用于日志记录) /// 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)); } /// /// 设置动态碰撞检测(简化版本,因为Clash Detective API在2017版本中有限制) /// private void SetupDynamicClashDetection() { try { LogManager.Info("动态碰撞检测设置完成(简化版本)"); } catch (Exception ex) { LogManager.Error($"设置动态碰撞检测失败: {ex.Message}"); } } /// /// 检查并高亮碰撞(简化版本) /// private void CheckAndHighlightCollisions() { try { // 简化的碰撞检测:检查动画对象是否与其他对象的包围盒相交 var doc = NavisApplication.ActiveDocument; var animatedBoundingBox = _animatedObject.BoundingBox(); // 获取所有其他有几何体的对象 var allItems = doc.Models.RootItemDescendantsAndSelf .Where(item => item.HasGeometry && !item.Equals(_animatedObject)) .ToList(); var collidingItems = new ModelItemCollection(); foreach (var item in allItems) { var itemBoundingBox = item.BoundingBox(); if (BoundingBoxesIntersect(animatedBoundingBox, itemBoundingBox)) { collidingItems.Add(item); } } // 清除之前的高亮 doc.Models.ResetAllTemporaryMaterials(); // 高亮碰撞对象(红色) if (collidingItems.Count > 0) { doc.Models.OverrideTemporaryColor(collidingItems, Color.Red); LogManager.Debug($"检测到 {collidingItems.Count} 处碰撞"); } } catch (Exception ex) { LogManager.Debug($"碰撞检测更新失败: {ex.Message}"); } } /// /// 检查两个包围盒是否相交 /// private bool BoundingBoxesIntersect(BoundingBox3D box1, BoundingBox3D box2) { return !(box1.Max.X < box2.Min.X || box2.Max.X < box1.Min.X || box1.Max.Y < box2.Min.Y || box2.Max.Y < box1.Min.Y || box1.Max.Z < box2.Min.Z || box2.Max.Z < box1.Min.Z); } /// /// 设置动画持续时间 /// public void SetAnimationDuration(double durationSeconds) { _animationDuration = Math.Max(1.0, durationSeconds); } /// /// 获取动画状态 /// public bool IsAnimating => _animationTimer != null && _animationTimer.Enabled; /// /// 获取 TimeLiner 是否可用 /// public bool IsTimeLinerAvailable => _timeLinerManager?.IsTimeLinerAvailable ?? false; /// /// 获取当前动画状态 /// public AnimationState CurrentState => _currentState; /// /// 获取当前 TimeLiner 任务ID /// public string CurrentTaskId => _currentTaskId; /// /// 获取 TimeLiner 集成管理器 /// public TimeLinerIntegrationManager TimeLinerManager => _timeLinerManager; /// /// 资源清理 /// public void Dispose() { StopAnimation(); ResetAnimation(); // 清理 TimeLiner 资源 if (_timeLinerManager != null) { try { _timeLinerManager.Dispose(); _timeLinerManager = null; LogManager.Info("TimeLiner 集成资源已清理"); } catch (Exception ex) { LogManager.Warning($"清理 TimeLiner 资源时出现警告: {ex.Message}"); } } } /// /// 快速测试TimeLiner API的可用性 /// public bool TestTimeLinerAPI() { try { var doc = NavisApplication.ActiveDocument; // 尝试访问TimeLiner var timeliner = doc.Timeliner; if (timeliner != null) { LogManager.Info("TimeLiner API基本可用"); return true; } else { LogManager.Info("TimeLiner API不可用"); return false; } } catch (Exception ex) { LogManager.Info($"TimeLiner API测试失败: {ex.Message}"); return false; } } /// /// 简化的动画设置方法,支持直接传入部件和路径 /// /// 部件模型 /// 路径点列表 /// 动画持续时间(秒) public bool SetupSimpleAnimation(ModelItem component, List pathPoints, double durationSeconds = 10.0) { try { if (component == null) { LogManager.Error("部件模型不能为空"); return false; } if (pathPoints == null || pathPoints.Count < 2) { LogManager.Error("路径点数量必须至少为2个"); return false; } // 使用现有的SetupAnimation方法 SetupAnimation(component, pathPoints, durationSeconds); LogManager.Info($"简化动画设置成功:部件={component.DisplayName}, 路径点数={pathPoints.Count}, 时长={durationSeconds}秒"); return true; } catch (Exception ex) { LogManager.Error($"简化动画设置失败: {ex.Message}"); return false; } } /// /// 获取当前选中的部件模型(简化版本) /// /// 选中的部件模型,如果没有选中或选中多个则返回null public static ModelItem GetSelectedComponent() { try { var doc = NavisApplication.ActiveDocument; var selectedItems = doc.CurrentSelection.SelectedItems; if (selectedItems.Count == 1) { var item = selectedItems.First; if (item.HasGeometry) { LogManager.Info($"已获取选中部件: {item.DisplayName}"); return item; } else { LogManager.Warning("选中的项目没有几何体,可能不适合用于动画"); return null; } } else if (selectedItems.Count == 0) { LogManager.Info("没有选中任何项目"); return null; } else { LogManager.Info($"选中了{selectedItems.Count}个项目,请只选择一个部件"); return null; } } catch (Exception ex) { LogManager.Error($"获取选中部件失败: {ex.Message}"); return null; } } /// /// 设置并触发状态变更事件 /// /// 新的动画状态 private void SetState(AnimationState newState) { if (_currentState != newState) { _currentState = newState; StateChanged?.Invoke(this, _currentState); } } /// /// 创建动画 /// /// 要动画化的模型对象 /// 路径点列表 /// 动画持续时间(秒) public void CreateAnimation(ModelItem animatedObject, List pathPoints, double durationSeconds = 10.0) { SetupAnimation(animatedObject, pathPoints, durationSeconds); SetState(AnimationState.Ready); } } }