diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fe69922..ad442a4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -133,7 +133,8 @@ "Read(//c/Users/Tellme/apps/OpenSource/AStar-master/Roy-T.AStar/**)", "Read(//c/Users/Tellme/apps/OpenSource/AStar-master/**)", "Bash(\"./compile.bat\")", - "Bash(xcopy:*)" + "Bash(xcopy:*)", + "Read(//c/ProgramData/Autodesk/Navisworks Manage 2026/NavisworksTransport/logs/**)" ], "deny": [], "additionalDirectories": [ diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj index 5b2209b..ffa1060 100644 --- a/NavisworksTransportPlugin.csproj +++ b/NavisworksTransportPlugin.csproj @@ -201,6 +201,9 @@ PathAnalysisDialog.xaml + + MediaControlBar.xaml + @@ -305,11 +308,19 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile + + Designer + MSBuild:Compile + diff --git a/doc/working/2017/步进式动画方案.md b/doc/working/2017/步进式动画方案.md index 1673e2e..dd01c49 100644 --- a/doc/working/2017/步进式动画方案.md +++ b/doc/working/2017/步进式动画方案.md @@ -365,4 +365,17 @@ │ 3. 改造播放控制为步进式 │ │ 4. 实现帧导航方法 │ │ 5. 更新UI添加步进控制 │ - │ 6. 测试各种播放模式 \ No newline at end of file + │ 6. 测试各种播放模式 + + + + 🎮 媒体控制功能: + + - 快退10帧 (Shift+←) - 快速后退功能 + - 上一帧 (←) - 精确单帧后退 + - 反向播放 (R键) - 连续反向播放动画 + - 暂停 (Space) - 居中暂停控制 + - 正向播放 - 标准前进播放 + - 下一帧 (→) - 精确单帧前进 + - 快进10帧 (Shift+→) - 快速前进功能 + - 停止 (Esc) - 停止并重置动画 \ No newline at end of file diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index 526fadc..bfb26a5 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -66,6 +66,11 @@ namespace NavisworksTransport.Core.Animation private double _movementSpeed = 1.0; // 运动速度(默认1米/秒) private double _detectionGap = 0.05; // 检测间隙(默认0.05米) + // === 双向播放和步进控制 === + private int _playbackDirection = 1; // 播放方向:1=正向,-1=反向 + private double _playbackSpeed = 1.0; // 播放速度倍率 + private bool _isStepMode = false; // 是否处于步进模式 + // === 性能监控 === private int _fpsFrameCount = 0; private DateTime _fpsCounterStart = DateTime.Now; @@ -316,7 +321,7 @@ namespace NavisworksTransport.Core.Animation LogManager.Info($"潜在碰撞对象数: {potentialColliders.Count}"); // 预计算每一帧 - for (int i = 0; i <= totalFrames; i++) + for (int i = 0; i < totalFrames; i++) { double progress = (double)i / totalFrames; var framePosition = InterpolatePosition(progress); @@ -640,6 +645,14 @@ namespace NavisworksTransport.Core.Animation SetState(AnimationState.Stopped); _pausedProgress = 0.0; // 重置暂停进度 _currentFrameIndex = 0; // 重置帧索引 + + // 将物体移回起点位置 + if (_pathPoints != null && _pathPoints.Count > 0 && _animatedObject != null) + { + UpdateObjectPosition(_pathPoints[0]); + LogManager.Info($"物体已移回起点位置: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})"); + } + LogManager.Info("动画已停止"); // 动画停止时不创建碰撞测试汇总,由动画完成事件统一处理 @@ -1315,6 +1328,245 @@ namespace NavisworksTransport.Core.Animation /// public TimeLinerIntegrationManager TimeLinerManager => _timeLinerManager; + #region 帧控制功能 + + /// + /// 获取当前帧索引 + /// + public int CurrentFrame => _currentFrameIndex; + + /// + /// 获取总帧数 + /// + public int TotalFrames => _animationFrames?.Count ?? 0; + + /// + /// 获取播放方向(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); + + // 更新碰撞高亮 + UpdateCollisionHighlightFromFrame(); + + // 计算并触发进度事件 + double progress = TotalFrames > 1 ? (double)_currentFrameIndex / (TotalFrames - 1) * 100 : 0; + ProgressChanged?.Invoke(this, progress); + + LogManager.Info($"跳转到帧 {frameIndex}/{TotalFrames - 1} (进度: {progress:F1}%)"); + } + 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); + LogManager.Debug($"前进 {frames} 帧,当前帧: {_currentFrameIndex}"); + } + 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); + LogManager.Debug($"后退 {frames} 帧,当前帧: {_currentFrameIndex}"); + } + 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 + /// /// 清理对象引用 /// @@ -1713,18 +1965,38 @@ namespace NavisworksTransport.Core.Animation return; // 还没到下一帧时间,跳过本次Idle } - // 计算动画进度 - var totalElapsed = (now - _animationStartTime).TotalSeconds; - var progress = Math.Min(totalElapsed / _animationDuration, 1.0); - - // 根据进度计算当前帧索引 - int targetFrameIndex = (int)(progress * (_animationFrames.Count - 1)); - targetFrameIndex = Math.Min(targetFrameIndex, _animationFrames.Count - 1); + // 计算下一帧索引(考虑播放方向和速度) + int nextFrameIndex = _currentFrameIndex; - // 如果帧索引发生变化,更新位置和碰撞高亮 - if (targetFrameIndex != _currentFrameIndex || _currentFrameIndex == 0) + if (_playbackDirection == 1) // 正向播放 { - _currentFrameIndex = targetFrameIndex; + nextFrameIndex = _currentFrameIndex + 1; + if (nextFrameIndex >= _animationFrames.Count) + { + // 正向播放到达终点 + nextFrameIndex = _animationFrames.Count - 1; + NavisApplication.Idle -= OnApplicationIdle; + FinishAnimation(); + return; + } + } + else // 反向播放 + { + nextFrameIndex = _currentFrameIndex - 1; + if (nextFrameIndex < 0) + { + // 反向播放到达起点 + nextFrameIndex = 0; + NavisApplication.Idle -= OnApplicationIdle; + FinishAnimation(); + return; + } + } + + // 更新帧索引和位置 + if (nextFrameIndex != _currentFrameIndex) + { + _currentFrameIndex = nextFrameIndex; // 使用预计算的帧位置 if (_currentFrameIndex < _animationFrames.Count) @@ -1735,34 +2007,29 @@ namespace NavisworksTransport.Core.Animation // 更新碰撞高亮(基于预计算结果) UpdateCollisionHighlightFromFrame(); - LogManager.Debug($"帧 {_currentFrameIndex}/{_animationFrames.Count-1}, 进度: {progress*100:F1}%, 位置: ({frameData.Position.X:F2},{frameData.Position.Y:F2},{frameData.Position.Z:F2})"); + // 计算当前进度(基于帧索引) + double progress = _animationFrames.Count > 1 ? (double)_currentFrameIndex / (_animationFrames.Count - 1) : 0; + + LogManager.Debug($"帧 {_currentFrameIndex}/{_animationFrames.Count-1}, 进度: {progress*100:F1}%, 方向: {(_playbackDirection == 1 ? "正向" : "反向")}, 位置: ({frameData.Position.X:F2},{frameData.Position.Y:F2},{frameData.Position.Z:F2})"); + + // 触发进度事件 + var progressPercent = progress * 100; + ProgressChanged?.Invoke(this, progressPercent); + + // 同步进度到 TimeLiner(如果可用) + if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) + { + _timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState); + } } } - // 触发进度事件 - var progressPercent = progress * 100; - ProgressChanged?.Invoke(this, progressPercent); - - // 同步进度到 TimeLiner(如果可用) - if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) - { - _timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState); - } - // 记录帧时间 _lastFrameTime = now; _animationFrameCount++; // 性能监控 UpdateFPSCounter(); - - // 检查动画完成 - if (progress >= 1.0) - { - // 先注销事件,避免重复调用 - NavisApplication.Idle -= OnApplicationIdle; - FinishAnimation(); - } } catch (Exception ex) { diff --git a/src/UI/WPF/Resources/MediaControlIcons.xaml b/src/UI/WPF/Resources/MediaControlIcons.xaml new file mode 100644 index 0000000..3c90a20 --- /dev/null +++ b/src/UI/WPF/Resources/MediaControlIcons.xaml @@ -0,0 +1,240 @@ + + + + + + + M11,6L2,12L11,18V6M20,6L11,12L20,18V6Z + + + + + M6,6V18H8V6H6M9.5,12L18,6V18L9.5,12Z + + + + + M8,6V18L19,12L8,6Z + + + + + M14,18H18V6H14M6,18H10V6H6V18Z + + + + + M8,6V18L19,12L8,6Z + + + + + M16,18H18V6H16M6,18L14.5,12L6,6V18Z + + + + + M13,6V18L22,12M4,18L12.5,12L4,6V18Z + + + + + M18,18H6V6H18V18Z + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index cebfa2f..a314a26 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -77,12 +77,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels private ObservableCollection _availableFrameRates; private int _selectedFrameRate = 30; private double _animationDuration = 10.0; - private double _currentAnimationTime = 0.0; private bool _canStartAnimation = true; private bool _canPauseAnimation = false; private bool _canStopAnimation = false; - private double _animationProgress = 0.0; - private string _startAnimationButtonText = "开始动画"; // 碰撞检测相关字段 private bool _hasCollisionResults = false; @@ -142,15 +139,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels } - /// - /// 当前动画时间 - /// - public double CurrentAnimationTime - { - get => _currentAnimationTime; - set => SetProperty(ref _currentAnimationTime, value); - } - /// /// 是否可以开始动画 /// @@ -179,25 +167,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels } - /// - /// 动画进度 - /// - public double AnimationProgress - { - get => _animationProgress; - set => SetProperty(ref _animationProgress, value); - } - - /// - /// 开始动画按钮文本 - /// - public string StartAnimationButtonText - { - get => _startAnimationButtonText; - set => SetProperty(ref _startAnimationButtonText, value); - } - - /// /// 是否有碰撞检测结果 /// @@ -359,11 +328,80 @@ namespace NavisworksTransport.UI.WPF.ViewModels set => SetProperty(ref _canGenerateAnimation, value); } + #region 媒体控制属性 + + /// + /// 时间轴位置(0-总帧数) + /// + public double TimelinePosition + { + get => _pathAnimationManager?.CurrentFrame ?? 0; + set + { + if (_pathAnimationManager != null) + { + _pathAnimationManager.SeekToFrame((int)Math.Round(value)); + } + OnPropertyChanged(); + } + } + + /// + /// 时间轴持续时间(总帧数) + /// + public double TimelineDuration => _pathAnimationManager?.TotalFrames - 1 ?? 0; + + /// + /// 当前时间显示(格式化) + /// + public string CurrentTimeDisplay + { + get + { + if (_pathAnimationManager == null) return "00:00"; + + var currentSeconds = (_pathAnimationManager.CurrentFrame / (double)(_pathAnimationManager.TotalFrames - 1)) * _animationDuration; + return FormatTime(currentSeconds); + } + } + + /// + /// 总时长显示(格式化) + /// + public string TotalTimeDisplay => FormatTime(_animationDuration); + + /// + /// 帧信息显示 + /// + public string FrameInfoDisplay + { + get + { + if (_pathAnimationManager == null) return "0/0"; + return $"{_pathAnimationManager.CurrentFrame + 1}/{_pathAnimationManager.TotalFrames}"; + } + } + + /// + /// 是否正在正向播放 + /// + public bool IsPlayingForward => _pathAnimationManager?.PlaybackDirection == 1 && + _pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing; + + /// + /// 是否正在反向播放 + /// + public bool IsPlayingReverse => _pathAnimationManager?.PlaybackDirection == -1 && + _pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing; + + #endregion + #endregion #region 命令 + // 原有命令 public ICommand StartAnimationCommand { get; private set; } public ICommand PauseAnimationCommand { get; private set; } public ICommand StopAnimationCommand { get; private set; } @@ -372,6 +410,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels public ICommand ClearAnimatedObjectCommand { get; private set; } public ICommand GenerateAnimationCommand { get; private set; } + // 媒体控制命令 + public ICommand PlayForwardCommand { get; private set; } + public ICommand PlayReverseCommand { get; private set; } + public ICommand StepForwardCommand { get; private set; } + public ICommand StepBackwardCommand { get; private set; } + public ICommand FastForwardCommand { get; private set; } + public ICommand FastBackwardCommand { get; private set; } + public ICommand SeekToStartCommand { get; private set; } + public ICommand SeekToEndCommand { get; private set; } + #endregion #region 构造函数 @@ -484,13 +532,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 设置默认动画参数 AnimationDuration = 10.0; - CurrentAnimationTime = 0.0; // 设置初始状态 - 修改: 默认状态应该是未激活,等待有效动画 CanPauseAnimation = false; CanStopAnimation = false; - StartAnimationButtonText = "开始动画"; - AnimationProgress = 0; // 初始化碰撞检测状态 HasCollisionResults = false; @@ -517,6 +562,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels /// private void InitializeCommands() { + // 原有命令 StartAnimationCommand = new RelayCommand(async () => await ExecuteStartAnimationAsync(), () => CanStartAnimation); PauseAnimationCommand = new RelayCommand(async () => await ExecutePauseAnimationAsync(), () => CanPauseAnimation); StopAnimationCommand = new RelayCommand(async () => await ExecuteStopAnimationAsync(), () => CanStopAnimation); @@ -525,6 +571,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject); GenerateAnimationCommand = new RelayCommand(ExecuteGenerateAnimation, () => CanGenerateAnimation); + // 媒体控制命令 + PlayForwardCommand = new RelayCommand(ExecutePlayForward, CanExecuteMediaCommands); + PlayReverseCommand = new RelayCommand(ExecutePlayReverse, CanExecuteMediaCommands); + StepForwardCommand = new RelayCommand(ExecuteStepForward, CanExecuteMediaCommands); + StepBackwardCommand = new RelayCommand(ExecuteStepBackward, CanExecuteMediaCommands); + FastForwardCommand = new RelayCommand(ExecuteFastForward, CanExecuteMediaCommands); + FastBackwardCommand = new RelayCommand(ExecuteFastBackward, CanExecuteMediaCommands); + SeekToStartCommand = new RelayCommand(ExecuteSeekToStart, CanExecuteMediaCommands); + SeekToEndCommand = new RelayCommand(ExecuteSeekToEnd, CanExecuteMediaCommands); + LogManager.Info("动画控制命令初始化完成"); } @@ -592,11 +648,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels LogManager.Info("已清理UI层碰撞检测缓存"); // 首先重置进度(开始全新动画时) - await _uiStateManager.ExecuteUIUpdateAsync(() => - { - AnimationProgress = 0; - CurrentAnimationTime = 0.0; - }); _pathAnimationManager.StartAnimation(); @@ -652,8 +703,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels await _uiStateManager.ExecuteUIUpdateAsync(() => { UpdateMainStatus("动画已停止"); - AnimationProgress = 0; - CurrentAnimationTime = 0.0; CanStartAnimation = true; CanPauseAnimation = false; @@ -738,6 +787,181 @@ namespace NavisworksTransport.UI.WPF.ViewModels } } + #region 媒体控制命令实现 + + /// + /// 检查媒体控制命令是否可执行 + /// + private bool CanExecuteMediaCommands() + { + return _pathAnimationManager != null && + _pathAnimationManager.CurrentState != NavisworksTransport.Core.Animation.AnimationState.Idle && + _pathAnimationManager.TotalFrames > 0; + } + + /// + /// 执行正向播放命令 + /// + private void ExecutePlayForward() + { + try + { + _pathAnimationManager?.PlayForward(); + UpdateMediaControlProperties(); + LogManager.Info("执行正向播放命令"); + } + catch (Exception ex) + { + LogManager.Error($"正向播放命令执行失败: {ex.Message}"); + UpdateMainStatus("正向播放失败"); + } + } + + /// + /// 执行反向播放命令 + /// + private void ExecutePlayReverse() + { + try + { + _pathAnimationManager?.PlayReverse(); + UpdateMediaControlProperties(); + LogManager.Info("执行反向播放命令"); + } + catch (Exception ex) + { + LogManager.Error($"反向播放命令执行失败: {ex.Message}"); + UpdateMainStatus("反向播放失败"); + } + } + + /// + /// 执行单帧前进命令 + /// + private void ExecuteStepForward() + { + try + { + _pathAnimationManager?.StepForward(); + UpdateMediaControlProperties(); + LogManager.Debug("执行单帧前进命令"); + } + catch (Exception ex) + { + LogManager.Error($"单帧前进命令执行失败: {ex.Message}"); + } + } + + /// + /// 执行单帧后退命令 + /// + private void ExecuteStepBackward() + { + try + { + _pathAnimationManager?.StepBackward(); + UpdateMediaControlProperties(); + LogManager.Debug("执行单帧后退命令"); + } + catch (Exception ex) + { + LogManager.Error($"单帧后退命令执行失败: {ex.Message}"); + } + } + + /// + /// 执行快进10帧命令 + /// + private void ExecuteFastForward() + { + try + { + _pathAnimationManager?.FastForward(); + UpdateMediaControlProperties(); + LogManager.Debug("执行快进10帧命令"); + } + catch (Exception ex) + { + LogManager.Error($"快进命令执行失败: {ex.Message}"); + } + } + + /// + /// 执行快退10帧命令 + /// + private void ExecuteFastBackward() + { + try + { + _pathAnimationManager?.FastBackward(); + UpdateMediaControlProperties(); + LogManager.Debug("执行快退10帧命令"); + } + catch (Exception ex) + { + LogManager.Error($"快退命令执行失败: {ex.Message}"); + } + } + + /// + /// 执行跳转到开头命令 + /// + private void ExecuteSeekToStart() + { + try + { + _pathAnimationManager?.SeekToFrame(0); + UpdateMediaControlProperties(); + LogManager.Info("跳转到动画开头"); + } + catch (Exception ex) + { + LogManager.Error($"跳转到开头失败: {ex.Message}"); + } + } + + /// + /// 执行跳转到结尾命令 + /// + private void ExecuteSeekToEnd() + { + try + { + int lastFrame = (_pathAnimationManager?.TotalFrames ?? 1) - 1; + _pathAnimationManager?.SeekToFrame(lastFrame); + UpdateMediaControlProperties(); + LogManager.Info("跳转到动画结尾"); + } + catch (Exception ex) + { + LogManager.Error($"跳转到结尾失败: {ex.Message}"); + } + } + + /// + /// 更新媒体控制相关属性 + /// + private void UpdateMediaControlProperties() + { + OnPropertyChanged(nameof(TimelinePosition)); + OnPropertyChanged(nameof(TimelineDuration)); + OnPropertyChanged(nameof(CurrentTimeDisplay)); + OnPropertyChanged(nameof(FrameInfoDisplay)); + OnPropertyChanged(nameof(IsPlayingForward)); + OnPropertyChanged(nameof(IsPlayingReverse)); + } + + /// + /// 格式化时间显示 + /// + private string FormatTime(double seconds) + { + var timeSpan = TimeSpan.FromSeconds(Math.Max(0, seconds)); + return $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}"; + } + + #endregion + #endregion #region 辅助方法 @@ -794,9 +1018,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 直接使用Dispatcher.BeginInvoke进行UI更新,避免UIStateManager的5秒超时导致积压 System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => { - // 直接使用传入的百分比数值 - AnimationProgress = progressPercent; - CurrentAnimationTime = (AnimationProgress / 100.0) * AnimationDuration; + // 更新时间轴相关属性 + OnPropertyChanged(nameof(TimelinePosition)); + OnPropertyChanged(nameof(CurrentTimeDisplay)); + OnPropertyChanged(nameof(FrameInfoDisplay)); })); } @@ -815,35 +1040,35 @@ namespace NavisworksTransport.UI.WPF.ViewModels CanStartAnimation = false; CanPauseAnimation = true; CanStopAnimation = true; - StartAnimationButtonText = "开始动画"; // 播放中时按钮显示为灰色的"开始动画" UpdateMainStatus("动画播放中"); + UpdateMediaControlProperties(); // 更新媒体控制属性 break; case NavisworksTransport.Core.Animation.AnimationState.Paused: CanStartAnimation = true; // 暂停状态下可以继续 CanPauseAnimation = false; CanStopAnimation = true; - StartAnimationButtonText = "继续播放"; // 暂停状态下显示"继续播放" UpdateMainStatus("动画已暂停"); + UpdateMediaControlProperties(); // 更新媒体控制属性 break; case NavisworksTransport.Core.Animation.AnimationState.Finished: CanStartAnimation = true; CanPauseAnimation = false; CanStopAnimation = false; - StartAnimationButtonText = "开始动画"; // 完成后恢复"开始动画" UpdateMainStatus("动画已完成"); // 动画完成后更新碰撞检测状态 UpdateCollisionStatusAfterAnimation(); + UpdateMediaControlProperties(); // 更新媒体控制属性 break; case NavisworksTransport.Core.Animation.AnimationState.Stopped: CanStartAnimation = true; CanPauseAnimation = false; CanStopAnimation = false; - StartAnimationButtonText = "开始动画"; // 停止后恢复"开始动画" UpdateMainStatus("动画已停止"); + UpdateMediaControlProperties(); // 更新媒体控制属性 break; default: - StartAnimationButtonText = "开始动画"; UpdateMainStatus($"动画状态: {state}"); + UpdateMediaControlProperties(); // 更新媒体控制属性 break; } })); @@ -987,11 +1212,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels // 清理选择状态 SelectedAnimatedObject = null; - - // 重置动画进度和状态 - AnimationProgress = 0; - CurrentAnimationTime = 0.0; - + // 清理碰撞检测结果 HasCollisionResults = false; UpdateMainStatus("碰撞检测重置"); @@ -1269,16 +1490,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels CanStartAnimation = hasValidAnimation && isAnimationReady; CanPauseAnimation = false; CanStopAnimation = true; - StartAnimationButtonText = "继续播放"; UpdateMainStatus("动画已暂停"); break; - + default: // 停止或其他状态:需要同时满足有效动画条件和动画已生成条件 CanStartAnimation = hasValidAnimation && isAnimationReady; CanPauseAnimation = false; CanStopAnimation = false; - StartAnimationButtonText = "开始动画"; // 更新状态文本 if (!hasValidPath && !hasValidAnimatedObject) @@ -1698,11 +1917,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels SelectedAnimatedObjectName = string.Empty; CurrentPathRoute = null; - // 重置动画参数 - AnimationProgress = 0; - CurrentAnimationTime = 0; - StartAnimationButtonText = "开始"; - // 禁用按钮 CanStartAnimation = false; CanPauseAnimation = false; diff --git a/src/UI/WPF/Views/AnimationControlView.xaml b/src/UI/WPF/Views/AnimationControlView.xaml index 71f29bf..e1363ba 100644 --- a/src/UI/WPF/Views/AnimationControlView.xaml +++ b/src/UI/WPF/Views/AnimationControlView.xaml @@ -15,6 +15,7 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:converters="clr-namespace:NavisworksTransport.UI.WPF.Converters" + xmlns:views="clr-namespace:NavisworksTransport.UI.WPF.Views" mc:Ignorable="d" d:DesignHeight="700" d:DesignWidth="480"> @@ -254,57 +255,14 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管 - + + \ No newline at end of file diff --git a/src/UI/WPF/Views/MediaControlBar.xaml.cs b/src/UI/WPF/Views/MediaControlBar.xaml.cs new file mode 100644 index 0000000..f5dc2a3 --- /dev/null +++ b/src/UI/WPF/Views/MediaControlBar.xaml.cs @@ -0,0 +1,153 @@ +using System.Windows.Controls; +using System.Windows.Input; +using NavisworksTransport.Utils; + +namespace NavisworksTransport.UI.WPF.Views +{ + /// + /// MediaControlBar.xaml 的交互逻辑 + /// 专业级媒体播放器风格的动画控制条 + /// + public partial class MediaControlBar : UserControl + { + public MediaControlBar() + { + InitializeComponent(); + + // 启用键盘焦点以支持快捷键 + this.Focusable = true; + this.KeyDown += OnKeyDown; + + LogManager.Debug("MediaControlBar 初始化完成"); + } + + /// + /// 键盘快捷键处理 + /// + private void OnKeyDown(object sender, KeyEventArgs e) + { + try + { + var viewModel = this.DataContext as NavisworksTransport.UI.WPF.ViewModels.AnimationControlViewModel; + if (viewModel == null) return; + + // 处理键盘快捷键 + switch (e.Key) + { + case Key.Space: + // 空格键:播放/暂停切换 + if (viewModel.CanPauseAnimation) + { + viewModel.PauseAnimationCommand?.Execute(null); + } + else if (viewModel.CanStartAnimation) + { + viewModel.StartAnimationCommand?.Execute(null); + } + e.Handled = true; + break; + + case Key.Left: + // 左箭头:单帧后退 或 Shift+左箭头:快退10帧 + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) + { + viewModel.FastBackwardCommand?.Execute(null); + } + else + { + viewModel.StepBackwardCommand?.Execute(null); + } + e.Handled = true; + break; + + case Key.Right: + // 右箭头:单帧前进 或 Shift+右箭头:快进10帧 + if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) + { + viewModel.FastForwardCommand?.Execute(null); + } + else + { + viewModel.StepForwardCommand?.Execute(null); + } + e.Handled = true; + break; + + case Key.R: + // R键:切换播放方向 + if (viewModel.IsPlayingReverse) + { + viewModel.PlayForwardCommand?.Execute(null); + } + else + { + viewModel.PlayReverseCommand?.Execute(null); + } + e.Handled = true; + break; + + case Key.Home: + // Home键:跳转到开头 + viewModel.SeekToStartCommand?.Execute(null); + e.Handled = true; + break; + + case Key.End: + // End键:跳转到结尾 + viewModel.SeekToEndCommand?.Execute(null); + e.Handled = true; + break; + + case Key.Escape: + // Esc键:停止播放 + viewModel.StopAnimationCommand?.Execute(null); + e.Handled = true; + break; + } + + if (e.Handled) + { + LogManager.Debug($"处理键盘快捷键: {e.Key} (修饰键: {Keyboard.Modifiers})"); + } + } + catch (System.Exception ex) + { + LogManager.Error($"处理键盘快捷键时发生错误: {ex.Message}"); + } + } + + /// + /// 控件获得焦点时自动聚焦到时间轴滑块,以便支持键盘操作 + /// + private void OnGotFocus(object sender, System.Windows.RoutedEventArgs e) + { + try + { + // 将焦点设置到时间轴滑块,以便键盘操作 + TimelineSlider?.Focus(); + } + catch (System.Exception ex) + { + LogManager.Debug($"设置焦点时出现异常: {ex.Message}"); + } + } + + /// + /// 鼠标进入控制条时获取焦点,以便支持键盘快捷键 + /// + private void OnMouseEnter(object sender, MouseEventArgs e) + { + try + { + if (!this.IsFocused) + { + this.Focus(); + } + } + catch (System.Exception ex) + { + LogManager.Debug($"鼠标进入时设置焦点异常: {ex.Message}"); + } + } + } +} \ No newline at end of file