NavisworksTransport/doc/migration/Animation_Migration_Guide.md

14 KiB
Raw Blame History

动画系统迁移实施指南

🎯 迁移目标

将现有的手动Transform变换动画系统升级为基于Navisworks 2026原生动画组件的专业动画系统。

📋 迁移前准备

1. 现状分析

// 当前动画实现分析
public class CurrentAnimationAnalysis
{
    // 问题1手动变换性能差
    public void PlayLegacyAnimation()
    {
        foreach (var frame in frames)
        {
            ComApi.State.OverrideTransform(modelItem, frame.Transform);
            Thread.Sleep(frameDelay); // 阻塞UI
        }
    }
    
    // 问题2复杂的时间管理
    private void ManualTimeControl()
    {
        // 大量手动时间计算代码
        // 容易出错,难以维护
    }
    
    // 问题3缺乏标准动画控制
    // 无法暂停、倒退、调速等
}

2. 性能基准测试

  • 记录当前动画帧率
  • 测量CPU使用率
  • 分析内存使用模式
  • 记录用户体验问题

🚀 迁移实施步骤

阶段1基础动画框架搭建第1周

1.1 创建动画管理器

public class LogisticsAnimationManager2026
{
    private readonly Document _document;
    private readonly Dictionary<string, AnimationSet> _animationSets;
    private readonly Dictionary<string, AnimationController> _controllers;
    
    public LogisticsAnimationManager2026(Document document)
    {
        _document = document;
        _animationSets = new Dictionary<string, AnimationSet>();
        _controllers = new Dictionary<string, AnimationController>();
    }
    
    public AnimationSet CreatePathAnimation(
        string name,
        ModelItem movingObject,
        List<PathPoint> pathPoints,
        TimeSpan duration,
        AnimationOptions options = null)
    {
        var animationSet = new AnimationSet(_document, name);
        
        // 创建变换轨道
        var transformTrack = animationSet.CreateTransformTrack(movingObject, "Transform");
        
        // 添加关键帧
        AddKeyframes(transformTrack, pathPoints, duration, options);
        
        // 创建控制器
        var controller = new AnimationController(animationSet);
        
        _animationSets[name] = animationSet;
        _controllers[name] = controller;
        
        return animationSet;
    }
    
    private void AddKeyframes(
        TransformTrack track, 
        List<PathPoint> pathPoints, 
        TimeSpan duration,
        AnimationOptions options)
    {
        for (int i = 0; i < pathPoints.Count; i++)
        {
            var progress = (double)i / (pathPoints.Count - 1);
            var keyTime = TimeSpan.FromMilliseconds(duration.TotalMilliseconds * progress);
            
            var keyframe = track.CreateKeyframe(keyTime);
            keyframe.Transform = CreateTransformFromPoint(pathPoints[i]);
            
            // 设置插值类型
            keyframe.InterpolationType = options?.InterpolationType ?? InterpolationType.Spline;
            
            // 设置缓动函数
            if (options?.EasingFunction != null)
            {
                keyframe.EasingFunction = options.EasingFunction;
            }
        }
    }
}

1.2 创建动画控制器

public class AnimationController
{
    private readonly AnimationSet _animationSet;
    private readonly Timer _updateTimer;
    private TimeSpan _currentTime;
    private bool _isPlaying;
    private double _playbackSpeed = 1.0;
    
    public event EventHandler<AnimationProgressEventArgs> ProgressChanged;
    public event EventHandler AnimationCompleted;
    public event EventHandler<AnimationEventArgs> KeyframeReached;
    
    public AnimationController(AnimationSet animationSet)
    {
        _animationSet = animationSet;
        _updateTimer = new Timer(UpdateAnimation, null, Timeout.Infinite, Timeout.Infinite);
    }
    
    // 基础控制
    public void Play()
    {
        _isPlaying = true;
        _updateTimer.Change(0, 16); // 60 FPS
        OnAnimationStateChanged(AnimationState.Playing);
    }
    
    public void Pause()
    {
        _isPlaying = false;
        _updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
        OnAnimationStateChanged(AnimationState.Paused);
    }
    
    public void Stop()
    {
        _isPlaying = false;
        _currentTime = TimeSpan.Zero;
        _updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
        ResetToInitialState();
        OnAnimationStateChanged(AnimationState.Stopped);
    }
    
    // 高级控制
    public void SeekTo(TimeSpan time)
    {
        _currentTime = time.Clamp(TimeSpan.Zero, _animationSet.Duration);
        _animationSet.EvaluateAt(_currentTime);
        ProgressChanged?.Invoke(this, new AnimationProgressEventArgs(_currentTime));
    }
    
