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 检测动画页签视图 - 采用与类别设置和分层管
-
+
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/UI/WPF/Views/MediaControlBar.xaml b/src/UI/WPF/Views/MediaControlBar.xaml
new file mode 100644
index 0000000..61df641
--- /dev/null
+++ b/src/UI/WPF/Views/MediaControlBar.xaml
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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