diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 9c073bf4..619d2e75 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index ff1638a0..34e4bd06 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -3260,7 +3260,7 @@ No valid error was returned; keep the last accepted result and reduce the step.< Result playback is unavailable.结果回放不可用。 The selected result property is unavailable.所选结果属性不可用。 The selected result frame is unavailable.所选结果帧不可用。 - Playback speed must be a positive integer.播放速度必须为正整数。 + Playback speed must be between %1 and %2 steps/second.播放速度必须为 %1 至 %2 步/秒。 nmFFmpegVideoWriter diff --git a/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h b/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h index 09f2f25c..5214408e 100644 --- a/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h +++ b/Include/nmNum/nmSubWxs/nmPebiResultPlaybackController.h @@ -22,10 +22,11 @@ class NM_SUB_WXS_EXPORT nmPebiResultPlaybackController : public QObject Q_OBJECT public: - /** @brief 回放速度的合法下限和窗口默认值,单位均为步/秒。 */ + /** @brief 回放速度的合法范围和窗口默认值,单位均为步/秒。 */ enum { MinimumPlaybackSpeed = 1, + MaximumPlaybackSpeed = 30, DefaultPlaybackSpeed = 5 }; @@ -120,7 +121,7 @@ private: bool synchronizeSceneIdentity(); /** @brief 按固定顺序同步失败后的 Scene 状态并通知界面。 */ bool finishFailedOperation(const QString& sError); - /** @brief 使用当前速度计算至少 1ms 的定时器间隔。 */ + /** @brief 向上取整到毫秒,避免定时器请求频率超过设定速度。 */ int calculatePlaybackInterval() const; /** @brief 已渲染属性和帧的唯一真实来源;本类不拥有。 */ diff --git a/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h b/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h index 13a93a4a..5164aeeb 100644 --- a/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h +++ b/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h @@ -3,6 +3,7 @@ #include "nmSubWxs_global.h" +#include #include #include #include @@ -94,6 +95,8 @@ private: void rebuildTimeItems(const QString& sPropertyId, int nFrameCount); /** @brief 清空并禁用不适用于静态属性的时间下拉框。 */ void clearTimeItems(); + /** @brief 只在播放状态变化时切换缓存图标和提示。 */ + void updatePlayPauseButton(bool bPlaying); /** @brief 使用 QVariant 精确匹配 ComboBox 的稳定业务数据。 */ int findItemByData(QComboBox* pComboBox, const QVariant& oData) const; @@ -138,6 +141,9 @@ private: QString m_sDisplayedTimePropertyId; /** @brief 当前时间框已完整构建的帧数,失败时为 -1。 */ int m_nDisplayedTimeFrameCount; + QIcon m_oPlayIcon; + QIcon m_oPauseIcon; + bool m_bDisplayedPlaying; }; #endif // NMWXPEBIRESULTCONTROLBAR_H diff --git a/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp b/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp index 9365579d..1be0e102 100644 --- a/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp +++ b/Src/nmNum/nmSubWxs/nmPebiResultPlaybackController.cpp @@ -20,7 +20,7 @@ static const char* const s_pPlaybackTranslationMarkers[] = QT_TRANSLATE_NOOP("nmPebiResultPlaybackController", "The selected result frame is unavailable."), QT_TRANSLATE_NOOP("nmPebiResultPlaybackController", - "Playback speed must be a positive integer.") + "Playback speed must be between %1 and %2 steps/second.") }; QString playbackText(const char* pText) @@ -244,10 +244,12 @@ bool nmPebiResultPlaybackController::setPlaybackSettings( int nStepsPerSecond, bool bLoopEnabled) { - if(nStepsPerSecond < MinimumPlaybackSpeed) { + if(nStepsPerSecond < MinimumPlaybackSpeed || + nStepsPerSecond > MaximumPlaybackSpeed) { // 公开接口不能依赖设置对话框兜底;非法参数也执行统一失败收尾。 return finishFailedOperation(playbackText( - "Playback speed must be a positive integer.")); + "Playback speed must be between %1 and %2 steps/second.") + .arg(MinimumPlaybackSpeed).arg(MaximumPlaybackSpeed)); } if(m_nStepsPerSecond == nStepsPerSecond && m_bLoopEnabled == bLoopEnabled) { @@ -482,6 +484,5 @@ bool nmPebiResultPlaybackController::finishFailedOperation( int nmPebiResultPlaybackController::calculatePlaybackInterval() const { - return qMax(1, qRound(1000.0 / - static_cast(m_nStepsPerSecond))); + return (1000 + m_nStepsPerSecond - 1) / m_nStepsPerSecond; } diff --git a/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp b/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp index 981e3807..990f517e 100644 --- a/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp +++ b/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp @@ -513,6 +513,8 @@ public: void applyScalarBarState(const nmPebiResultDisplayState& oDisplay) { + // 仅在快照、属性或配色提交成功后替换;普通切帧复用同一份 LUT。 + m_oCurrentDisplayState = oDisplay; if(m_pScalarBarLayer == nullptr) { return; } @@ -626,6 +628,7 @@ public: delete m_pAdapter; m_pAdapter = nullptr; m_pSnapshot.clear(); + m_oCurrentDisplayState = nmPebiResultDisplayState(); m_listAvailablePropertyIds.clear(); m_sCurrentPropertyId.clear(); m_nCurrentFrameIndex = -1; @@ -694,6 +697,8 @@ public: QString m_sCurrentPropertyId; /** @brief 按稳定属性 ID 保存的窗口级样式;clear 不删除。 */ QMap m_mapDisplaySettings; + /** @brief 仅缓存当前绑定、当前属性的显示状态,不按帧积累。 */ + nmPebiResultDisplayState m_oCurrentDisplayState; nmVTKMeshLayer::DisplayMode m_eMeshDisplayMode; ///< 本窗口显示偏好。 int m_nCurrentFrameIndex; /** @brief 不同快照对象完整提交成功后的单调序号,clear 和失败均不回退。 */ @@ -1030,9 +1035,16 @@ bool nmPebiResultSceneController::applyPropertyFrame( m_pPrivate->m_mapDisplaySettings.value( sPropertyId, nmPebiResultDisplaySettings()); nmPebiResultDisplayState oDisplayCandidate; - if(!nmPebiResultDisplayStateBuilder::build( - m_pPrivate->m_pSnapshot, sPropertyId, oTargetSettings, - oDisplayCandidate, pError)) { + const bool bReuseDisplayState = + sPropertyId == m_pPrivate->m_sCurrentPropertyId && + m_pPrivate->m_oCurrentDisplayState.m_pLookupTable != nullptr && + m_pPrivate->m_oCurrentDisplayState.m_oSettings == oTargetSettings; + if(bReuseDisplayState) { + // 色域来自整个快照,与帧号无关;快照绑定和配色提交负责刷新缓存。 + oDisplayCandidate = m_pPrivate->m_oCurrentDisplayState; + } else if(!nmPebiResultDisplayStateBuilder::build( + m_pPrivate->m_pSnapshot, sPropertyId, oTargetSettings, + oDisplayCandidate, pError)) { return false; } @@ -1069,18 +1081,20 @@ bool nmPebiResultSceneController::applyPropertyFrame( m_pPrivate->m_sCurrentPropertyId = sPropertyId; m_pPrivate->m_nCurrentFrameIndex = nFrameIndex; - m_pPrivate->m_mapDisplaySettings.insert( - sPropertyId, oDisplayCandidate.m_oSettings); - m_pPrivate->m_dBusinessRange[0] = - oDisplayCandidate.m_dBusinessRange[0]; - m_pPrivate->m_dBusinessRange[1] = - oDisplayCandidate.m_dBusinessRange[1]; - m_pPrivate->m_dDisplayRange[0] = - oDisplayCandidate.m_dVisibleRange[0]; - m_pPrivate->m_dDisplayRange[1] = - oDisplayCandidate.m_dVisibleRange[1]; - m_pPrivate->m_bHasPropertyRange = true; - m_pPrivate->applyScalarBarState(oDisplayCandidate); + if(!bReuseDisplayState) { + m_pPrivate->m_mapDisplaySettings.insert( + sPropertyId, oDisplayCandidate.m_oSettings); + m_pPrivate->m_dBusinessRange[0] = + oDisplayCandidate.m_dBusinessRange[0]; + m_pPrivate->m_dBusinessRange[1] = + oDisplayCandidate.m_dBusinessRange[1]; + m_pPrivate->m_dDisplayRange[0] = + oDisplayCandidate.m_dVisibleRange[0]; + m_pPrivate->m_dDisplayRange[1] = + oDisplayCandidate.m_dVisibleRange[1]; + m_pPrivate->m_bHasPropertyRange = true; + m_pPrivate->applyScalarBarState(oDisplayCandidate); + } m_pPrivate->m_pContainer->renderView(); return true; } @@ -1115,6 +1129,13 @@ bool nmPebiResultSceneController::applyCurrentDisplaySettings( return false; } + const nmPebiResultDisplaySettings oCurrentSettings = + m_pPrivate->m_mapDisplaySettings.value( + sExpectedPropertyId, nmPebiResultDisplaySettings()); + if(oSettings == oCurrentSettings) { + return true; + } + nmPebiResultDisplayState oDisplayCandidate; if(!nmPebiResultDisplayStateBuilder::build( m_pPrivate->m_pSnapshot, @@ -1125,13 +1146,6 @@ bool nmPebiResultSceneController::applyCurrentDisplaySettings( return false; } - const nmPebiResultDisplaySettings oCurrentSettings = - m_pPrivate->m_mapDisplaySettings.value( - sExpectedPropertyId, nmPebiResultDisplaySettings()); - if(oDisplayCandidate.m_oSettings == oCurrentSettings.normalized()) { - return true; - } - // MeshLayer 先完整验证候选 LUT;失败时旧 Mapper 和 ScalarBar 仍共享旧 LUT。 if(!m_pPrivate->m_pMeshLayer->setLookupTable( oDisplayCandidate.m_pLookupTable)) { diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultAnimationSettingsDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultAnimationSettingsDlg.cpp index 47b55c38..f77c247e 100644 --- a/Src/nmNum/nmSubWxs/nmWxPebiResultAnimationSettingsDlg.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPebiResultAnimationSettingsDlg.cpp @@ -16,8 +16,6 @@ #include #include -#include - namespace { QString resultAnimationIconPath(const QString& sFileName) @@ -44,11 +42,13 @@ nmWxPebiResultAnimationSettingsDlg( m_pSpeedSpin->setRange( nmPebiResultPlaybackController::MinimumPlaybackSpeed, - INT_MAX); - m_pSpeedSpin->setValue(qMax( + nmPebiResultPlaybackController::MaximumPlaybackSpeed); + m_pSpeedSpin->setValue(qBound( static_cast( nmPebiResultPlaybackController::MinimumPlaybackSpeed), - nStepsPerSecond)); + nStepsPerSecond, + static_cast( + nmPebiResultPlaybackController::MaximumPlaybackSpeed))); m_pLoopCheck->setChecked(bLoopEnabled); QFormLayout* pFormLayout = new QFormLayout(); diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp index 9fa2d4e6..aae7fd53 100644 --- a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp @@ -71,7 +71,8 @@ nmWxPebiResultControlBar::nmWxPebiResultControlBar( m_pExportButton(nullptr), m_bExportInProgress(false), m_nDisplayedBindingRevision(0), - m_nDisplayedTimeFrameCount(-1) + m_nDisplayedTimeFrameCount(-1), + m_bDisplayedPlaying(false) { Q_ASSERT(pSceneController != nullptr); createUi(); @@ -287,7 +288,8 @@ void nmWxPebiResultControlBar::refreshFromController() m_pDisplayModeCombo, QVariant(static_cast( m_pSceneController->getMeshDisplayMode()))); - if(nDisplayModeIndex >= 0) { + if(nDisplayModeIndex >= 0 && + m_pDisplayModeCombo->currentIndex() != nDisplayModeIndex) { const bool bDisplayModeBlocked = m_pDisplayModeCombo->blockSignals(true); m_pDisplayModeCombo->setCurrentIndex(nDisplayModeIndex); @@ -316,9 +318,7 @@ void nmWxPebiResultControlBar::refreshFromController() m_pPlayPauseButton->setEnabled(false); m_pSettingsButton->setEnabled(false); m_pExportAction->setEnabled(false); - m_pPlayPauseButton->setIcon(QIcon(resultControlIconPath( - QString::fromLatin1("NmStartAnimation.png")))); - m_pPlayPauseButton->setToolTip(tr("Play")); + updatePlayPauseButton(false); return; } @@ -349,9 +349,11 @@ void nmWxPebiResultControlBar::refreshFromController() m_pPlaybackController->getCurrentPropertyId(); const int nPropertyIndex = findItemByData( m_pPropertyCombo, QVariant(sPropertyId)); - const bool bPropertyBlocked = m_pPropertyCombo->blockSignals(true); - m_pPropertyCombo->setCurrentIndex(nPropertyIndex); - m_pPropertyCombo->blockSignals(bPropertyBlocked); + if(m_pPropertyCombo->currentIndex() != nPropertyIndex) { + const bool bPropertyBlocked = m_pPropertyCombo->blockSignals(true); + m_pPropertyCombo->setCurrentIndex(nPropertyIndex); + m_pPropertyCombo->blockSignals(bPropertyBlocked); + } const bool bTimeDependent = m_pPlaybackController->isPropertyTimeDependent(sPropertyId); const int nFrameCount = bTimeDependent @@ -366,11 +368,16 @@ void nmWxPebiResultControlBar::refreshFromController() const int nCurrentFrame = m_pPlaybackController->getCurrentFrameIndex(); if(bTimeDependent) { - const int nTimeIndex = findItemByData( - m_pTimeCombo, QVariant(nCurrentFrame)); - const bool bTimeBlocked = m_pTimeCombo->blockSignals(true); - m_pTimeCombo->setCurrentIndex(nTimeIndex); - m_pTimeCombo->blockSignals(bTimeBlocked); + // rebuildTimeItems 按帧号完整构建,索引与帧号一致,无需每帧线性搜索。 + const int nTimeIndex = nCurrentFrame >= 0 && + nCurrentFrame < m_pTimeCombo->count() && + m_pTimeCombo->itemData(nCurrentFrame) == QVariant(nCurrentFrame) + ? nCurrentFrame : -1; + if(m_pTimeCombo->currentIndex() != nTimeIndex) { + const bool bTimeBlocked = m_pTimeCombo->blockSignals(true); + m_pTimeCombo->setCurrentIndex(nTimeIndex); + m_pTimeCombo->blockSignals(bTimeBlocked); + } } const bool bHasCompleteTimeList = bTimeDependent && @@ -408,9 +415,16 @@ void nmWxPebiResultControlBar::refreshFromController() m_pExportAction->setToolTip(sExportTip); m_pExportAction->setStatusTip(sExportTip); - m_pPlayPauseButton->setIcon(QIcon(resultControlIconPath( - bPlaying ? QString::fromLatin1("NmStopAnimation.png") - : QString::fromLatin1("NmStartAnimation.png")))); + updatePlayPauseButton(bPlaying); +} + +void nmWxPebiResultControlBar::updatePlayPauseButton(bool bPlaying) +{ + if(m_bDisplayedPlaying == bPlaying) { + return; + } + m_bDisplayedPlaying = bPlaying; + m_pPlayPauseButton->setIcon(bPlaying ? m_oPauseIcon : m_oPlayIcon); m_pPlayPauseButton->setToolTip( bPlaying ? tr("Pause") : tr("Play")); } @@ -499,6 +513,9 @@ void nmWxPebiResultControlBar::createUi() m_pPlayPauseButton = createControlButton( this, QString::fromLatin1("NmStartAnimation.png"), tr("Play")); + m_oPlayIcon = m_pPlayPauseButton->icon(); + m_oPauseIcon = QIcon(resultControlIconPath( + QString::fromLatin1("NmStopAnimation.png"))); m_pSettingsButton = createControlButton( this, QString::fromLatin1("NmAnimationSettings.png"), tr("Animation settings"));