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"));