    public void SetPlaybackSpeed(double speed)
    {
        _playbackSpeed = Math.Max(0.1, Math.Min(10.0, speed));
        _animationSet.PlaybackSpeed = _playbackSpeed;
    }
    
    public void PlayReverse()
    {
        _playbackSpeed = -Math.Abs(_playbackSpeed);
        Play();
    }
    
    private void UpdateAnimation(object state)
    {
        if (!_isPlaying) return;
        
        var deltaTime = TimeSpan.FromMilliseconds(16 * _playbackSpeed);
        _currentTime = _currentTime.Add(deltaTime);
        
        // 检查边界
        if (_currentTime >= _animationSet.Duration)
        {
            if (_animationSet.Loop)
            {
                _currentTime = TimeSpan.Zero;
            }
            else
            {
                AnimationCompleted?.Invoke(this, EventArgs.Empty);
                Stop();
                return;
            }
        }
        else if (_currentTime < TimeSpan.Zero)
        {
            _currentTime = _animationSet.Duration;
        }
        
        // 更新动画状态
        _animationSet.EvaluateAt(_currentTime);
        ProgressChanged?.Invoke(this, new AnimationProgressEventArgs(_currentTime));
    }
}

阶段2高级动画功能第2周

2.1 相机跟随动画

public class CameraFollowAnimation
{
    public SavedViewpointAnimation CreateFollowAnimation(
        List<PathPoint> pathPoints,
        CameraFollowSettings settings)
    {
        var viewpointAnimation = new SavedViewpointAnimation();
        viewpointAnimation.Name = "物流路径跟随";
        viewpointAnimation.Duration = settings.Duration;
        viewpointAnimation.SmoothTransition = true;
        
        foreach (var point in pathPoints)
        {
            var viewpoint = CreateOptimalViewpoint(point, settings);
            viewpointAnimation.SavedViewpoints.Add(viewpoint);
        }
        
        return viewpointAnimation;
    }
    
    private SavedViewpoint CreateOptimalViewpoint(PathPoint pathPoint, CameraFollowSettings settings)
    {
        var viewpoint = new SavedViewpoint();
        
        // 智能相机定位
        var cameraPosition = CalculateOptimalCameraPosition(pathPoint, settings);
        var lookDirection = CalculateLookDirection(pathPoint, cameraPosition, settings);
        
        viewpoint.Position = cameraPosition;
        viewpoint.LookDirection = lookDirection;
        viewpoint.UpVector = settings.UpVector ?? Vector3D.UnitZ;
        viewpoint.FieldOfView = settings.FieldOfView;
        
        return viewpoint;
    }
    
    private Point3D CalculateOptimalCameraPosition(PathPoint pathPoint, CameraFollowSettings settings)
    {
        // 考虑路径方向、障碍物、最佳视角等因素
        var direction = pathPoint.Direction ?? Vector3D.UnitX;
        var offset = settings.CameraOffset;
        
        // 应用偏移和旋转
        var rotatedOffset = ApplyRotation(offset, direction);
        return pathPoint.Position + rotatedOffset;
    }
}

2.2 动画序列编排

public class AnimationSequencer
{
    private readonly List<AnimationStep> _steps;
    private int _currentStepIndex;
    
    public AnimationSequencer()
    {
        _steps = new List<AnimationStep>();
    }
    
    public AnimationSequencer AddStep(AnimationSet animation, TimeSpan delay = default)
    {
        _steps.Add(new AnimationStep
        {
            Animation = animation,
            Delay = delay,
            Type = AnimationStepType.Parallel
        });
        return this;
    }
    
    public AnimationSequencer AddSequentialStep(AnimationSet animation, TimeSpan delay = default)
    {
        _steps.Add(new AnimationStep
        {
            Animation = animation,
            Delay = delay,
            Type = AnimationStepType.Sequential
        });
        return this;
    }
    
    public async Task PlaySequenceAsync()
    {
        _currentStepIndex = 0;
        
        while (_currentStepIndex < _steps.Count)
        {
            var step = _steps[_currentStepIndex];
            
            if (step.Delay > TimeSpan.Zero)
            {
                await Task.Delay(step.Delay);
            }
            
            if (step.Type == AnimationStepType.Sequential)
            {
                await PlayStepAsync(step);
            }
            else
            {
                _ = PlayStepAsync(step); // 并行执行
            }
            
            _currentStepIndex++;
        }
    }
    
    private async Task PlayStepAsync(AnimationStep step)
    {
        var controller = new AnimationController(step.Animation);
        var tcs = new TaskCompletionSource<bool>();
        
        controller.AnimationCompleted += (s, e) => tcs.SetResult(true);
        controller.Play();
        
        await tcs.Task;
    }
}

阶段3交互式动画控制第3周

