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);
}
}
}