feat(nmNum): 接入独立PEBI结果视图与状态同步

- 新增结果组合窗口,整合渲染场景、播放控制条、显示工具栏和状态提示
- 统一计算完成、成果加载和独立后处理窗口的结果视图安装流程
- 增加结果快照身份及新鲜度通知,输入变化后保留最后成功画面
- 使用绑定序号隔离同ID快照、播放定时器和颜色设置事务
- 支持独立后处理窗口在复用时重新绑定当前DataManager
- 成功操作后清除历史普通错误,并保留过期及绑定错误优先级
- 限制PEBI结果状态修改在Manager所属线程执行
- 移除生产入口对旧动画结果控件的依赖
- 更新中文翻译
feature/nmNum-postprocessing-view-20260910
lh 2 weeks ago
parent b088b31907
commit d62a7241cd

Binary file not shown.

@ -2883,6 +2883,19 @@ Reason: %1</source>
<message><source>Loop enabled</source><translation>启用循环</translation></message>
<message><source>Speed (steps/second)</source><translation>速度(步/秒)</translation></message>
</context>
<context>
<name>nmWxPebiResultViewWidget</name>
<message><source>No result data.</source><translation>暂无结果数据。</translation></message>
<message><source>Result Grid</source><translation>结果网格</translation></message>
<message><source>Color Scale</source><translation>色标</translation></message>
<message><source>Scale Grid</source><translation>比例网格</translation></message>
<message><source>Cell Edges</source><translation>单元边线</translation></message>
<message><source>Well Markers</source><translation>井位标记</translation></message>
<message><source>Well Names</source><translation>井名</translation></message>
<message><source>Unable to display the latest result.</source><translation>无法显示最新结果。</translation></message>
<message><source>Unable to display the latest result: %1</source><translation>无法显示最新结果:%1</translation></message>
<message><source>Current inputs have changed. Showing the last successful result.</source><translation>当前输入已修改,正在显示上一次成功求解的结果。</translation></message>
</context>
<context>
<name>nmWxPebiGridViewWidget</name>
<message><source>No grid data.</source><translation>暂无网格数据。</translation></message>

@ -565,9 +565,15 @@ public:
void markPebiGridBuilt();
/** @brief 仅在后台划分期间输入未变化时登记网格成功。 */
bool markPebiGridBuiltIfCurrent(quint64 nGridInputRevision);
/** @brief 几何或求解井集合变化后,使 PEBI 网格和结果失效。 */
/**
* @brief 几何或求解井集合变化后,使 PEBI 网格和结果失效。
* @note 只能在 Manager 所属线程调用;Release 下越线程请求会被拒绝。
*/
void invalidatePebiGrid();
/** @brief 产量、PVT 等变化后,仅使 PEBI 结果失效。 */
/**
* @brief 产量、PVT 等变化后,仅使 PEBI 结果失效。
* @note 只能在 Manager 所属线程调用;Release 下越线程请求会被拒绝。
*/
void invalidatePebiResults();
/** @brief 判断当前 PEBI 网格是否与最新输入一致。 */
bool isPebiGridValid() const;
@ -786,12 +792,34 @@ public:
bool bSucceeded);
/** @brief 并行结果快照或其结果井选择已经改变。 */
void sigPebiResultSnapshotChanged();
/**
* @brief 结果快照身份、存在性或相对当前输入的新鲜度已经改变。
* @note 当前结果井选择不发送本信号;新结果视图只监听本信号。
*/
void sigPebiResultStateChanged();
private slots:
/** @brief 根据参数面板实际编辑的属性类型,使对应 PEBI 输入版本失效。 */
void onRegisteredAttributeEdited(QString sName, QVariant oValue);
private:
/** @brief 一次 Manager 修改前捕获的可观察结果状态。 */
struct PebiResultStateSnapshot
{
QSharedPointer<const nmPebiResultSnapshot> m_pSnapshot;
QString m_sSnapshotId;
bool m_bCurrent;
};
/** @brief 断言并在 Release 中拒绝越线程的 PEBI 结果状态修改。 */
bool ensurePebiResultOwnerThread(const char* pOperation) const;
/** @brief 在已经确认属于 Manager 的线程中发送结果状态通知。 */
void emitPebiResultStateChangedOnOwnerThread();
/** @brief 捕获快照共享身份、稳定 ID 和相对输入版本的新鲜度。 */
PebiResultStateSnapshot capturePebiResultState() const;
/** @brief 仅在修改前后的可观察结果状态确实不同时发送新状态信号。 */
void notifyPebiResultStateChangedIfNeeded(
const PebiResultStateSnapshot& oPreviousState);
/** @brief 按 UUID 给当前实时井统一绑定或解除最后结果快照弱引用。 */
void rebindPebiResultSnapshotToWells();
/** @brief 在候选 Manager 内完整读取并校验一套 v5 成果。 */

@ -3,16 +3,29 @@
#include "iSubWndBaseFit.h"
#include "nmSubWnd_global.h"
#include <QPointer>
class nmDataDemo;
class nmDataAnalyzeManager;
class nmWxPebiResultViewWidget;
class NM_SUB_WND_EXPORT nmSubWndPostprocessing : public iSubWndBaseFit
{
Q_OBJECT
public:
explicit nmSubWndPostprocessing(QWidget *parent = 0, QString sExt = "");
/** @brief 显式绑定创建窗口时的分析 Manager;空指针创建稳定空结果窗口。 */
explicit nmSubWndPostprocessing(
nmDataAnalyzeManager* pDataManager,
QWidget *parent = 0,
QString sExt = "");
~nmSubWndPostprocessing();
/** @brief 切换独立窗口观察的 Manager,并立即转发给已创建的结果控件。 */
void setDataManager(nmDataAnalyzeManager* pDataManager);
/** @brief 返回当前显式绑定的 Manager;对象销毁后自动返回空指针。 */
nmDataAnalyzeManager* getDataManager() const;
// 配置窗体
virtual void configWnd(QString sPrevInfo);
@ -23,4 +36,10 @@ public:
public:
// 执行命令:通常来自Ribbon
virtual bool runCmdBy(QString sName, QString sID);
private:
/** @brief 独立窗口只观察显式注入的 Manager,不回退到全局当前实例。 */
QPointer<nmDataAnalyzeManager> m_pDataManager;
/** @brief 主界面中的结果组合控件,由 Qt 窗口对象树拥有。 */
QPointer<nmWxPebiResultViewWidget> m_pResultView;
};

@ -133,6 +133,14 @@ public:
QString sRstName, \
const ZxMainWindow* pMainWnd);
/**
* @brief 统一安装或同步 PEBI 三维结果视图和只读结果参数页。
* @note 不要求当前已有快照;空快照会清空两个页面中的旧结果内容。
*/
static bool ensurePebiResultWidgets(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager);
/// @brief 获取缓存的TabWidget指针,确保弹窗与MDI切换使用同一个容器,指针比较才能正确
ZxTabWidget* getTabWidget() const { return m_pTabWx; }

@ -6,6 +6,7 @@
#include <QObject>
#include <QString>
#include <QStringList>
#include <QtGlobal>
class QTimer;
class nmPebiResultSceneController;
@ -37,7 +38,7 @@ public:
virtual ~nmPebiResultPlaybackController();
/**
* @brief 按 Snapshot ID 同步场景状态。
* @brief 按 Scene 成功绑定序号同步场景状态。
*
* 新快照和无快照会停止播放;同快照普通重同步保留有效播放状态。
*/
@ -70,6 +71,8 @@ public:
bool isAvailable() const;
/** @brief 返回控制器最近一次同步到的快照 ID。 */
QString getObservedSnapshotId() const;
/** @brief 返回最近一次普通 Scene 同步观察到的成功绑定序号。 */
quint64 getObservedBindingRevision() const;
/** @brief 直接转发 Scene 中按 Catalog 排序的可用属性 ID。 */
QStringList getAvailablePropertyIds() const;
/** @brief 直接转发 Scene 已成功渲染的当前属性 ID。 */
@ -127,6 +130,10 @@ private:
QTimer* m_pTimer;
/** @brief 最近一次同步成功的 Snapshot ID,用于识别快照替换。 */
QString m_sObservedSnapshotId;
/** @brief 最近一次普通 Scene 同步观察到的成功绑定序号。 */
quint64 m_nObservedBindingRevision;
/** @brief 当前活动 Timer 启动时捕获的 Scene 成功绑定序号。 */
quint64 m_nPlaybackBindingRevision;
/** @brief 同一快照内最后一次成功提交的 pressure 零基帧。 */
int m_nLastPressureFrameIndex;
/** @brief 当前窗口播放速度,单位为步/秒。 */