3.1 事件驱动动画

public class InteractiveAnimationSystem
{
    private readonly Document _document;
    private readonly Scripter _scripter;
    private readonly Dictionary<string, AnimationTrigger> _triggers;
    
    public InteractiveAnimationSystem(Document document)
    {
        _document = document;
        _scripter = document.Scripter;
        _triggers = new Dictionary<string, AnimationTrigger>();
        
        SetupEventHandlers();
    }
    
    private void SetupEventHandlers()
    {
        _scripter.OnKeyPress += HandleKeyPress;
        _scripter.OnMouseClick += HandleMouseClick;
        _scripter.OnModelItemSelected += HandleItemSelection;
    }
    
    public void RegisterTrigger(string name, AnimationTrigger trigger)
    {
        _triggers[name] = trigger;
    }
    
    private void HandleKeyPress(KeyPressEventArgs e)
    {
        var triggerName = $"Key_{e.Key}";
        if (_triggers.TryGetValue(triggerName, out var trigger))
        {
            ExecuteTrigger(trigger);
        }
    }
    
    private void HandleMouseClick(MouseClickEventArgs e)
    {
        var pickResult = _document.CurrentViewpoint.PickItemFromPoint(e.X, e.Y);
        if (pickResult.ModelItem != null)
        {
            var triggerName = $"Click_{pickResult.ModelItem.DisplayName}";
            if (_triggers.TryGetValue(triggerName, out var trigger))
            {
                ExecuteTrigger(trigger);
            }
        }
    }
    
    private void ExecuteTrigger(AnimationTrigger trigger)
    {
        switch (trigger.Action)
        {
            case TriggerAction.PlayAnimation:
                trigger.Animation.Play();
                break;
            case TriggerAction.ToggleAnimation:
                if (trigger.Animation.IsPlaying)
                    trigger.Animation.Pause();
                else
                    trigger.Animation.Play();
                break;
            case TriggerAction.StopAnimation:
                trigger.Animation.Stop();
                break;
        }
    }
}

🔄 迁移策略

渐进式迁移方法

public class AnimationMigrationHelper
{
    // 步骤1包装现有动画为新接口
    public static AnimationSet WrapLegacyAnimation(LegacyAnimationData legacyData)
    {
        var animationSet = new AnimationSet(legacyData.Document, legacyData.Name);
        var track = animationSet.CreateTransformTrack(legacyData.MovingObject, "Transform");
        
        // 转换现有关键帧
        foreach (var frame in legacyData.Frames)
        {
            var keyframe = track.CreateKeyframe(frame.Time);
            keyframe.Transform = frame.Transform;
        }
        
        return animationSet;
    }
    
    // 步骤2逐步替换动画创建逻辑
    public static void MigrateAnimationCreation()
    {
        // 替换手动变换为动画集创建
        // 保持接口兼容性
    }
    
    // 步骤3升级动画控制逻辑
    public static void MigrateAnimationControl()
    {
        // 替换Thread.Sleep为专业时间轴控制
        // 添加标准播放控制功能
    }
}

验收标准

功能验收

  • 所有现有动画功能正常工作
  • 新增标准动画控制(播放/暂停/停止/调速)
  • 支持相机跟随动画
  • 支持动画序列编排
  • 支持交互式动画触发

性能验收

  • 动画帧率稳定在60fps
  • CPU使用率降低60%以上
  • 内存使用稳定,无泄漏
  • 响应延迟低于50ms

用户体验验收

  • 动画播放流畅自然
  • 控制响应及时准确
  • 界面操作直观易用
  • 错误处理友好

🚨 风险控制

技术风险

  1. 兼容性风险:新动画系统与现有功能的兼容性

    • 解决方案:保留适配层,渐进式迁移
  2. 性能风险:新系统可能在某些场景下性能不如预期

    • 解决方案:充分的性能测试和优化
  3. 学习成本团队需要熟悉新的动画API

    • 解决方案:提供详细文档和示例代码

项目风险

  1. 时间风险:迁移可能比预期耗时更长

    • 解决方案:分阶段实施,优先核心功能
  2. 质量风险新系统可能引入新的bug

    • 解决方案:全面的测试覆盖和回归测试

📊 预期收益

技术收益

  • 性能提升动画流畅度提升200%CPU使用率降低60%
  • 代码质量复杂度降低70%,可维护性大幅提升
  • 功能丰富:支持专业级动画控制和交互

业务收益

  • 用户体验:流畅专业的动画效果提升产品形象
  • 开发效率动画开发时间缩短80%
  • 扩展性:为未来高级功能奠定基础

这个迁移将彻底改变项目的动画实现质量,从业余级别的手工实现升级为专业级的工业标准实现。