perf(nm3d): 优化压力场动画切帧与播放控制

- 复用当前颜色表,跳过不变的色标更新
- 播放速度限制为 1–30 步/秒,默认保持 5
- 缓存播放图标,按帧索引更新时间选择框
- 同步中文速度提示及 TS/QM 文件
develop
lh 11 hours ago
parent 9853aaf56c
commit 7e4cc450d2

Binary file not shown.

@ -3260,7 +3260,7 @@ No valid error was returned; keep the last accepted result and reduce the step.<
<message><source>Result playback is unavailable.</source><translation>结果回放不可用。</translation></message>
<message><source>The selected result property is unavailable.</source><translation>所选结果属性不可用。</translation></message>
<message><source>The selected result frame is unavailable.</source><translation>所选结果帧不可用。</translation></message>
<message><source>Playback speed must be a positive integer.</source><translation>播放速度必须为正整数。</translation></message>
<message><source>Playback speed must be between %1 and %2 steps/second.</source><translation>播放速度必须为 %1 至 %2 步/秒。</translation></message>
</context>
<context>
<name>nmFFmpegVideoWriter</name>

@ -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 已渲染属性和帧的唯一真实来源;本类不拥有。 */

@ -3,6 +3,7 @@
#include "nmSubWxs_global.h"
#include <QIcon>
#include <QStringList>
#include <QWidget>
#include <QtGlobal>
@ -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

@ -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<double>(m_nStepsPerSecond)));
return (1000 + m_nStepsPerSecond - 1) / m_nStepsPerSecond;
}

@ -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<QString, nmPebiResultDisplaySettings> 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)) {

@ -16,8 +16,6 @@
#include <QVBoxLayout>
#include <QtGlobal>
#include <limits.h>
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<int>(
nmPebiResultPlaybackController::MinimumPlaybackSpeed),
nStepsPerSecond));
nStepsPerSecond,
static_cast<int>(
nmPebiResultPlaybackController::MaximumPlaybackSpeed)));
m_pLoopCheck->setChecked(bLoopEnabled);
QFormLayout* pFormLayout = new QFormLayout();

@ -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<int>(
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"));

Loading…
Cancel
Save