NavisworksTransport/src/Core/Animation/PathAnimationManager.cs

1379 lines
54 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using Autodesk.Navisworks.Api;
using NavisApplication = Autodesk.Navisworks.Api.Application;
namespace NavisworksTransport.Core.Animation
{
/// <summary>
/// 定义动画播放的状态
/// </summary>
public enum AnimationState
{
Idle, // 空闲,未生成动画
Ready, // 已就绪,动画已生成但未播放
Playing, // 播放中
Paused, // 暂停
Stopped, // 已停止
Finished // 已完成
}
/// <summary>
/// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果
/// 注意由于Navisworks API限制无法直接使用Animator API因此使用OverridePermanentTransform实现动画
/// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务
/// </summary>
public class PathAnimationManager
{
private static PathAnimationManager _instance;
private ModelItem _animatedObject;
private List<Point3D> _pathPoints;
// === Idle事件动画系统 ===
private double _frameInterval; // 帧间隔(毫秒)
private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间
private int _collisionCheckCounter = 0; // 碰撞检测计数器(用于降频)
// === 动画参数 ===
private double _animationDuration = 10.0; // 动画总时长(秒)
private DateTime _animationStartTime;
private int _animationFrameRate = 30; // 动画帧率默认30FPS
private int _animationFrameCount = 0; // 动画帧计数
private double _collisionDetectionAccuracy = 0.1; // 检测精度默认0.1米)
private double _movementSpeed = 1.0; // 运动速度默认1米/秒)
private double _detectionGap = 0.05; // 检测间隙默认0.05米)
// === 性能监控 ===
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之间
// TimeLiner 集成
private TimeLinerIntegrationManager _timeLinerManager;
private string _currentTaskId;
// --- 新增事件 ---
/// <summary>
/// 当动画状态发生改变时触发
/// </summary>
public event EventHandler<AnimationState> StateChanged;
/// <summary>
/// 当动画进度更新时触发 (0.0-100.0)
/// </summary>
public event EventHandler<double> ProgressChanged;
// 动画完成事件 (旧版,保留兼容性)
public event EventHandler AnimationCompleted;
/// <summary>
/// 当检测到碰撞时触发
/// </summary>
public event EventHandler<CollisionDetectedEventArgs> CollisionDetected;
public PathAnimationManager()
{
_pathPoints = new List<Point3D>();
// 初始化动画模式
_frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔
_lastFrameTime = DateTime.MinValue;
_fpsCounterStart = DateTime.Now;
// 初始化 TimeLiner 集成
try
{
_timeLinerManager = new TimeLinerIntegrationManager();
LogManager.Info($"PathAnimationManager 初始化完成 - Idle事件模式, 目标FPS: {_animationFrameRate}");
}
catch (Exception ex)
{
LogManager.Warning($"TimeLiner 集成初始化失败,将使用基础动画功能: {ex.Message}");
_timeLinerManager = null;
}
// 订阅文档状态事件
DocumentStateManager.Instance.DocumentInvalidated += OnDocumentInvalidated;
}
/// <summary>
/// 获取单例实例
/// </summary>
public static PathAnimationManager GetInstance()
{
if (_instance == null)
{
_instance = new PathAnimationManager();
}
return _instance;
}
/// <summary>
/// 设置动画参数
/// </summary>
/// <param name="animatedObject">要动画化的模型对象</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
public void SetupAnimation(ModelItem animatedObject, List<Point3D> 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<Point3D>(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;
}
}
/// <summary>
/// 将车辆移动到路径起点
/// </summary>
private void MoveVehicleToPathStart()
{
try
{
if (_pathPoints.Count == 0) return;
var doc = NavisApplication.ActiveDocument;
var modelItems = new ModelItemCollection { _animatedObject };
// 获取物体的包围盒
var boundingBox = _animatedObject.BoundingBox();
// 计算物体底面中心点(而不是整体中心点)
var objectBottomCenter = new Point3D(
_originalCenter.X,
_originalCenter.Y,
boundingBox.Min.Z // 使用包围盒的最小Z值作为底面
);
// 计算从物体底面中心到路径起点的偏移
var startOffset = new Vector3D(
_pathPoints[0].X - objectBottomCenter.X,
_pathPoints[0].Y - objectBottomCenter.Y,
_pathPoints[0].Z - objectBottomCenter.Z
);
// 创建变换并应用
var startTransform = Transform3D.CreateTranslation(startOffset);
doc.Models.OverridePermanentTransform(modelItems, startTransform, false);
// 更新当前位置为路径起点
_currentPosition = _pathPoints[0];
LogManager.Info($"物体已移动到路径起点,底面对齐地面");
LogManager.Info($"物体包围盒: Min=({boundingBox.Min.X:F2},{boundingBox.Min.Y:F2},{boundingBox.Min.Z:F2}), Max=({boundingBox.Max.X:F2},{boundingBox.Max.Y:F2},{boundingBox.Max.Z:F2})");
LogManager.Info($"物体原始中心: ({_originalCenter.X:F2},{_originalCenter.Y:F2},{_originalCenter.Z:F2})");
LogManager.Info($"物体底面中心: ({objectBottomCenter.X:F2},{objectBottomCenter.Y:F2},{objectBottomCenter.Z:F2})");
LogManager.Info($"路径起点坐标: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})");
LogManager.Info($"应用的偏移量: ({startOffset.X:F2},{startOffset.Y:F2},{startOffset.Z:F2})");
}
catch (Exception ex)
{
LogManager.Error($"移动部件到路径起点失败: {ex.Message}");
}
}
/// <summary>
/// 开始播放动画
/// </summary>
public void StartAnimation()
{
try
{
if (_animatedObject == null || _pathPoints.Count < 2)
{
throw new InvalidOperationException("请先调用SetupAnimation设置动画参数");
}
// 停止之前的动画
StopAnimation();
// 创建 TimeLiner 任务(如果可用)
if (_timeLinerManager != null && _timeLinerManager.IsTimeLinerAvailable)
{
string objectName = "UnknownObject";
try
{
objectName = _animatedObject?.DisplayName ?? "UnknownObject";
}
catch (Exception)
{
objectName = "DisposedObject";
}
var taskName = $"{objectName}_运输_{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;
_animationFrameCount = 0; // 重置帧计数
_pausedProgress = 0.0; // 重置暂停进度
// 重置Idle模式状态
_lastFrameTime = DateTime.MinValue;
_collisionCheckCounter = 0;
_fpsFrameCount = 0;
_fpsCounterStart = DateTime.Now;
_frameInterval = 1000.0 / _animationFrameRate;
// 订阅Idle事件
NavisApplication.Idle += OnApplicationIdle;
SetState(AnimationState.Playing);
LogManager.Info("动画开始播放 - Idle事件模式");
}
catch (Exception ex)
{
LogManager.Error($"启动动画失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 停止动画
/// </summary>
public void StopAnimation()
{
try
{
// 检查动画状态,避免重复停止
if (_currentState == AnimationState.Stopped || _currentState == AnimationState.Idle)
{
LogManager.Debug($"动画已处于{_currentState}状态,跳过重复停止操作");
return;
}
// 注销Idle事件
NavisApplication.Idle -= OnApplicationIdle;
LogManager.Debug("[Idle模式] Idle事件已注销");
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
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}");
}
}
/// <summary>
/// 完成动画(当动画自然播放结束时调用)
/// </summary>
private void FinishAnimation()
{
try
{
// 注销Idle事件
NavisApplication.Idle -= OnApplicationIdle;
// 重置暂停进度
_pausedProgress = 0.0;
// 直接设置为完成状态,避免中间状态切换
SetState(AnimationState.Finished);
LogManager.Info($"动画播放完成,总帧数: {_animationFrameCount}, 平均FPS: {_actualFPS:F1}");
// 触发旧版完成事件(保持兼容性)
AnimationCompleted?.Invoke(this, EventArgs.Empty);
// 更新 TimeLiner 任务状态
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, 1.0, AnimationState.Finished);
}
// 动画结束后统一创建所有碰撞测试(基于官方示例的批量处理)
LogManager.Info($"动画播放完成,开始创建最终的碰撞测试汇总(检测间隙: {_detectionGap}米)...");
ClashDetectiveIntegration.Instance.CreateAllAnimationCollisionTests(_detectionGap);
}
catch (Exception ex)
{
LogManager.Error($"完成动画失败: {ex.Message}");
}
}
/// <summary>
/// 暂停动画
/// </summary>
public void PauseAnimation()
{
try
{
if (_currentState == AnimationState.Playing)
{
// 计算并保存当前进度
double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds;
_pausedProgress = Math.Min(elapsedSeconds / _animationDuration, 1.0);
// 暂时注销Idle事件
NavisApplication.Idle -= OnApplicationIdle;
LogManager.Debug("[Idle模式] 暂停时注销Idle事件");
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}");
}
}
/// <summary>
/// 恢复动画
/// </summary>
public void ResumeAnimation()
{
try
{
if (_currentState == AnimationState.Paused)
{
// 使用保存的暂停进度重新设置动画开始时间
// 设置开始时间,使得当前时间对应保存的暂停进度
_animationStartTime = DateTime.Now.AddSeconds(-(_pausedProgress * _animationDuration));
// 重新订阅Idle事件
_lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行
NavisApplication.Idle += OnApplicationIdle;
LogManager.Debug("[Idle模式] 恢复时重新订阅Idle事件");
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}");
}
}
/// <summary>
/// 重置动画对象到原始位置
/// </summary>
public void ResetAnimation()
{
try
{
// 只在需要时停止动画,避免重复调用
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
StopAnimation(); // 停止当前动画
}
// 恢复对象的原始变换安全检查Navisworks对象可用性
if (_animatedObject != null)
{
try
{
// 检查Navisworks应用程序和文档是否仍然可用
var activeDoc = NavisApplication.ActiveDocument;
if (activeDoc != null && activeDoc.Models != null)
{
// 先安全获取对象名称,避免访问已释放对象的属性
string objectName = GetSafeObjectName(_animatedObject);
var modelItems = new ModelItemCollection { _animatedObject };
activeDoc.Models.OverridePermanentTransform(modelItems, _originalTransform, false);
LogManager.Info($"部件 {objectName} 已重置到原始位置");
}
else
{
LogManager.Info("Navisworks文档已不可用跳过对象重置操作");
}
}
catch (Exception resetEx)
{
LogManager.Warning($"重置对象位置时出现警告可能因为Navisworks对象已释放: {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}");
// 在资源清理阶段不再抛出异常,避免影响程序正常关闭
}
}
/// <summary>
/// 安全获取ModelItem的DisplayName避免访问已释放对象的警告
/// </summary>
/// <param name="item">ModelItem对象</param>
/// <returns>安全的显示名称</returns>
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})";
}
}
/// <summary>
/// 根据进度插值计算当前位置
/// </summary>
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];
}
/// <summary>
/// 在两点间插值
/// </summary>
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
);
}
/// <summary>
/// 计算路径总长度
/// </summary>
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;
}
/// <summary>
/// 获取对象当前位置
/// </summary>
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);
}
}
/// <summary>
/// 计算两点间距离
/// </summary>
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);
}
/// <summary>
/// 更新对象位置
/// </summary>
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}");
}
}
/// <summary>
/// 从车辆变换矩阵中获取真实的缩放系数
/// 注意:此方法已废弃但保留,用于车辆模型的缩放处理。
/// 现在使用场馆部件进行动画,通常不需要复杂的缩放计算。
/// </summary>
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} (变换矩阵第一行模长)");
// 如果缩放系数接近155039.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;
}
}
/// <summary>
/// 获取文档单位信息(用于日志记录)
/// </summary>
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";
}
}
/// <summary>
/// 获取模型当前变换
/// </summary>
private Transform3D GetCurrentTransform(ModelItem item)
{
// 获取包围盒中心作为参考点
var boundingBox = item.BoundingBox();
var center = boundingBox.Center;
// 创建基于中心点的单位变换
return Transform3D.CreateTranslation(new Vector3D(center.X, center.Y, center.Z));
}
/// <summary>
/// 使用 Clash Detective 集成进行碰撞检测并使用缓存的碰撞结果
/// </summary>
private void CheckAndHighlightCollisionsWithClashDetective()
{
try
{
if (_animatedObject == null)
return;
// 使用 Clash Detective 集成进行碰撞检测,传递检测间隙参数
var collisionResults = ClashDetectiveIntegration.Instance.DetectCollisions(_animatedObject, null, _detectionGap);
// 高亮显示碰撞对象
ClashDetectiveIntegration.Instance.HighlightCollisions(collisionResults);
// 缓存碰撞结果,动画结束后统一处理
if (collisionResults.Count > 0)
{
LogManager.Info($"=== [动画运行中] 检测到 {collisionResults.Count} 个碰撞,开始记录详细位置 ===");
// 缓存所有碰撞结果,包含位置信息
var animatedObjectPosition = GetObjectPosition(_animatedObject);
LogManager.Info($"[动画位置] 动画对象 {_animatedObject.DisplayName}: ({animatedObjectPosition.X:F2},{animatedObjectPosition.Y:F2},{animatedObjectPosition.Z:F2})");
int collisionIndex = 0;
foreach (var collision in collisionResults)
{
collisionIndex++;
var collisionObjectPosition = GetObjectPosition(collision.Item2);
LogManager.Info($"[碰撞位置{collisionIndex}] 动画对象 vs {collision.Item2.DisplayName}:");
LogManager.Info($" 动画物体位置: ({animatedObjectPosition.X:F2},{animatedObjectPosition.Y:F2},{animatedObjectPosition.Z:F2})");
LogManager.Info($" 碰撞物体位置: ({collisionObjectPosition.X:F2},{collisionObjectPosition.Y:F2},{collisionObjectPosition.Z:F2})");
LogManager.Info($" 两物体距离: {CalculateDistance(animatedObjectPosition, collisionObjectPosition):F2}");
LogManager.Info($" 碰撞状态: 已检测到碰撞");
ClashDetectiveIntegration.Instance.CacheCollisionDuringAnimation(_animatedObject, animatedObjectPosition, collision.Item2, collisionObjectPosition);
}
LogManager.Info($"=== [动画运行中] 位置记录完成 ===");
}
LogManager.Debug($"碰撞检测完成: {collisionResults.Count} 个碰撞 (已缓存)");
}
catch (Exception ex)
{
LogManager.Error($"碰撞检测失败: {ex.Message}");
}
}
/// <summary>
/// 设置动态碰撞检测(实时模式,集成 Clash Detective
/// </summary>
private void SetupDynamicClashDetection()
{
try
{
// 初始化 Clash Detective 集成
ClashDetectiveIntegration.Instance.Initialize();
// 订阅碰撞检测事件
ClashDetectiveIntegration.Instance.CollisionDetected += OnClashDetectiveCollisionDetected;
LogManager.Info("动态碰撞检测设置完成(实时模式)");
}
catch (Exception ex)
{
LogManager.Error($"设置动态碰撞检测失败: {ex.Message}");
}
}
/// <summary>
/// 处理 Clash Detective 碰撞检测事件
/// </summary>
private void OnClashDetectiveCollisionDetected(object sender, CollisionDetectedEventArgs e)
{
try
{
// 将碰撞结果转发到动画管理器的事件
OnCollisionDetected(e);
LogManager.Debug($"Clash Detective 检测到 {e.CollisionCount} 个碰撞");
}
catch (Exception ex)
{
LogManager.Error($"处理 Clash Detective 碰撞事件失败: {ex.Message}");
}
}
/// <summary>
/// 触发碰撞检测事件
/// </summary>
protected virtual void OnCollisionDetected(CollisionDetectedEventArgs e)
{
CollisionDetected?.Invoke(this, e);
}
// 已删除CheckAndHighlightCollisions - 使用Clash Detective集成代替
/// <summary>
/// 检查两个包围盒是否相交
/// </summary>
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);
}
/// <summary>
/// 设置动画持续时间
/// </summary>
public void SetAnimationDuration(double durationSeconds)
{
_animationDuration = Math.Max(1.0, durationSeconds);
}
/// <summary>
/// 获取动画状态
/// </summary>
public bool IsAnimating => _currentState == AnimationState.Playing;
/// <summary>
/// 获取 TimeLiner 是否可用
/// </summary>
public bool IsTimeLinerAvailable => _timeLinerManager?.IsTimeLinerAvailable ?? false;
/// <summary>
/// 获取当前动画状态
/// </summary>
public AnimationState CurrentState => _currentState;
/// <summary>
/// 获取当前 TimeLiner 任务ID
/// </summary>
public string CurrentTaskId => _currentTaskId;
/// <summary>
/// 获取 TimeLiner 集成管理器
/// </summary>
public TimeLinerIntegrationManager TimeLinerManager => _timeLinerManager;
/// <summary>
/// 清理对象引用
/// </summary>
public void ClearReferences()
{
try
{
LogManager.Info("[PathAnimationManager] 清理对象引用");
// 停止动画
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
StopAnimation();
}
// 清空对象引用
_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}");
}
}
/// <summary>
/// 验证动画对象是否有效
/// </summary>
private bool IsAnimatedObjectValid()
{
if (_animatedObject == null)
return false;
// 使用DocumentStateManager验证
return DocumentStateManager.Instance.IsModelItemValid(_animatedObject);
}
/// <summary>
/// 文档失效事件处理
/// </summary>
private void OnDocumentInvalidated(object sender, EventArgs e)
{
try
{
LogManager.Info("[PathAnimationManager] 文档失效,停止动画并清理");
ClearReferences();
}
catch (Exception ex)
{
LogManager.Error($"[PathAnimationManager] 处理文档失效事件失败: {ex.Message}");
}
}
/// <summary>
/// 资源清理
/// </summary>
public void Dispose()
{
try
{
LogManager.Info("开始清理PathAnimationManager资源");
// 1. 优先停止动画(最小化的操作,不触发复杂的重置逻辑)
// 只有在动画运行时才停止,避免重复调用
try
{
if (_currentState == AnimationState.Playing || _currentState == AnimationState.Paused)
{
StopAnimation();
}
else
{
LogManager.Debug($"动画已处于{_currentState}状态,跳过停止操作");
}
}
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. 取消订阅文档事件
DocumentStateManager.Instance.DocumentInvalidated -= OnDocumentInvalidated;
LogManager.Info("PathAnimationManager资源清理完成");
}
catch (Exception ex)
{
LogManager.Error($"PathAnimationManager资源清理过程中发生异常: {ex.Message}");
// 不抛出异常,避免影响应用程序正常关闭
}
}
/// <summary>
/// 快速测试TimeLiner API的可用性
/// </summary>
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;
}
}
/// <summary>
/// 简化的动画设置方法,支持直接传入部件和路径
/// </summary>
/// <param name="component">部件模型</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
public bool SetupSimpleAnimation(ModelItem component, List<Point3D> 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;
}
}
/// <summary>
/// 获取当前选中的部件模型(简化版本)
/// </summary>
/// <returns>选中的部件模型如果没有选中或选中多个则返回null</returns>
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;
}
}
/// <summary>
/// 设置动画帧率
/// </summary>
public void SetAnimationFrameRate(int frameRate)
{
_animationFrameRate = Math.Max(10, Math.Min(60, frameRate)); // 限制在10-60FPS之间
LogManager.Info($"动画帧率设置为: {_animationFrameRate}FPS");
}
/// <summary>
/// 设置碰撞检测精度
/// </summary>
public void SetCollisionDetectionAccuracy(double accuracy)
{
_collisionDetectionAccuracy = Math.Max(0.01, accuracy); // 最小0.01米
LogManager.Info($"碰撞检测精度设置为: {_collisionDetectionAccuracy}m");
}
/// <summary>
/// 设置运动速度
/// </summary>
public void SetMovementSpeed(double speed)
{
_movementSpeed = Math.Max(0.1, speed); // 最小0.1米/秒
LogManager.Info($"运动速度设置为: {_movementSpeed}m/s");
}
/// <summary>
/// 设置检测间隙
/// </summary>
public void SetDetectionGap(double gap)
{
var roundedGap = Math.Round(Math.Max(0.0, gap), 2);
// 只在值真正改变时才更新和记录日志
if (Math.Abs(_detectionGap - roundedGap) > 0.001)
{
_detectionGap = roundedGap;
LogManager.Info($"间隙={_detectionGap:F2}m");
}
}
/// <summary>
/// 获取当前动画帧率
/// </summary>
public int AnimationFrameRate => _animationFrameRate;
/// <summary>
/// 获取当前碰撞检测精度
/// </summary>
public double CollisionDetectionAccuracy => _collisionDetectionAccuracy;
/// <summary>
/// 获取当前运动速度
/// </summary>
public double MovementSpeed => _movementSpeed;
/// <summary>
/// 获取当前检测间隙
/// </summary>
public double DetectionGap => _detectionGap;
/// <summary>
/// 设置并触发状态变更事件
/// </summary>
/// <param name="newState">新的动画状态</param>
private void SetState(AnimationState newState)
{
if (_currentState != newState)
{
_currentState = newState;
StateChanged?.Invoke(this, _currentState);
}
}
/// <summary>
/// 创建动画
/// </summary>
/// <param name="animatedObject">要动画化的模型对象</param>
/// <param name="pathPoints">路径点列表</param>
/// <param name="durationSeconds">动画持续时间(秒)</param>
public void CreateAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0)
{
SetupAnimation(animatedObject, pathPoints, durationSeconds);
SetState(AnimationState.Ready);
}
#region
/// <summary>
/// Idle事件处理器 - 新的动画核心
/// </summary>
private void OnApplicationIdle(object sender, EventArgs e)
{
try
{
// 检查动画状态
if (_currentState != AnimationState.Playing)
{
return; // 非播放状态,跳过更新
}
// 验证动画对象是否仍然有效
if (!IsAnimatedObjectValid())
{
LogManager.Warning("[PathAnimationManager] 动画对象已失效,停止动画");
StopAnimation();
return;
}
// 帧率控制
var now = DateTime.Now;
var elapsed = (now - _lastFrameTime).TotalMilliseconds;
if (elapsed < _frameInterval)
{
return; // 还没到下一帧时间跳过本次Idle
}
// 计算动画进度
var totalElapsed = (now - _animationStartTime).TotalSeconds;
var progress = Math.Min(totalElapsed / _animationDuration, 1.0);
// 更新动画位置(使用原有逻辑)
Point3D newPosition = InterpolatePosition(progress);
UpdateObjectPosition(newPosition);
// 触发进度事件
var progressPercent = progress * 100;
ProgressChanged?.Invoke(this, progressPercent);
// 同步进度到 TimeLiner如果可用
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
}
// 碰撞检测降频执行每3帧检测一次
_collisionCheckCounter++;
if (_collisionCheckCounter >= 3)
{
_collisionCheckCounter = 0;
if (_currentState == AnimationState.Playing)
{
CheckAndHighlightCollisionsWithClashDetective();
}
}
// 记录帧时间
_lastFrameTime = now;
_animationFrameCount++;
// 性能监控
UpdateFPSCounter();
// 检查动画完成
if (progress >= 1.0)
{
// 先注销事件,避免重复调用
NavisApplication.Idle -= OnApplicationIdle;
FinishAnimation();
}
}
catch (Exception ex)
{
LogManager.Error($"[Idle模式] 动画更新异常: {ex.Message}");
// 发生异常时停止动画,避免持续错误
StopAnimation();
}
}
/// <summary>
/// 更新FPS计数器
/// </summary>
private void UpdateFPSCounter()
{
_fpsFrameCount++;
var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds;
// 每秒更新一次FPS统计
if (elapsed >= 1.0)
{
_actualFPS = _fpsFrameCount / elapsed;
LogManager.Debug($"[性能监控] 实际FPS: {_actualFPS:F1} (目标: {_animationFrameRate})");
// 重置计数器
_fpsFrameCount = 0;
_fpsCounterStart = DateTime.Now;
}
}
#endregion
}
}