@ -7,6 +7,7 @@
#include <QSharedPointer>
#include <QString>
#include <QStringList>
#include <QtGlobal>
class nmPebiResultSceneControllerPrivate;
class nmPebiResultSnapshot;
@ -52,11 +53,13 @@ public:
/**
* @brief 事务式提交当前属性的颜色、范围、比例和越界颜色设置。
* @param nExpectedBindingRevision 对话框打开时记录的成功绑定序号。
* @param sExpectedSnapshotId 对话框打开时记录的快照 ID。
* @param sExpectedPropertyId 对话框打开时记录的属性 ID。
* @note 身份或设置校验失败时不修改场景且不 Render;相同设置直接成功。
*/
bool applyCurrentDisplaySettings(
quint64 nExpectedBindingRevision,
const QString& sExpectedSnapshotId,
const QString& sExpectedPropertyId,
const nmPebiResultDisplaySettings& oSettings,
@ -77,6 +80,11 @@ public:
/** @brief 返回当前是否持有可用快照且稳定结果图层仍完整。 */
bool hasSnapshot() const;
/**
* @brief 返回本窗口最近一次成功提交不同快照对象后的单调绑定序号。
* @note 初始值为 0;clear、失败和相同对象重复绑定均不改变该值。
*/
quint64 getSnapshotBindingRevision() const;
/** @brief 返回当前快照稳定 ID;无可用快照时返回空字符串。 */
QString getCurrentSnapshotId() const;
/** @brief 按 Catalog 顺序返回当前快照真实存在的属性 ID。 */

@ -6,6 +6,7 @@
#include <QDialog>
#include <QString>
#include <QtGlobal>
class QCheckBox;
class QComboBox;
@ -18,8 +19,9 @@ class nmPebiResultSceneController;
/**
* @brief Saphir 风格的当前结果属性颜色设置对话框。
*
* 对话框保存打开时的 Snapshot ID 和 Property ID,每次 Apply/OK 都交给
* SceneController 原子复核。Cancel 只丢弃尚未 Apply 的编辑,不回滚已提交状态。
* 对话框保存打开时的绑定序号、Snapshot ID 和 Property ID,每次 Apply/OK
* 都交给 SceneController 原子复核。Cancel 只丢弃尚未 Apply 的编辑,
* 不回滚已经提交的状态。
*/
class NM_SUB_WXS_EXPORT nmWxPebiResultColorScaleSettingsDlg
: public QDialog
@ -90,6 +92,7 @@ private:
void setErrorText(const QString& sError);
nmPebiResultSceneController* m_pSceneController; ///< 非所有权 Scene 真源。
quint64 m_nExpectedBindingRevision; ///< 打开时捕获的 Scene 成功绑定序号。
QString m_sExpectedSnapshotId; ///< 打开时捕获的快照稳定 ID。
QString m_sExpectedPropertyId; ///< 打开时捕获的属性稳定 ID。
QString m_sDisplayUnit; ///< 当前属性默认显示单位,仅用于标签。

@ -5,6 +5,7 @@
#include <QStringList>
#include <QWidget>
#include <QtGlobal>
class QComboBox;
class QToolButton;
@ -42,6 +43,8 @@ public:
nmPebiResultPlaybackController* getPlaybackController() const;
signals:
/** @brief 一次明确的用户操作已经成功提交,供组合窗口清除旧普通错误。 */
void operationSucceeded();
/** @brief 转发回放控制器错误,供后续组合窗口显示非阻塞提示。 */
void operationFailed(const QString& sError);
@ -110,6 +113,8 @@ private:
QToolButton* m_pSettingsButton;
/** @brief 当前属性项对应的 Snapshot ID,仅用于避免重复重建 UI。 */
QString m_sDisplayedSnapshotId;
/** @brief 属性和时间列表最近一次完整展示的 Scene 成功绑定序号。 */
quint64 m_nDisplayedBindingRevision;
/** @brief 当前属性框已展示的有序 ID,仅用于避免重复重建 UI。 */
QStringList m_listDisplayedPropertyIds;
/** @brief 当前时间框对应的动态属性 ID。 */

@ -0,0 +1,134 @@
#ifndef NMWXPEBIRESULTVIEWWIDGET_H
#define NMWXPEBIRESULTVIEWWIDGET_H
#include "nmSubWxs_global.h"
#include <QPointer>
#include <QSharedPointer>
#include <QString>
#include <QWidget>
class QAction;
class QLabel;
class QMenu;
class QToolBar;
class nmDataAnalyzeManager;
class nmPebiResultSceneController;
class nmPebiResultSnapshot;
class nmWxPebiResultControlBar;
class nmWxVTKRenderContainerWidget;
/**
* @brief 独立的 PEBI 结果场景组合窗口。
*
* 本控件显式观察 DataManager,只组合通用渲染容器、结果 SceneController、
* 底部控制条和右侧 QAction。Manager 当前状态与 Scene 最后成功显示状态分别
* 缓存,因此新快照绑定失败时可以继续检查旧画面。
*/
class NM_SUB_WXS_EXPORT nmWxPebiResultViewWidget : public QWidget
{
Q_OBJECT
public:
/**
* @param pDataManager 非所有权对象,可为空;销毁后窗口自动进入空状态。
* @note 非空 Manager 必须与本窗口同属 Qt GUI 线程。
*/
explicit nmWxPebiResultViewWidget(
nmDataAnalyzeManager* pDataManager,
QWidget* pParent = nullptr);
/** @brief 按控制条、SceneController、渲染容器的顺序解除窗口资源。 */
virtual ~nmWxPebiResultViewWidget();
/**
* @brief 切换显式 Manager;相同指针只同步,不清空当前相机和场景。
* @note 跨线程 Manager 会被拒绝,避免同步读取快照引用时发生数据竞争。
*/
void setDataManager(nmDataAnalyzeManager* pDataManager);
/** @brief 返回当前观察的 Manager;对象销毁后自动返回空指针。 */
nmDataAnalyzeManager* getDataManager() const;
/** @brief 合并重入请求并读取 Manager 最新结果状态。 */
void synchronizeFromDataManager();
private slots:
/** @brief 响应快照身份、存在性或新鲜度变化,不响应结果井选择。 */
void onPebiResultStateChanged();
/** @brief Manager 析构时不再读取其成员,直接停止播放并清理场景。 */
void onDataManagerDestroyed(QObject* pObject);
/** @brief 将业务显隐 QAction 追加到通用右键菜单。 */
void appendContextMenuActions(QMenu* pMenu);
/** @brief 转发非阻塞操作错误,但不覆盖绑定失败或过期提示。 */
void onControlOperationFailed(const QString& sError);
/** @brief 明确操作成功后清除旧普通错误,不影响绑定失败或过期状态。 */
void onControlOperationSucceeded();
/** @brief 切换结果网格显隐。 */
void onMeshToggled(bool bVisible);
/** @brief 切换色标显隐。 */
void onScalarBarToggled(bool bVisible);
/** @brief 切换比例网格显隐。 */
void onScaleGridToggled(bool bVisible);
/** @brief 切换单元边线显隐。 */
void onCellEdgesToggled(bool bVisible);
/** @brief 切换二维井位标记显隐。 */
void onWellMarkersToggled(bool bVisible);
/** @brief 切换二维井名显隐。 */
void onWellNamesToggled(bool bVisible);
private:
Q_DISABLE_COPY(nmWxPebiResultViewWidget)
/** @brief 创建唯一渲染容器、SceneController、工具栏、提示栏和控制条。 */
void createUi();
/** @brief 执行一次非重入的 Manager 最新状态读取和事务提交。 */
void synchronizeOnce();
/** @brief 停止播放、清理 Scene,并把所有业务控件同步为空状态。 */
void clearSceneAndControls(const QString& sEmptyMessage);
/** @brief 保存本轮已经观察到的 Manager 快照身份和新鲜度。 */
void rememberObservedState(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
const QString& sSnapshotId,
bool bCurrent);
/** @brief 从 SceneController 镜像六个业务 QAction 的勾选和可用状态。 */
void synchronizeBusinessActions();
/** @brief 根据“绑定失败、过期、操作错误”的固定优先级刷新提示栏。 */
void refreshStatusBar();
/** @brief 断开当前 Manager 的两个连接,不访问已经析构的对象。 */
void disconnectDataManager();
QPointer<nmDataAnalyzeManager> m_pDataManager; ///< 当前显式注入的 Manager。
nmWxVTKRenderContainerWidget* m_pRenderContainer; ///< 唯一通用 VTK 容器。
nmPebiResultSceneController* m_pSceneController; ///< 唯一结果场景控制器。
nmWxPebiResultControlBar* m_pControlBar; ///< 独占回放控制器和 Timer 的底栏。
QToolBar* m_pToolBar; ///< Qt 父对象拥有的右侧竖向工具栏。
QLabel* m_pStatusLabel; ///< 过期或错误的非阻塞提示栏。
QAction* m_pMeshAction; ///< 结果网格显隐动作。
QAction* m_pScalarBarAction; ///< 色标显隐动作。
QAction* m_pScaleGridAction; ///< 比例网格显隐动作。
QAction* m_pCellEdgesAction; ///< 单元边线显隐动作。
QAction* m_pWellMarkersAction; ///< 二维井位标记显隐动作。
QAction* m_pWellNamesAction; ///< 二维井名显隐动作。
/** @brief Manager 最近一次通知后观察到的快照强引用。 */
QSharedPointer<const nmPebiResultSnapshot> m_pObservedSnapshot;
/** @brief Manager 最近一次通知后观察到的稳定 Snapshot ID。 */
QString m_sObservedSnapshotId;
/** @brief 最近观察到的快照是否仍与当前输入版本一致。 */
bool m_bObservedSnapshotCurrent;
/** @brief 是否已经建立过一份可用于去重的 Manager 观察状态。 */
bool m_bObservedStateValid;
/** @brief Scene 最后一次成功绑定的快照强引用,与观察状态相互独立。 */
QSharedPointer<const nmPebiResultSnapshot> m_pDisplayedSnapshot;
/** @brief Scene 最后一次成功绑定的稳定 Snapshot ID。 */
QString m_sDisplayedSnapshotId;
QString m_sBindingError; ///< 最新 Manager 快照绑定失败的高优先级错误。
QString m_sOperationError; ///< 当前控制条普通操作错误。
bool m_bSynchronizing; ///< 防止 VTK 提交期间递归进入同步。
bool m_bSyncPending; ///< 重入时记录一次待处理请求,结束后循环读取最新状态。
};
#endif // NMWXPEBIRESULTVIEWWIDGET_H

@ -1529,6 +1529,9 @@ void nmDataAnalyzeManager::endBackgroundUse()
nmDataWellBase *nmDataAnalyzeManager::createWell(NM_WELL_MODEL eWellType)
{
if(!ensurePebiResultOwnerThread("createWell")) {
return nullptr;
}
// 根据当前Fitting窗口来获取对应的井相关数据
iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting();
nmDataAnalyzeContextProvider* pContextProvider = nmDataAnalyzeContext::provider();
@ -1571,7 +1574,7 @@ nmDataWellBase *nmDataAnalyzeManager::createWell(NM_WELL_MODEL eWellType)
// Map 中增加真实井会改变井约束点和求解器井槽位,旧网格必须立即失效。
m_vWellData.append(pWellData);
m_oNumericalAnalysisCase.invalidateGrid();
invalidatePebiGrid();
emit dataChanged();
return pWellData;
}
@ -1658,6 +1661,9 @@ bool nmDataAnalyzeManager::canRemoveWell(
bool nmDataAnalyzeManager::removeWell(nmDataWellBase* pWellData)
{
if(!ensurePebiResultOwnerThread("removeWell")) {
return false;
}
// 固定主井及非法指针必须在任何版本递增、图元解绑和对象删除前拒绝。
// 该检查是数据层最终兜底,不能只依赖 Map 界面的提前拦截。
if(!canRemoveWell(pWellData)) {
@ -1670,7 +1676,7 @@ bool nmDataAnalyzeManager::removeWell(nmDataWellBase* pWellData)
// 第二步:Map 中删除任意真实井都会改变网格井集合。即使它是自动参与的
// 无产量观察井,也必须让旧网格、旧求解器顺序和旧结果一起失效。
m_oNumericalAnalysisCase.invalidateGrid();
invalidatePebiGrid();
// 第三步:删除井对象前,先从“包含其他井”中移除对应 WellCode。
QVector<nmCalculationWellRef> vecIncludedWells =
@ -2006,6 +2012,9 @@ nmDataWellBase* nmDataAnalyzeManager::findWellByInstanceId(
void nmDataAnalyzeManager::initCurWellData()
{
if(!ensurePebiResultOwnerThread("initCurWellData")) {
return;
}
// 获取当前默认井数据
ZxDataWell* pWellData = zxCurWell;
if(pWellData == nullptr) {
@ -2178,12 +2187,15 @@ void nmDataAnalyzeManager::initCurWellData()
if(m_oNumericalAnalysisCase.getPrimaryWellCode().isEmpty() &&
m_pCurDataWell != nullptr &&
!m_pCurDataWell->getWellCode().isEmpty()) {
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
const QString sPrimaryWellCode = m_pCurDataWell->getWellCode();
m_oNumericalAnalysisCase.setPrimaryWellCode(sPrimaryWellCode);
m_oNumericalAnalysisCase.setPrimaryWellMode(
m_pCurDataWell->getFlowSegmentCount() > 0
? NM_CaseWell_RateControlled
: NM_CaseWell_Observation);
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
}
@ -2428,6 +2440,9 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData)
{
if(!ensurePebiResultOwnerThread("appendNmWellData")) {
return;
}
if (pWellData == nullptr)
return;
bool bWellAdded = false;
@ -2441,7 +2456,7 @@ void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData)
// 使旧网格失效。已由 createWell() 登记的井不重复增加输入版本号。
if(bWellAdded)
{
m_oNumericalAnalysisCase.invalidateGrid();
invalidatePebiGrid();
}
// 通知面板新增井分组
@ -2462,6 +2477,9 @@ void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData)
bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell,
nmDataWellBase* pNewWell)
{
if(!ensurePebiResultOwnerThread("replaceWellData")) {
return false;
}
// 先完成所有无副作用的前置校验,失败时保持双方所有权不变。
if (pOldWell == nullptr || pNewWell == nullptr ||
pOldWell == pNewWell || m_vWellData.contains(pNewWell) ||
@ -2495,7 +2513,7 @@ bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell,
}
m_vWellData.insert(qMin(nOldIndex, m_vWellData.size()), pNewWell);
// 井型改变会改变 PEBI 几何输入,旧求解器顺序和网格必须整体失效。
m_oNumericalAnalysisCase.invalidateGrid();
invalidatePebiGrid();
if (bWasCurrentWell)
{
m_pCurDataWell = pNewWell;
@ -3894,11 +3912,14 @@ NM_Grid_Type nmDataAnalyzeManager::getGridType()
void nmDataAnalyzeManager::setGridType(NM_Grid_Type newGridType)
{
if(!ensurePebiResultOwnerThread("setGridType")) {
return;
}
if(m_eGridType == newGridType) {
return;
}
m_eGridType = newGridType;
m_oNumericalAnalysisCase.invalidateGrid();
invalidatePebiGrid();
}
NM_SOLVER_MODEL_TYPE nmDataAnalyzeManager::getSolverModelType() const
@ -3908,11 +3929,14 @@ NM_SOLVER_MODEL_TYPE nmDataAnalyzeManager::getSolverModelType() const
void nmDataAnalyzeManager::setSolverModelType(NM_SOLVER_MODEL_TYPE newSolverModelType)
{
if(!ensurePebiResultOwnerThread("setSolverModelType")) {
return;
}
if(m_eSolverModelType == newSolverModelType) {
return;
}
m_eSolverModelType = newSolverModelType;
m_oNumericalAnalysisCase.invalidateResults();
invalidatePebiResults();
}
int nmDataAnalyzeManager::getPebiSolverType() const
@ -3922,11 +3946,14 @@ int nmDataAnalyzeManager::getPebiSolverType() const
void nmDataAnalyzeManager::setPebiSolverType(int nSolverType)
{
if(!ensurePebiResultOwnerThread("setPebiSolverType")) {
return;
}
if((nSolverType == PebiSolverCpuAccelerated ||
nSolverType == PebiSolverOriginal) &&
m_nPebiSolverType != nSolverType) {
m_nPebiSolverType = nSolverType;
m_oNumericalAnalysisCase.invalidateResults();
invalidatePebiResults();
}
}
@ -3937,11 +3964,14 @@ int nmDataAnalyzeManager::getPebiOmpThreads() const
void nmDataAnalyzeManager::setPebiOmpThreads(int nOmpThreads)
{
if(!ensurePebiResultOwnerThread("setPebiOmpThreads")) {
return;
}
if(nOmpThreads == 1 || nOmpThreads == 2 || nOmpThreads == 4
|| nOmpThreads == 8 || nOmpThreads == 16) {
if(m_nPebiOmpThreads != nOmpThreads) {
m_nPebiOmpThreads = nOmpThreads;
m_oNumericalAnalysisCase.invalidateResults();
invalidatePebiResults();
}
}
}
@ -3953,10 +3983,13 @@ int nmDataAnalyzeManager::getPebiIluReuseSteps() const
void nmDataAnalyzeManager::setPebiIluReuseSteps(int nIluReuseSteps)
{
if(!ensurePebiResultOwnerThread("setPebiIluReuseSteps")) {
return;
}
if(nIluReuseSteps >= 1 && nIluReuseSteps <= 100 &&
m_nPebiIluReuseSteps != nIluReuseSteps) {
m_nPebiIluReuseSteps = nIluReuseSteps;
m_oNumericalAnalysisCase.invalidateResults();
invalidatePebiResults();
}
}
@ -4161,9 +4194,12 @@ nmDataLayer* nmDataAnalyzeManager::getLayerData()
void nmDataAnalyzeManager::setLayers(QVector<nmDataLayer*> vecLayers)
{
if(!ensurePebiResultOwnerThread("setLayers")) {
return;
}
m_vecLayers = vecLayers;
// 几何分层会改变储层离散输入,旧网格和旧结果不再有效。
m_oNumericalAnalysisCase.invalidateGrid();
invalidatePebiGrid();
emit dataChanged();
}
@ -4311,16 +4347,22 @@ nmDataAnalyzeManager::getNumericalAnalysisCase() const
void nmDataAnalyzeManager::setPrimaryWellCode(const QString& sWellCode)
{
if(!ensurePebiResultOwnerThread("setPrimaryWellCode")) {
return;
}
if(!sWellCode.isEmpty() && findWellByCode(sWellCode) == nullptr) {
return;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
const quint64 nOldRevision =
m_oNumericalAnalysisCase.getGridInputRevision();
m_oNumericalAnalysisCase.setPrimaryWellCode(sWellCode);
if(m_oNumericalAnalysisCase.getGridInputRevision() != nOldRevision) {
emit dataChanged();
}
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
QString nmDataAnalyzeManager::getPrimaryWellCode() const
@ -4330,12 +4372,18 @@ QString nmDataAnalyzeManager::getPrimaryWellCode() const
void nmDataAnalyzeManager::setIncludeOtherWells(bool bInclude)
{
if(!ensurePebiResultOwnerThread("setIncludeOtherWells")) {
return;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
const bool bOldInclude = m_oNumericalAnalysisCase.getIncludeOtherWells();
m_oNumericalAnalysisCase.setIncludeOtherWells(bInclude);
if(bOldInclude != m_oNumericalAnalysisCase.getIncludeOtherWells()) {
// 开关只改变当前求解输入。最后结果下拉框继续使用快照冻结的 UUID 列表。
emit dataChanged();
}
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
bool nmDataAnalyzeManager::getIncludeOtherWells() const
@ -4345,6 +4393,11 @@ bool nmDataAnalyzeManager::getIncludeOtherWells() const
void nmDataAnalyzeManager::setPebiGridControl(double dGridControl)
{
if(!ensurePebiResultOwnerThread("setPebiGridControl")) {
return;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
const quint64 nOldGridInputRevision =
m_oNumericalAnalysisCase.getGridInputRevision();
m_oNumericalAnalysisCase.setPebiGridControl(dGridControl);
@ -4353,6 +4406,7 @@ void nmDataAnalyzeManager::setPebiGridControl(double dGridControl)
// 分析方案已完成网格和结果失效,这里只通知已打开的网格窗口刷新。
emit dataChanged();
}
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
double nmDataAnalyzeManager::getPebiGridControl() const
@ -4363,6 +4417,11 @@ double nmDataAnalyzeManager::getPebiGridControl() const
void nmDataAnalyzeManager::setIncludedCalculationWells(
const QVector<nmCalculationWellRef>& vecWells)
{
if(!ensurePebiResultOwnerThread("setIncludedCalculationWells")) {
return;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
const QVector<nmCalculationWellRef> vecOldWells =
m_oNumericalAnalysisCase.getIncludedWells();
QVector<nmCalculationWellRef> vecValidWells;
@ -4399,6 +4458,7 @@ void nmDataAnalyzeManager::setIncludedCalculationWells(
// 第三步:这里只通知当前输入变化,不修改独立快照中的结果井选择。
emit dataChanged();
}
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
QVector<nmCalculationWellRef>
@ -4610,12 +4670,24 @@ bool nmDataAnalyzeManager::markPebiGridBuiltIfCurrent(
void nmDataAnalyzeManager::invalidatePebiGrid()
{
if(!ensurePebiResultOwnerThread("invalidatePebiGrid")) {
return;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
m_oNumericalAnalysisCase.invalidateGrid();
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
void nmDataAnalyzeManager::invalidatePebiResults()
{
if(!ensurePebiResultOwnerThread("invalidatePebiResults")) {
return;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
m_oNumericalAnalysisCase.invalidateResults();
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
bool nmDataAnalyzeManager::isPebiGridValid() const
@ -4649,6 +4721,59 @@ bool nmDataAnalyzeManager::isPebiResultSnapshotCurrent() const
m_oNumericalAnalysisCase.getResultInputRevision();
}
nmDataAnalyzeManager::PebiResultStateSnapshot
nmDataAnalyzeManager::capturePebiResultState() const
{
PebiResultStateSnapshot oState;
oState.m_bCurrent = false;
if(!ensurePebiResultOwnerThread("capturePebiResultState")) {
return oState;
}
oState.m_pSnapshot = m_pPebiResultSnapshot;
oState.m_sSnapshotId = m_pPebiResultSnapshot.isNull()
? QString() : m_pPebiResultSnapshot->getSnapshotId();
oState.m_bCurrent = isPebiResultSnapshotCurrent();
return oState;
}
void nmDataAnalyzeManager::notifyPebiResultStateChangedIfNeeded(
const PebiResultStateSnapshot& oPreviousState)
{
if(!ensurePebiResultOwnerThread(
"notifyPebiResultStateChangedIfNeeded")) {
return;
}
const PebiResultStateSnapshot oCurrentState =
capturePebiResultState();
const bool bSnapshotIdentityChanged =
oPreviousState.m_pSnapshot.data() !=
oCurrentState.m_pSnapshot.data() ||
oPreviousState.m_sSnapshotId != oCurrentState.m_sSnapshotId;
if(bSnapshotIdentityChanged ||
oPreviousState.m_bCurrent != oCurrentState.m_bCurrent) {
emitPebiResultStateChangedOnOwnerThread();
}
}
bool nmDataAnalyzeManager::ensurePebiResultOwnerThread(
const char* pOperation) const
{
const bool bOwnerThread = QThread::currentThread() == thread();
Q_ASSERT_X(bOwnerThread, "nmDataAnalyzeManager", pOperation);
if(!bOwnerThread) {
// 结果快照引用和输入版本均未加锁;Release 也必须在首次读取前拒绝。
qWarning() << "Rejected cross-thread PEBI result state mutation:"
<< pOperation;
}
return bOwnerThread;
}
void nmDataAnalyzeManager::emitPebiResultStateChangedOnOwnerThread()
{
Q_ASSERT(QThread::currentThread() == thread());
emit sigPebiResultStateChanged();
}
QString nmDataAnalyzeManager::getCurrentResultWellInstanceId() const
{
return m_sCurrentResultWellInstanceId;
@ -4806,12 +4931,15 @@ bool nmDataAnalyzeManager::commitPebiResultSnapshot(
return false;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
// 发布点只替换只读引用;先前快照由仍在查看它的窗口延迟释放。
m_pPebiResultSnapshot = pPublishedCandidate;
m_sCurrentResultWellInstanceId = sNextWellInstanceId;
pCandidate.clear();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
return true;
}
@ -4843,16 +4971,22 @@ void nmDataAnalyzeManager::rebindPebiResultSnapshotToWells()
void nmDataAnalyzeManager::clearPebiResultSnapshot()
{
if(!ensurePebiResultOwnerThread("clearPebiResultSnapshot")) {
return;
}
if(m_pPebiResultSnapshot.isNull() &&
m_sCurrentResultWellInstanceId.isEmpty())
{
return;
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
m_pPebiResultSnapshot.clear();
m_sCurrentResultWellInstanceId.clear();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
}
// 设置当前查看井
@ -4869,9 +5003,12 @@ nmDataWellBase* nmDataAnalyzeManager::getCurWellData()
void nmDataAnalyzeManager::notifyDataChanged()
{
if(!ensurePebiResultOwnerThread("notifyDataChanged")) {
return;
}
// 本入口用于几何、井集合等网格输入;仅影响求解的参数应调用
// invalidatePebiResults(),避免无意义地重建当前有效网格。
m_oNumericalAnalysisCase.invalidateGrid();
invalidatePebiGrid();
emit dataChanged();
}
@ -4880,6 +5017,9 @@ void nmDataAnalyzeManager::onRegisteredAttributeEdited(
QString sName,
QVariant oValue)
{
if(!ensurePebiResultOwnerThread("onRegisteredAttributeEdited")) {
return;
}
Q_UNUSED(oValue);
if(isPebiGridInputAttributeName(sName)) {
@ -4894,7 +5034,10 @@ void nmDataAnalyzeManager::onRegisteredAttributeEdited(
void nmDataAnalyzeManager::notifyGeometryListChanged()
{
m_oNumericalAnalysisCase.invalidateGrid();
if(!ensurePebiResultOwnerThread("notifyGeometryListChanged")) {
return;
}
invalidatePebiGrid();
emit sigGeometryListChanged();
emit dataChanged();
}
@ -5206,9 +5349,13 @@ QVector<nmPropertyInterpolationDataSet> nmDataAnalyzeManager::getPropertyInterpo
void nmDataAnalyzeManager::setPropertyInterpolationDataSets(
const QVector<nmPropertyInterpolationDataSet>& dataSets)
{
if(!ensurePebiResultOwnerThread(
"setPropertyInterpolationDataSets")) {
return;
}
m_vecPropertyInterpolationDataSets = dataSets;
// 属性插值只覆盖求解器单元属性,不改变二维 PEBI 拓扑。
m_oNumericalAnalysisCase.invalidateResults();
invalidatePebiResults();
}
// 从 JSON 文件读取数据到 C++ 对象
@ -6642,6 +6789,9 @@ bool nmDataAnalyzeManager::validateNmResultV3Window(
bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
{
if(!ensurePebiResultOwnerThread("loadNmResult")) {
return false;
}
try {
// v5 先在独立候选对象上完成全部解析和业务校验。
// 任一步失败时只析构候选,当前窗口已经发布的数据保持不变。
@ -6660,10 +6810,13 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
}
}
const PebiResultStateSnapshot oPreviousResultState =
capturePebiResultState();
swapLoadedProjectState(oLoadedManager);
rebuildLoadedAttributeRegistry();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
notifyPebiResultStateChangedIfNeeded(oPreviousResultState);
return true;
}
catch(const std::bad_alloc&) {

@ -37,6 +37,7 @@
#include "nmSubWndMain.h"
#include "nmPlotDialogContext.h"
#include "nmPlotDialogContextProvider.h"
#include "nmSubWndUtils.h"
#include "nmWxGridVTKContainerWidget.h"
#include "nmWxSelectWellsDlg.h"
@ -60,7 +61,6 @@
#include "nmReservoirPropertiesDataSource.h"
#include "nmWxReservoirPropertiesEditor.h"
#include "nmWxPropertyInterpolationDlg.h"
#include "nmWxPostprocessingAnimationWidget.h"
#include "nmWxGeoRefDlg.h"
#include "iSubWndFitting.h"
@ -2070,77 +2070,8 @@ bool nmSubWndMain::populateResultWidgets(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager)
{
if(pSubWndFit == nullptr || pDataManager == nullptr ||
pDataManager->getGridType() != NM_Grid_PEBI ||
nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) != pDataManager) {
return false;
}
QString sErrorMessage;
QWidget* p3DWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Nm3D, true, &sErrorMessage);
if(p3DWidget == nullptr) {
return false;
}
nmWxPostprocessingAnimationWidget* pVtkWidget =
p3DWidget->findChild<nmWxPostprocessingAnimationWidget*>();
if(pVtkWidget != nullptr) {
// 复用既有渲染窗口和 VTK 管道,只切换本轮已整体提交的数据。
pVtkWidget->refreshResult();
} else {
pVtkWidget = new nmWxPostprocessingAnimationWidget(NULL, pDataManager);
QLayout* p3DLayout = p3DWidget->layout();
if(p3DLayout == nullptr) {
p3DLayout = new QVBoxLayout(p3DWidget);
} else {
QLayoutItem* pItem = nullptr;
while((pItem = p3DLayout->takeAt(0)) != nullptr) {
if(pItem->widget() != nullptr) {
delete pItem->widget();
}
delete pItem;
}
}
p3DLayout->addWidget(pVtkWidget);
}
// 结果参数界面
QWidget* pResultPage = pSubWndFit->getFitSubRstWxOf(
FSRT_NmRst, true, &sErrorMessage);
if(pResultPage == nullptr) {
return false;
}
nmWxParaPropertyPebi* pResultWidget =
pResultPage->findChild<nmWxParaPropertyPebi*>();
if(pResultWidget == nullptr) {
// 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。
pResultWidget = new nmWxParaPropertyPebi;
pResultWidget->initUI();
pResultWidget->setDataManager(pDataManager);
// 保持 8 月 24 日版本的稳定顺序:离屏完成参数构建后再加入可见布局。
pResultWidget->rebuildResultParas();
QLayout* pResultLayout = pResultPage->layout();
if(pResultLayout == nullptr) {
pResultLayout = new QVBoxLayout(pResultPage);
} else {
QLayoutItem* pItem = nullptr;
while((pItem = pResultLayout->takeAt(0)) != nullptr) {
if(pItem->widget() != nullptr) {
delete pItem->widget();
}
delete pItem;
}
}
pResultLayout->addWidget(pResultWidget);
return true;
}
pResultWidget->setDataManager(pDataManager);
pResultWidget->rebuildResultParas();
return true;
return nmSubWndUtils::ensurePebiResultWidgets(
pSubWndFit, pDataManager);
}
void nmSubWndMain::schedulePendingResultWidgets()

@ -32,19 +32,24 @@
#endif
#include "nmDataDemo.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataLogFile.h"
#include "nmGuiPlot.h"
#include "nmSubWndPostprocessing.h"
#include "nmWxPostprocessingWidget.h"
#include "nmWxPostprocessingAnimationWidget.h"
#include "nmWxPebiResultViewWidget.h"
#include <QVector>
#include <QProcess>
nmSubWndPostprocessing::nmSubWndPostprocessing(QWidget *parent, QString sExt) :
iSubWndBaseFit(parent, sExt)
nmSubWndPostprocessing::nmSubWndPostprocessing(
nmDataAnalyzeManager* pDataManager,
QWidget *parent,
QString sExt) :
iSubWndBaseFit(parent, sExt),
m_pDataManager(pDataManager),
m_pResultView(nullptr)
{
m_sMdiWndType = s_MdiType_DataAnal;
setWindowTitle(tr("nmSubWndPostprocessing"));
@ -56,6 +61,21 @@ nmSubWndPostprocessing::~nmSubWndPostprocessing()
#endif
}
void nmSubWndPostprocessing::setDataManager(
nmDataAnalyzeManager* pDataManager)
{
m_pDataManager = pDataManager;
if(!m_pResultView.isNull()) {
// 已有独立窗口复用同一个渲染基础设施,只切换其显式数据来源。
m_pResultView->setDataManager(pDataManager);
}
}
nmDataAnalyzeManager* nmSubWndPostprocessing::getDataManager() const
{
return m_pDataManager.data();
}
void nmSubWndPostprocessing::configWnd(QString sPrevInfo)
{
iSubWndBaseFit::configWnd(sPrevInfo);
@ -90,13 +110,10 @@ void nmSubWndPostprocessing::initUI()
QWidget* nmSubWndPostprocessing::initUiMainWx()
{
// 渲染
//QString sDir = ZxBaseUtil::getCurWellDirOf("Nm/Solver");
//QString sFile = sDir + "oilPost.vtk";
// nmWxPostprocessingWidget* vtkWidget = new nmWxPostprocessingWidget(NULL);
// vtkWidget->loadVTKFile(sFile);
nmWxPostprocessingAnimationWidget* vtkWidget = new nmWxPostprocessingAnimationWidget(NULL);
return vtkWidget;
// 业务窗口只组合新结果视图,Manager 为空时由控件显示稳定空状态。
m_pResultView = new nmWxPebiResultViewWidget(
m_pDataManager.data(), NULL);
return m_pResultView.data();
}
bool nmSubWndPostprocessing::runCmdBy(QString sName, QString sID)

@ -3,9 +3,12 @@
#include <QEvent>
#include <QDir>
#include <QFileInfo>
#include <QLayout>
#include <QLayoutItem>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QTimer>
#include <QVBoxLayout>
#include "zxSysUtils.h"
#include "ZxUiBase.h"
@ -36,7 +39,7 @@
#include "nmWxGridDlg.h"
#include "nmWxPostprocessingAnimationWidget.h"
#include "nmWxPebiResultViewWidget.h"
#include "nmGuiPlot.h"
#include "ZxObjBase.h"
@ -162,6 +165,17 @@ iSubWnd* nmSubWndUtils::makesureSubWnd(QString sID, QString sExt, \
{
bCreatedNewOne = false;
// 独立后处理窗口允许先以空 Manager 创建。再次点击入口时,
// 必须把当前分析显式转发给既有窗口,不能沿用旧绑定。
if(_isSame(sID, "5116")) {
nmSubWndPostprocessing* pPostprocessing =
dynamic_cast<nmSubWndPostprocessing*>(pSubWnd);
if(pPostprocessing != nullptr) {
pPostprocessing->setDataManager(
nmDataAnalyzeManager::getCurrentInstance());
}
}
// TODO 根据需要,是否需要激活当前
if (i != pTabWx->currentIndex())
{
@ -235,7 +249,11 @@ iSubWnd* nmSubWndUtils::makesureSubWnd(QString sID, QString sExt, \
pSubWnd = pSubWndGrid;
} else if(nID == 5116) {
// 后处理
nmSubWndPostprocessing* pSubWndPostProcessing = new nmSubWndPostprocessing(NULL, sExt);
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
// 没有当前分析时仍创建空窗口,后续可通过显式 setDataManager() 绑定。
nmSubWndPostprocessing* pSubWndPostProcessing =
new nmSubWndPostprocessing(pDataManager, NULL, sExt);
pSubWnd = pSubWndPostProcessing;
}
@ -447,6 +465,86 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
pWxDockNm2->setWidget(pParaWx);
}
bool nmSubWndUtils::ensurePebiResultWidgets(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager)
{
if(pSubWndFit == nullptr || pDataManager == nullptr ||
pDataManager->getGridType() != NM_Grid_PEBI ||
nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) !=
pDataManager) {
return false;
}
QString sErrorMessage;
QWidget* p3DPage = pSubWndFit->getFitSubRstWxOf(
FSRT_Nm3D, true, &sErrorMessage);
QWidget* pResultPage = pSubWndFit->getFitSubRstWxOf(
FSRT_NmRst, true, &sErrorMessage);
if(p3DPage == nullptr || pResultPage == nullptr) {
return false;
}
nmWxPebiResultViewWidget* pResultView =
p3DPage->findChild<nmWxPebiResultViewWidget*>();
if(pResultView == nullptr) {
// 首次安装时复用载体已有布局,只移除旧业务控件和布局项。
QLayout* p3DLayout = p3DPage->layout();
if(p3DLayout == nullptr) {
QVBoxLayout* pNewLayout = new QVBoxLayout(p3DPage);
pNewLayout->setContentsMargins(0, 0, 0, 0);
pNewLayout->setSpacing(0);
p3DLayout = pNewLayout;
} else {
QLayoutItem* pItem = nullptr;
while((pItem = p3DLayout->takeAt(0)) != nullptr) {
if(pItem->widget() != nullptr) {
delete pItem->widget();
}
delete pItem;
}
}
pResultView = new nmWxPebiResultViewWidget(
pDataManager, p3DPage);
p3DLayout->addWidget(pResultView);
} else {
// 相同 Manager 只会同步;不同 Manager 才由组合窗口完整清场再绑定。
pResultView->setDataManager(pDataManager);
}
nmWxParaPropertyPebi* pResultParameters =
pResultPage->findChild<nmWxParaPropertyPebi*>();
if(pResultParameters == nullptr) {
QLayout* pResultLayout = pResultPage->layout();
if(pResultLayout == nullptr) {
QVBoxLayout* pNewLayout = new QVBoxLayout(pResultPage);
pNewLayout->setContentsMargins(0, 0, 0, 0);
pNewLayout->setSpacing(0);
pResultLayout = pNewLayout;
} else {
QLayoutItem* pItem = nullptr;
while((pItem = pResultLayout->takeAt(0)) != nullptr) {
if(pItem->widget() != nullptr) {
delete pItem->widget();
}
delete pItem;
}
}
// 保持既有稳定顺序:先在可见布局之外完成只读参数构建,再挂入页面。
pResultParameters = new nmWxParaPropertyPebi();
pResultParameters->initUI();
pResultParameters->setDataManager(pDataManager);
pResultParameters->rebuildResultParas();
pResultLayout->addWidget(pResultParameters);
return true;
}
// 结果参数页没有监听快照状态信号,每次生产入口同步都必须显式重建。
pResultParameters->setDataManager(pDataManager);
pResultParameters->rebuildResultParas();
return true;
}
bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd)
{
iSubWndFitting* pSubWndF = dynamic_cast<iSubWndFitting*>(pSubWnd);
@ -1062,113 +1160,19 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
return false;
}
if(pWxDockNm1->widget() != nullptr &&
pWxDockNm2->widget() != nullptr) {
return true; // 与 develop 一致,已有数值面板不重复创建。
if(pWxDockNm1->widget() == nullptr ||
pWxDockNm2->widget() == nullptr) {
// 缺失左侧数值面板时先完成 Dock 装配,再统一维护两个结果页面。
nmSubWndMain* pSubWndMain = new nmSubWndMain(NULL, "");
ZxMainWindow* pMainWnd1 = const_cast<ZxMainWindow*>(pMainWnd);
pSubWndMain->setMainWindow(pMainWnd1);
setupNumericalDockWidgets(pDataManager, pSubWndMain,
pWxDockNm1, pWxDockNm2);
}
// 创建Map,暂时
nmSubWndMain* pSubWndMain = new nmSubWndMain(NULL, "");
ZxMainWindow* pMainWnd1 = const_cast<ZxMainWindow*>(pMainWnd);
pSubWndMain->setMainWindow(pMainWnd1);
setupNumericalDockWidgets(pDataManager, pSubWndMain,
pWxDockNm1, pWxDockNm2);
// v3 场图只以独立快照是否存在为准,不再依赖旧时间步 Map。
if(pDataManager->hasPebiResultSnapshot()) {
QString sErrorMessage;
// 刷新场图
QWidget* p3DWidget = pSubWndF->getFitSubRstWxOf(FSRT_Nm3D, true, &sErrorMessage);
// 创建 vtkWidget
nmWxPostprocessingAnimationWidget* pVtkWidget = nullptr;
pVtkWidget = new nmWxPostprocessingAnimationWidget(
nullptr, pDataManager);
if(p3DWidget == nullptr || pVtkWidget == nullptr) {
delete pVtkWidget;
return false;
}
// 先清理
// 获取当前布局
QLayout* p3DLayout = p3DWidget->layout();
if(p3DLayout) {
// 移除布局中的所有控件
QLayoutItem* item;
while((item = p3DLayout->takeAt(0))) {
if(item->widget()) {
// 删除控件
delete item->widget();
}
delete item; // 删除布局项
}
// 删除布局
delete p3DLayout;
}
// 确保布局被移除
p3DWidget->setLayout(nullptr);
// 创建一个垂直布局管理器
QVBoxLayout* p3DnewLayout = new QVBoxLayout(p3DWidget);
// 将现有的控件添加到布局中
p3DnewLayout->addWidget(pVtkWidget);
// 设置 widget 的布局
p3DWidget->setLayout(p3DnewLayout);
// 刷新结果参数
// 结果参数界面
QWidget* pRstWidget = pSubWndF->getFitSubRstWxOf(FSRT_NmRst, true, &sErrorMessage);
if(pRstWidget == nullptr) {
return false;
}
// 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。
nmWxParaPropertyPebi* pResultWidget = new nmWxParaPropertyPebi();
pResultWidget->initUI();
pResultWidget->setDataManager(pDataManager);
pResultWidget->rebuildResultParas();
// 先清理
// 获取当前布局
QLayout* pRstLayout = pRstWidget->layout();
if(pRstLayout) {
// 移除布局中的所有控件
QLayoutItem* item;
while((item = pRstLayout->takeAt(0))) {
if(item->widget()) {
// 删除控件
delete item->widget();
}
delete item; // 删除布局项
}
// 删除布局
delete pRstLayout;
}
// 确保布局被移除
pRstWidget->setLayout(nullptr);
// 创建一个垂直布局管理器
QVBoxLayout* pRstnewLayout = new QVBoxLayout(pRstWidget);
// 将现有的控件添加到布局中
pRstnewLayout->addWidget(pResultWidget);
// 设置 widget 的布局
pRstWidget->setLayout(pRstnewLayout);
// 即使两个 Dock 已存在,也必须刷新三维结果和只读结果参数页。
if(!ensurePebiResultWidgets(pSubWndF, pDataManager)) {
return false;
}
}

@ -43,6 +43,8 @@ nmPebiResultPlaybackController::nmPebiResultPlaybackController(
: QObject(pParent),
m_pSceneController(pSceneController),
m_pTimer(new QTimer(this)),
m_nObservedBindingRevision(0),
m_nPlaybackBindingRevision(0),
m_nLastPressureFrameIndex(0),
m_nStepsPerSecond(DefaultPlaybackSpeed),
m_bLoopEnabled(false),
@ -62,10 +64,11 @@ nmPebiResultPlaybackController::~nmPebiResultPlaybackController()
void nmPebiResultPlaybackController::synchronizeFromScene()
{
const QString sPreviousSnapshotId = m_sObservedSnapshotId;
const quint64 nPreviousBindingRevision =
m_nObservedBindingRevision;
const bool bAvailable = synchronizeSceneIdentity();
const bool bSnapshotChanged = bAvailable &&
sPreviousSnapshotId != m_sObservedSnapshotId;
nPreviousBindingRevision != m_nObservedBindingRevision;
// 无快照或新快照必须终止旧回放;同快照同步只校正 Scene 已提交状态。
if(!bAvailable || bSnapshotChanged) {
@ -213,6 +216,16 @@ bool nmPebiResultPlaybackController::togglePlayback()
}
}
const quint64 nBindingRevision =
m_pSceneController->getSnapshotBindingRevision();
if(nBindingRevision == 0 ||
nBindingRevision != m_nObservedBindingRevision) {
return finishFailedOperation(playbackText(
"Result playback is unavailable."));
}
// Timer 归属只在全部校验和末帧复位成功后发布,防止旧回调推进新绑定。
m_nPlaybackBindingRevision = nBindingRevision;
m_pTimer->setInterval(calculatePlaybackInterval());
m_pTimer->start();
m_bPlaying = true;
@ -263,6 +276,11 @@ QString nmPebiResultPlaybackController::getObservedSnapshotId() const
return m_sObservedSnapshotId;
}
quint64 nmPebiResultPlaybackController::getObservedBindingRevision() const
{
return m_nObservedBindingRevision;
}
QStringList nmPebiResultPlaybackController::getAvailablePropertyIds() const
{
return isAvailable()
@ -342,14 +360,23 @@ bool nmPebiResultPlaybackController::isPlaying() const
void nmPebiResultPlaybackController::onPlaybackTimeout()
{
const QString sPreviousSnapshotId = m_sObservedSnapshotId;
if(!synchronizeSceneIdentity()) {
// 先验证 Timer 启动时捕获的绑定,已经排队的旧事件不得推进后来绑定的快照。
const bool bTimerOwnsCurrentBinding =
m_bPlaying && m_pTimer != nullptr && m_pTimer->isActive() &&
m_pSceneController != nullptr &&
m_pSceneController->hasSnapshot() &&
m_nPlaybackBindingRevision != 0 &&
m_pSceneController->getSnapshotBindingRevision() ==
m_nPlaybackBindingRevision;
if(!bTimerOwnsCurrentBinding) {
stopTimerWithoutSignal();
emit stateChanged();
return;
}
if(sPreviousSnapshotId != m_sObservedSnapshotId) {
// 旧 Timer 已排队的回调不得推进刚提交的新快照。
// Timer 归属通过后再做普通身份同步;任何不一致都只停止,不提交下一帧。
if(!synchronizeSceneIdentity() || !m_bPlaying ||
m_nPlaybackBindingRevision == 0) {
stopTimerWithoutSignal();
emit stateChanged();
return;
@ -400,6 +427,7 @@ bool nmPebiResultPlaybackController::stopTimerWithoutSignal()
m_pTimer->stop();
}
m_bPlaying = false;
m_nPlaybackBindingRevision = 0;
return bWasPlaying;
}
@ -409,22 +437,28 @@ bool nmPebiResultPlaybackController::synchronizeSceneIdentity()
!m_pSceneController->hasSnapshot()) {
stopTimerWithoutSignal();
m_sObservedSnapshotId.clear();
m_nObservedBindingRevision = 0;
m_nLastPressureFrameIndex = 0;
return false;
}
const QString sSnapshotId =
m_pSceneController->getCurrentSnapshotId();
if(sSnapshotId.isEmpty()) {
const quint64 nBindingRevision =
m_pSceneController->getSnapshotBindingRevision();
if(sSnapshotId.isEmpty() || nBindingRevision == 0) {
stopTimerWithoutSignal();
m_sObservedSnapshotId.clear();
m_nObservedBindingRevision = 0;
m_nLastPressureFrameIndex = 0;
return false;
}
if(m_sObservedSnapshotId != sSnapshotId) {
if(m_nObservedBindingRevision != nBindingRevision ||
m_sObservedSnapshotId != sSnapshotId) {
stopTimerWithoutSignal();
m_sObservedSnapshotId = sSnapshotId;
m_nObservedBindingRevision = nBindingRevision;
// 新快照由 SceneController 固定提交压力第 0 帧;无压力分支仍从 0 开始。
m_nLastPressureFrameIndex =
isPressureProperty(getCurrentPropertyId()) &&

@ -398,6 +398,7 @@ public:
m_pWellLayer(nullptr),
m_pAdapter(nullptr),
m_nCurrentFrameIndex(-1),
m_nSnapshotBindingRevision(0),
m_bHasPropertyRange(false),
m_bHasBounds(false),
m_bMeshVisible(true),
@ -874,6 +875,8 @@ public:
/** @brief 按稳定属性 ID 保存的窗口级样式;clear 不删除。 */
QMap<QString, nmPebiResultDisplaySettings> m_mapDisplaySettings;
int m_nCurrentFrameIndex;
/** @brief 不同快照对象完整提交成功后的单调序号,clear 和失败均不回退。 */
quint64 m_nSnapshotBindingRevision;
double m_dBusinessRange[2];
double m_dDisplayRange[2];
double m_dBounds[6];
@ -1123,6 +1126,8 @@ bool nmPebiResultSceneController::setSnapshot(
Q_UNUSED(bCenterReset);
// showReadyState 是本提交路径唯一的 Render 入口。
m_pPrivate->m_pContainer->showReadyState();
// 候选管线、相机和首次 Render 已完整提交,最后才发布新绑定序号。
++m_pPrivate->m_nSnapshotBindingRevision;
return true;
}
@ -1239,6 +1244,7 @@ bool nmPebiResultSceneController::applyPropertyFrame(
}
bool nmPebiResultSceneController::applyCurrentDisplaySettings(
quint64 nExpectedBindingRevision,
const QString& sExpectedSnapshotId,
const QString& sExpectedPropertyId,
const nmPebiResultDisplaySettings& oSettings,
@ -1254,6 +1260,8 @@ bool nmPebiResultSceneController::applyCurrentDisplaySettings(
// 身份核对和实际提交处在同一个 GUI 线程调用中,旧对话框不能污染新快照。
if(!isResultSceneGuiThread() || !hasSnapshot() ||
nExpectedBindingRevision == 0 ||
nExpectedBindingRevision != getSnapshotBindingRevision() ||
sExpectedSnapshotId.isEmpty() ||
sExpectedPropertyId.isEmpty() ||
sExpectedSnapshotId != getCurrentSnapshotId() ||
@ -1427,6 +1435,12 @@ bool nmPebiResultSceneController::hasSnapshot() const
m_pPrivate->m_pWellLayer->hasActiveAnnotations();
}
quint64 nmPebiResultSceneController::getSnapshotBindingRevision() const
{
return m_pPrivate != nullptr
? m_pPrivate->m_nSnapshotBindingRevision : 0;
}
QString nmPebiResultSceneController::getCurrentSnapshotId() const
{
return hasSnapshot()

@ -79,6 +79,7 @@ nmWxPebiResultColorScaleSettingsDlg(
QWidget* pParent)
: QDialog(pParent),
m_pSceneController(pSceneController),
m_nExpectedBindingRevision(0),
m_bManualRangeEdited(false),
m_bInitialized(false),
m_pColorMapCombo(nullptr),
@ -324,6 +325,8 @@ bool nmWxPebiResultColorScaleSettingsDlg::initializeFromScene()
if(m_pSceneController == nullptr || !m_pSceneController->hasSnapshot()) {
return false;
}
m_nExpectedBindingRevision =
m_pSceneController->getSnapshotBindingRevision();
m_sExpectedSnapshotId = m_pSceneController->getCurrentSnapshotId();
m_sExpectedPropertyId = m_pSceneController->getCurrentPropertyId();
m_sDisplayUnit = nmPebiResultPropertyDisplayText::getDefaultDisplayUnit(
@ -331,7 +334,8 @@ bool nmWxPebiResultColorScaleSettingsDlg::initializeFromScene()
m_pUnitLabel->setText(m_sDisplayUnit.isEmpty()
? tr("Dimensionless") : m_sDisplayUnit);
double dVisibleRange[2] = { 0.0, 0.0 };
if(m_sExpectedSnapshotId.isEmpty() || m_sExpectedPropertyId.isEmpty() ||
if(m_nExpectedBindingRevision == 0 ||
m_sExpectedSnapshotId.isEmpty() || m_sExpectedPropertyId.isEmpty() ||
!m_pSceneController->getCurrentPropertyRange(m_dPropertyRange) ||
!m_pSceneController->getCurrentDisplayRange(dVisibleRange) ||
!m_pSceneController->getCurrentDisplaySettings(
@ -421,6 +425,7 @@ bool nmWxPebiResultColorScaleSettingsDlg::applyEditor()
QString sError;
if(!collectEditedSettings(oSettings, sError) ||
!m_pSceneController->applyCurrentDisplaySettings(
m_nExpectedBindingRevision,
m_sExpectedSnapshotId, m_sExpectedPropertyId,
oSettings, &sError)) {
setErrorText(sError);

@ -55,6 +55,7 @@ nmWxPebiResultControlBar::nmWxPebiResultControlBar(
m_pLastButton(nullptr),
m_pPlayPauseButton(nullptr),
m_pSettingsButton(nullptr),
m_nDisplayedBindingRevision(0),
m_nDisplayedTimeFrameCount(-1)
{
Q_ASSERT(pSceneController != nullptr);
@ -102,8 +103,10 @@ void nmWxPebiResultControlBar::onPropertyIndexChanged(int nIndex)
if(m_pPlaybackController == nullptr || nIndex < 0) {
return;
}
m_pPlaybackController->selectProperty(
m_pPropertyCombo->itemData(nIndex).toString());
if(m_pPlaybackController->selectProperty(
m_pPropertyCombo->itemData(nIndex).toString())) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onTimeIndexChanged(int nIndex)
@ -111,8 +114,10 @@ void nmWxPebiResultControlBar::onTimeIndexChanged(int nIndex)
if(m_pPlaybackController == nullptr || nIndex < 0) {
return;
}
m_pPlaybackController->selectFrame(
m_pTimeCombo->itemData(nIndex).toInt());
if(m_pPlaybackController->selectFrame(
m_pTimeCombo->itemData(nIndex).toInt())) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onColorSettingsClicked()
@ -128,6 +133,8 @@ void nmWxPebiResultControlBar::onColorSettingsClicked()
m_pSceneController, this);
connect(&oDialog, SIGNAL(displaySettingsApplied()),
this, SLOT(refreshColorPreview()));
connect(&oDialog, SIGNAL(displaySettingsApplied()),
this, SIGNAL(operationSucceeded()));
connect(&oDialog, SIGNAL(operationFailed(QString)),
this, SLOT(forwardOperationFailure(QString)));
oDialog.exec();
@ -136,27 +143,37 @@ void nmWxPebiResultControlBar::onColorSettingsClicked()
void nmWxPebiResultControlBar::onFirstFrameClicked()
{
m_pPlaybackController->selectFirstFrame();
if(m_pPlaybackController->selectFirstFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onPreviousFrameClicked()
{
m_pPlaybackController->selectPreviousFrame();
if(m_pPlaybackController->selectPreviousFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onNextFrameClicked()
{
m_pPlaybackController->selectNextFrame();
if(m_pPlaybackController->selectNextFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onLastFrameClicked()
{
m_pPlaybackController->selectLastFrame();
if(m_pPlaybackController->selectLastFrame()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onPlayPauseClicked()
{
m_pPlaybackController->togglePlayback();
if(m_pPlaybackController->togglePlayback()) {
emit operationSucceeded();
}
}
void nmWxPebiResultControlBar::onAnimationSettingsClicked()
@ -167,9 +184,11 @@ void nmWxPebiResultControlBar::onAnimationSettingsClicked()
m_pPlaybackController->getPlaybackSpeed(),
m_pPlaybackController->isLoopEnabled(), this);
if(oDialog.exec() == QDialog::Accepted) {
m_pPlaybackController->setPlaybackSettings(
oDialog.getPlaybackSpeed(),
oDialog.isLoopEnabled());
if(m_pPlaybackController->setPlaybackSettings(
oDialog.getPlaybackSpeed(),
oDialog.isLoopEnabled())) {
emit operationSucceeded();
}
}
}
@ -184,6 +203,7 @@ void nmWxPebiResultControlBar::refreshFromController()
m_pPropertyCombo->blockSignals(bPropertyBlocked);
clearTimeItems();
m_sDisplayedSnapshotId.clear();
m_nDisplayedBindingRevision = 0;
m_listDisplayedPropertyIds.clear();
m_pPropertyCombo->setEnabled(false);
m_pColorSettingsButton->setEnabled(false);
@ -202,14 +222,18 @@ void nmWxPebiResultControlBar::refreshFromController()
const QString sSnapshotId =
m_pPlaybackController->getObservedSnapshotId();
const quint64 nBindingRevision =
m_pPlaybackController->getObservedBindingRevision();
const QStringList listPropertyIds =
m_pPlaybackController->getAvailablePropertyIds();
const bool bSnapshotChanged =
m_nDisplayedBindingRevision != nBindingRevision ||
m_sDisplayedSnapshotId != sSnapshotId;
if(bSnapshotChanged ||
m_listDisplayedPropertyIds != listPropertyIds) {
rebuildPropertyItems(listPropertyIds);
m_sDisplayedSnapshotId = sSnapshotId;
m_nDisplayedBindingRevision = nBindingRevision;
m_listDisplayedPropertyIds = listPropertyIds;
// 即使属性和帧数相同,新快照的真实时间值也可能不同,不能复用旧列表。

@ -0,0 +1,577 @@
#include "nmWxPebiResultViewWidget.h"
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSceneController.h"
#include "nmPebiResultSnapshot.h"
#include "nmVTKViewCommandController.h"
#include "nmWxPebiResultControlBar.h"
#include "nmWxVTKRenderContainerWidget.h"
#include <QAction>
#include <QCoreApplication>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QSize>
#include <QSizePolicy>
#include <QThread>
#include <QToolBar>
#include <QVBoxLayout>
namespace {
QString resultViewIconPath(const QString& sFileName)
{
const QString sApplicationDir = QCoreApplication::applicationDirPath();
return sApplicationDir.section('/', 0, -2) +
"/Res/Icon/" + sFileName;
}
bool isResultViewGuiThread()
{
QCoreApplication* pApplication = QCoreApplication::instance();
return pApplication == nullptr ||
pApplication->thread() == QThread::currentThread();
}
void setActionCheckedWithoutSignal(QAction* pAction, bool bChecked)
{
if(pAction == nullptr) {
return;
}
const bool bBlocked = pAction->blockSignals(true);
pAction->setChecked(bChecked);
pAction->blockSignals(bBlocked);
}
}
nmWxPebiResultViewWidget::nmWxPebiResultViewWidget(
nmDataAnalyzeManager* pDataManager,
QWidget* pParent)
: QWidget(pParent),
m_pRenderContainer(nullptr),
m_pSceneController(nullptr),
m_pControlBar(nullptr),
m_pToolBar(nullptr),
m_pStatusLabel(nullptr),
m_pMeshAction(nullptr),
m_pScalarBarAction(nullptr),
m_pScaleGridAction(nullptr),
m_pCellEdgesAction(nullptr),
m_pWellMarkersAction(nullptr),
m_pWellNamesAction(nullptr),
m_bObservedSnapshotCurrent(false),
m_bObservedStateValid(false),
m_bSynchronizing(false),
m_bSyncPending(false)
{
createUi();
setDataManager(pDataManager);
}
nmWxPebiResultViewWidget::~nmWxPebiResultViewWidget()
{
disconnectDataManager();
if(m_pControlBar != nullptr) {
m_pControlBar->stopPlayback();
delete m_pControlBar;
m_pControlBar = nullptr;
}
if(m_pSceneController != nullptr) {
m_pSceneController->clear();
delete m_pSceneController;
m_pSceneController = nullptr;
}
// 渲染容器、工具栏、状态栏和 QAction 最后由 QWidget 对象树统一释放。
}
void nmWxPebiResultViewWidget::setDataManager(
nmDataAnalyzeManager* pDataManager)
{
Q_ASSERT(isResultViewGuiThread());
Q_ASSERT(pDataManager == nullptr ||
pDataManager->thread() == QThread::currentThread());
if(!isResultViewGuiThread() ||
(pDataManager != nullptr &&
pDataManager->thread() != QThread::currentThread())) {
// 窗口会同步读取不可变快照引用,不能跨线程直接访问 Manager。
return;
}
if(m_pDataManager.data() == pDataManager) {
// 生产入口可能重复安装同一 Manager;这里只读最新状态,不能清空相机。
synchronizeFromDataManager();
return;
}
disconnectDataManager();
clearSceneAndControls(tr("No result data."));
m_pDataManager = pDataManager;
if(!m_pDataManager.isNull()) {
connect(m_pDataManager.data(), SIGNAL(sigPebiResultStateChanged()),
this, SLOT(onPebiResultStateChanged()));
connect(m_pDataManager.data(), SIGNAL(destroyed(QObject*)),
this, SLOT(onDataManagerDestroyed(QObject*)));
}
synchronizeFromDataManager();
}
nmDataAnalyzeManager* nmWxPebiResultViewWidget::getDataManager() const
{
return m_pDataManager.data();
}
void nmWxPebiResultViewWidget::synchronizeFromDataManager()
{
Q_ASSERT(isResultViewGuiThread());
Q_ASSERT(m_pDataManager.isNull() ||
m_pDataManager->thread() == QThread::currentThread());
if(!isResultViewGuiThread() ||
(!m_pDataManager.isNull() &&
m_pDataManager->thread() != QThread::currentThread())) {
return;
}
if(m_bSynchronizing) {
// 同步期间只记录一次待处理请求,当前事务结束后重新读取 Manager 最新值。
m_bSyncPending = true;
return;
}
m_bSynchronizing = true;
do {
m_bSyncPending = false;
synchronizeOnce();
} while(m_bSyncPending);
m_bSynchronizing = false;
}
void nmWxPebiResultViewWidget::onPebiResultStateChanged()
{
synchronizeFromDataManager();
}
void nmWxPebiResultViewWidget::onDataManagerDestroyed(QObject* pObject)
{
Q_UNUSED(pObject);
// destroyed() 到达时 QPointer 可能已经清空,槽内绝不再读取 Manager。
m_pDataManager = nullptr;
if(m_bSynchronizing) {
// 极端嵌套事件下不得在快照提交栈内递归清场,退出当前事务后再读空状态。
m_bSyncPending = true;
return;
}
clearSceneAndControls(tr("No result data."));
// 记录稳定空状态,后续重复同步空 Manager 时不再清场或切换状态页。
rememberObservedState(
QSharedPointer<const nmPebiResultSnapshot>(),
QString(), false);
}
void nmWxPebiResultViewWidget::appendContextMenuActions(QMenu* pMenu)
{
if(pMenu == nullptr) {
return;
}
pMenu->addAction(m_pMeshAction);
pMenu->addAction(m_pScalarBarAction);
pMenu->addAction(m_pScaleGridAction);
pMenu->addAction(m_pCellEdgesAction);
pMenu->addAction(m_pWellMarkersAction);
pMenu->addAction(m_pWellNamesAction);
}
void nmWxPebiResultViewWidget::onControlOperationFailed(
const QString& sError)
{
// 高优先级状态存在时丢弃普通错误,避免其在过期提示解除后突然出现。
if(!m_sBindingError.isEmpty() ||
(m_bObservedStateValid && !m_bObservedSnapshotCurrent)) {
return;
}
m_sOperationError = sError;
refreshStatusBar();
}
void nmWxPebiResultViewWidget::onControlOperationSucceeded()
{
if(m_sOperationError.isEmpty()) {
return;
}
// 这里只清除最低优先级的普通操作错误;刷新函数仍优先显示绑定失败和过期。
m_sOperationError.clear();
refreshStatusBar();
}
void nmWxPebiResultViewWidget::onMeshToggled(bool bVisible)
{
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
m_pSceneController->setMeshVisible(bVisible);
}
}
void nmWxPebiResultViewWidget::onScalarBarToggled(bool bVisible)
{
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
m_pSceneController->setScalarBarVisible(bVisible);
}
}
void nmWxPebiResultViewWidget::onScaleGridToggled(bool bVisible)
{
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
m_pSceneController->setScaleGridVisible(bVisible);
}
}
void nmWxPebiResultViewWidget::onCellEdgesToggled(bool bVisible)
{
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
m_pSceneController->setCellEdgesVisible(bVisible);
}
}
void nmWxPebiResultViewWidget::onWellMarkersToggled(bool bVisible)
{
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
m_pSceneController->setWellMarkersVisible(bVisible);
}
}
void nmWxPebiResultViewWidget::onWellNamesToggled(bool bVisible)
{
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
m_pSceneController->setWellNamesVisible(bVisible);
}
}
void nmWxPebiResultViewWidget::createUi()
{
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
pMainLayout->setContentsMargins(0, 0, 0, 0);
pMainLayout->setSpacing(0);
QWidget* pRenderArea = new QWidget(this);
QHBoxLayout* pRenderLayout = new QHBoxLayout(pRenderArea);
pRenderLayout->setContentsMargins(0, 0, 0, 0);
pRenderLayout->setSpacing(0);
// 通用容器是本窗口唯一创建 QVTKWidget、Renderer 和 RenderWindow 的对象。
m_pRenderContainer = new nmWxVTKRenderContainerWidget(pRenderArea);
m_pRenderContainer->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Expanding);
m_pRenderContainer->setViewCommandCapabilities(
nmVTKViewCommandController::ViewCommand_ZoomIn |
nmVTKViewCommandController::ViewCommand_ZoomOut |
nmVTKViewCommandController::ViewCommand_Fit |
nmVTKViewCommandController::ViewCommand_Top |
nmVTKViewCommandController::ViewCommand_Front |
nmVTKViewCommandController::ViewCommand_SetRotationCenter |
nmVTKViewCommandController::ViewCommand_SaveImage |
nmVTKViewCommandController::ViewCommand_CopyImage |
nmVTKViewCommandController::ViewCommand_Print |
nmVTKViewCommandController::ViewCommand_PrintPreview);
m_pSceneController = new nmPebiResultSceneController(m_pRenderContainer);
m_pToolBar = new QToolBar(pRenderArea);
m_pToolBar->setOrientation(Qt::Vertical);
m_pToolBar->setMovable(false);
m_pToolBar->setFloatable(false);
m_pToolBar->setIconSize(QSize(32, 32));
m_pToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_pToolBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
m_pMeshAction = new QAction(QIcon(resultViewIconPath("NmGrid.png")),
tr("Result Grid"), this);
m_pScalarBarAction = new QAction(
QIcon(resultViewIconPath("NmColorScale.png")),
tr("Color Scale"), this);
m_pScaleGridAction = new QAction(
QIcon(resultViewIconPath("NmScaleGrid.png")),
tr("Scale Grid"), this);
m_pCellEdgesAction = new QAction(
QIcon(resultViewIconPath("SpecialLineVisible.png")),
tr("Cell Edges"), this);
m_pWellMarkersAction = new QAction(
QIcon(resultViewIconPath("NmWell.png")),
tr("Well Markers"), this);
m_pWellNamesAction = new QAction(
QIcon(resultViewIconPath("NmShow.png")),
tr("Well Names"), this);
QAction* const pBusinessActions[] = {
m_pMeshAction, m_pScalarBarAction, m_pScaleGridAction,
m_pCellEdgesAction, m_pWellMarkersAction, m_pWellNamesAction
};
for(int nIndex = 0; nIndex < 6; ++nIndex) {
pBusinessActions[nIndex]->setCheckable(true);
pBusinessActions[nIndex]->setToolTip(
pBusinessActions[nIndex]->text());
}
nmVTKViewCommandController* pCommandController =
m_pRenderContainer->getViewCommandController();
if(pCommandController != nullptr) {
QAction* pZoomOutAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_ZoomOut);
QAction* pFitAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_Fit);
QAction* pZoomInAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_ZoomIn);
QAction* pTopAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_Top);
QAction* pFrontAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_Front);
QAction* pPickCenterAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_SetRotationCenter);
QAction* pCopyAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_CopyImage);
if(pZoomOutAction != nullptr) m_pToolBar->addAction(pZoomOutAction);
if(pFitAction != nullptr) m_pToolBar->addAction(pFitAction);
if(pZoomInAction != nullptr) m_pToolBar->addAction(pZoomInAction);
m_pToolBar->addSeparator();
if(pTopAction != nullptr) m_pToolBar->addAction(pTopAction);
if(pFrontAction != nullptr) m_pToolBar->addAction(pFrontAction);
if(pPickCenterAction != nullptr) m_pToolBar->addAction(pPickCenterAction);
m_pToolBar->addSeparator();
for(int nIndex = 0; nIndex < 6; ++nIndex) {
m_pToolBar->addAction(pBusinessActions[nIndex]);
}
m_pToolBar->addSeparator();
if(pCopyAction != nullptr) m_pToolBar->addAction(pCopyAction);
}
connect(m_pMeshAction, SIGNAL(toggled(bool)),
this, SLOT(onMeshToggled(bool)));
connect(m_pScalarBarAction, SIGNAL(toggled(bool)),
this, SLOT(onScalarBarToggled(bool)));
connect(m_pScaleGridAction, SIGNAL(toggled(bool)),
this, SLOT(onScaleGridToggled(bool)));
connect(m_pCellEdgesAction, SIGNAL(toggled(bool)),
this, SLOT(onCellEdgesToggled(bool)));
connect(m_pWellMarkersAction, SIGNAL(toggled(bool)),
this, SLOT(onWellMarkersToggled(bool)));
connect(m_pWellNamesAction, SIGNAL(toggled(bool)),
this, SLOT(onWellNamesToggled(bool)));
connect(m_pRenderContainer, SIGNAL(contextMenuAboutToShow(QMenu*)),
this, SLOT(appendContextMenuActions(QMenu*)));
pRenderLayout->addWidget(m_pRenderContainer, 1);
pRenderLayout->addWidget(m_pToolBar);
pMainLayout->addWidget(pRenderArea, 1);
m_pStatusLabel = new QLabel(this);
m_pStatusLabel->setWordWrap(true);
m_pStatusLabel->setContentsMargins(8, 4, 8, 4);
m_pStatusLabel->hide();
pMainLayout->addWidget(m_pStatusLabel);
m_pControlBar = new nmWxPebiResultControlBar(
m_pSceneController, this);
connect(m_pControlBar, SIGNAL(operationFailed(QString)),
this, SLOT(onControlOperationFailed(QString)));
connect(m_pControlBar, SIGNAL(operationSucceeded()),
this, SLOT(onControlOperationSucceeded()));
pMainLayout->addWidget(m_pControlBar);
synchronizeBusinessActions();
m_pRenderContainer->showEmptyState(tr("No result data."));
}
void nmWxPebiResultViewWidget::synchronizeOnce()
{
if(m_pDataManager.isNull()) {
if(m_bObservedStateValid && m_pObservedSnapshot.isNull()) {
return;
}
clearSceneAndControls(tr("No result data."));
rememberObservedState(
QSharedPointer<const nmPebiResultSnapshot>(),
QString(), false);
return;
}
const QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
m_pDataManager->getPebiResultSnapshot();
const QString sSnapshotId = pSnapshot.isNull()
? QString() : pSnapshot->getSnapshotId();
const bool bCurrent = !pSnapshot.isNull() &&
m_pDataManager->isPebiResultSnapshotCurrent();
const bool bSameIdentity = m_bObservedStateValid &&
m_pObservedSnapshot.data() == pSnapshot.data() &&
m_sObservedSnapshotId == sSnapshotId;
const bool bSameState = bSameIdentity &&
m_bObservedSnapshotCurrent == bCurrent;
if(bSameState) {
return;
}
const bool bBecameStale = bSameIdentity &&
m_bObservedSnapshotCurrent && !bCurrent;
m_sOperationError.clear();
if(pSnapshot.isNull()) {
clearSceneAndControls(tr("No result data."));
rememberObservedState(pSnapshot, sSnapshotId, false);
return;
}
if(!bSameIdentity) {
// 候选提交前停止本窗口 Timer;失败时旧 Scene 和最后压力帧记忆仍保留。
m_pControlBar->stopPlayback();
QString sError;
const bool bBound = m_pSceneController->setSnapshot(
pSnapshot, &sError);
rememberObservedState(pSnapshot, sSnapshotId, bCurrent);
if(bBound) {
m_pDisplayedSnapshot = pSnapshot;
m_sDisplayedSnapshotId = sSnapshotId;
m_sBindingError.clear();
// 新成功绑定序号会让 Playback 和 ControlBar 同时重置各自缓存。
m_pControlBar->synchronizeFromScene();
synchronizeBusinessActions();
refreshStatusBar();
return;
}
m_sBindingError = sError.isEmpty()
? tr("Unable to display the latest result.")
: tr("Unable to display the latest result: %1").arg(sError);
if(!m_pSceneController->hasSnapshot()) {
m_pDisplayedSnapshot.clear();
m_sDisplayedSnapshotId.clear();
}
m_pControlBar->synchronizeFromScene();
synchronizeBusinessActions();
if(m_pSceneController->hasSnapshot()) {
// 旧画面保持 Ready;这里只显示红色非阻塞提示,不能追加 Render。
refreshStatusBar();
} else {
m_pStatusLabel->hide();
m_pRenderContainer->showErrorState(m_sBindingError);
}
return;
}
rememberObservedState(pSnapshot, sSnapshotId, bCurrent);
if(bBecameStale) {
// 只在“当前 -> 过期”的边沿暂停一次;用户之后可以手动重新播放旧结果。
m_pControlBar->stopPlayback();
}
refreshStatusBar();
}
void nmWxPebiResultViewWidget::clearSceneAndControls(
const QString& sEmptyMessage)
{
if(m_pControlBar != nullptr) {
m_pControlBar->stopPlayback();
}
if(m_pSceneController != nullptr) {
m_pSceneController->clear();
}
m_pObservedSnapshot.clear();
m_sObservedSnapshotId.clear();
m_bObservedSnapshotCurrent = false;
m_bObservedStateValid = false;
m_pDisplayedSnapshot.clear();
m_sDisplayedSnapshotId.clear();
m_sBindingError.clear();
m_sOperationError.clear();
if(m_pControlBar != nullptr) {
m_pControlBar->synchronizeFromScene();
}
synchronizeBusinessActions();
if(m_pStatusLabel != nullptr) {
m_pStatusLabel->hide();
}
if(m_pRenderContainer != nullptr) {
m_pRenderContainer->showEmptyState(sEmptyMessage);
}
}
void nmWxPebiResultViewWidget::rememberObservedState(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
const QString& sSnapshotId,
bool bCurrent)
{
m_pObservedSnapshot = pSnapshot;
m_sObservedSnapshotId = sSnapshotId;
m_bObservedSnapshotCurrent = bCurrent;
m_bObservedStateValid = true;
}
void nmWxPebiResultViewWidget::synchronizeBusinessActions()
{
if(m_pSceneController == nullptr) {
return;
}
setActionCheckedWithoutSignal(
m_pMeshAction, m_pSceneController->isMeshVisible());
setActionCheckedWithoutSignal(
m_pScalarBarAction, m_pSceneController->isScalarBarVisible());
setActionCheckedWithoutSignal(
m_pScaleGridAction, m_pSceneController->isScaleGridVisible());
setActionCheckedWithoutSignal(
m_pCellEdgesAction, m_pSceneController->areCellEdgesVisible());
setActionCheckedWithoutSignal(
m_pWellMarkersAction, m_pSceneController->areWellMarkersVisible());
setActionCheckedWithoutSignal(
m_pWellNamesAction, m_pSceneController->areWellNamesVisible());
const bool bEnabled = m_pSceneController->hasSnapshot();
m_pMeshAction->setEnabled(bEnabled);
m_pScalarBarAction->setEnabled(bEnabled);
m_pScaleGridAction->setEnabled(bEnabled);
m_pCellEdgesAction->setEnabled(bEnabled);
m_pWellMarkersAction->setEnabled(bEnabled);
m_pWellNamesAction->setEnabled(bEnabled);
}
void nmWxPebiResultViewWidget::refreshStatusBar()
{
if(m_pStatusLabel == nullptr || m_pSceneController == nullptr ||
!m_pSceneController->hasSnapshot()) {
if(m_pStatusLabel != nullptr) {
m_pStatusLabel->hide();
}
return;
}
if(!m_sBindingError.isEmpty()) {
m_pStatusLabel->setStyleSheet(
"QLabel { color: #a32626; background: #fdecec; } ");
m_pStatusLabel->setText(m_sBindingError);
m_pStatusLabel->show();
return;
}
if(m_bObservedStateValid && !m_bObservedSnapshotCurrent) {
m_pStatusLabel->setStyleSheet(
"QLabel { color: #6b5200; background: #fff4bf; } ");
m_pStatusLabel->setText(tr(
"Current inputs have changed. Showing the last successful result."));
m_pStatusLabel->show();
return;
}
if(!m_sOperationError.isEmpty()) {
m_pStatusLabel->setStyleSheet(
"QLabel { color: #a32626; background: #fdecec; } ");
m_pStatusLabel->setText(m_sOperationError);
m_pStatusLabel->show();
return;
}
m_pStatusLabel->hide();
}
void nmWxPebiResultViewWidget::disconnectDataManager()
{
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager != nullptr) {
disconnect(pDataManager, SIGNAL(sigPebiResultStateChanged()),
this, SLOT(onPebiResultStateChanged()));
disconnect(pDataManager, SIGNAL(destroyed(QObject*)),
this, SLOT(onDataManagerDestroyed(QObject*)));
}
m_pDataManager = nullptr;
}
Loading…
Cancel
Save