|
|
#include "nmWxPebiResultViewWidget.h"
|
|
|
#include "nmVolumeImportTask.h"
|
|
|
#include "nmVolumeSceneController.h"
|
|
|
#include "nmVolumeSliceInteraction.h"
|
|
|
#include "nmWxVolumeSliceSettingsDlg.h"
|
|
|
#include "nmVTKViewState.h"
|
|
|
#include <QFileDialog>
|
|
|
#include <QToolButton>
|
|
|
#include <QPainter>
|
|
|
#include <QPixmap>
|
|
|
#include <QPolygon>
|
|
|
#include <QPen>
|
|
|
|
|
|
#include "iUnitGroup.h"
|
|
|
#include "iUnitHelper.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
#include "nmFFmpegVideoWriter.h"
|
|
|
#include "nmPebiRegionPressureCalculator.h"
|
|
|
#include "nmPebiResultAnimationExporter.h"
|
|
|
#include "nmPebiResultDisplaySettings.h"
|
|
|
#include "nmPebiResultPlaybackController.h"
|
|
|
#include "nmPebiResultPropertyCatalog.h"
|
|
|
#include "nmPebiResultPropertyDisplayText.h"
|
|
|
#include "nmPebiResultSceneController.h"
|
|
|
#include "nmPebiResultSnapshot.h"
|
|
|
#include "nmVTKMeshLayer.h"
|
|
|
#include "nmVTKPlanarPolygonInteractorStyle.h"
|
|
|
#include "nmVTKPlanarPolygonLayer.h"
|
|
|
#include "nmVTKScene.h"
|
|
|
#include "nmVTKViewCommandController.h"
|
|
|
#include "nmWxPebiResultControlBar.h"
|
|
|
#include "nmWxPebiResultAnimationExportDlg.h"
|
|
|
#include "nmWxVTKRenderContainerWidget.h"
|
|
|
|
|
|
#include <QAction>
|
|
|
#include <QAtomicInt>
|
|
|
#include <QCheckBox>
|
|
|
#include <QComboBox>
|
|
|
#include <QCoreApplication>
|
|
|
#include <QDialog>
|
|
|
#include <QDir>
|
|
|
#include <QGridLayout>
|
|
|
#include <QGroupBox>
|
|
|
#include <QHBoxLayout>
|
|
|
#include <QIcon>
|
|
|
#include <QLabel>
|
|
|
#include <QLineEdit>
|
|
|
#include <QLocale>
|
|
|
#include <QMenu>
|
|
|
#include <QMessageBox>
|
|
|
#include <QMetaObject>
|
|
|
#include <QPointer>
|
|
|
#include <QProgressDialog>
|
|
|
#include <QPushButton>
|
|
|
#include <QSize>
|
|
|
#include <QSizePolicy>
|
|
|
#include <QThread>
|
|
|
#include <QToolBar>
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
|
#include <vtkCamera.h>
|
|
|
#include <vtkCallbackCommand.h>
|
|
|
#include <vtkMath.h>
|
|
|
#include <vtkObject.h>
|
|
|
#include <vtkRenderer.h>
|
|
|
#include <vtkSmartPointer.h>
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
#if !defined(_MSC_VER)
|
|
|
#include <cmath>
|
|
|
#endif
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
QAtomicInt g_nPebiAnimationExportActive(0);
|
|
|
|
|
|
const char* const PEBI_REGION_PRESSURE_LAYER_ID =
|
|
|
"PebiResult.RegionPressurePolygon";
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
bool isFiniteFilterEditorValue(double dValue)
|
|
|
{
|
|
|
#if defined(_MSC_VER)
|
|
|
return _finite(dValue) != 0;
|
|
|
#else
|
|
|
return std::isfinite(dValue);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
QString formatRegionValue(double dValue)
|
|
|
{
|
|
|
if(!isFiniteFilterEditorValue(dValue)) {
|
|
|
return QString();
|
|
|
}
|
|
|
return QLocale().toString(dValue, 'g', 10);
|
|
|
}
|
|
|
|
|
|
QLineEdit* addRegionStatisticsRow(QGridLayout* pLayout,
|
|
|
int nRow,
|
|
|
const QString& sName,
|
|
|
QWidget* pParent,
|
|
|
QWidget* pUnitWidget = nullptr)
|
|
|
{
|
|
|
QLabel* pNameLabel = new QLabel(sName, pParent);
|
|
|
QLineEdit* pValueEdit = new QLineEdit(pParent);
|
|
|
pNameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
pValueEdit->setReadOnly(true);
|
|
|
pValueEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
pValueEdit->setMinimumWidth(150);
|
|
|
pLayout->addWidget(pNameLabel, nRow, 0);
|
|
|
pLayout->addWidget(pValueEdit, nRow, 1);
|
|
|
if(pUnitWidget != nullptr) {
|
|
|
pLayout->addWidget(pUnitWidget, nRow, 2);
|
|
|
}
|
|
|
return pValueEdit;
|
|
|
}
|
|
|
|
|
|
QComboBox* createRegionUnitCombo(QWidget* pParent,
|
|
|
const QString& sUnit)
|
|
|
{
|
|
|
QComboBox* pCombo = new QComboBox(pParent);
|
|
|
pCombo->addItem(sUnit);
|
|
|
pCombo->setEnabled(false);
|
|
|
pCombo->setMinimumWidth(80);
|
|
|
pCombo->setMaximumWidth(140);
|
|
|
pCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
|
pCombo->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
|
|
|
return pCombo;
|
|
|
}
|
|
|
|
|
|
bool canProjectRegionOntoXyPlane(vtkRenderer* pRenderer)
|
|
|
{
|
|
|
vtkCamera* pCamera = pRenderer == nullptr
|
|
|
? nullptr : pRenderer->GetActiveCamera();
|
|
|
if(pCamera == nullptr) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
double dDirection[3];
|
|
|
pCamera->GetDirectionOfProjection(dDirection);
|
|
|
const double dLength = vtkMath::Norm(dDirection);
|
|
|
return vtkMath::IsFinite(dLength) && dLength > 0.0 &&
|
|
|
vtkMath::IsFinite(dDirection[2]) &&
|
|
|
qAbs(dDirection[2]) > 1.0e-10 * dLength;
|
|
|
}
|
|
|
|
|
|
class AnimationExportGuard
|
|
|
{
|
|
|
public:
|
|
|
explicit AnimationExportGuard(nmWxPebiResultControlBar* pControlBar)
|
|
|
: m_pControlBar(pControlBar),
|
|
|
m_bAcquired(g_nPebiAnimationExportActive.testAndSetOrdered(0, 1))
|
|
|
{
|
|
|
if(m_bAcquired && m_pControlBar != nullptr) {
|
|
|
m_pControlBar->setExportInProgress(true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
~AnimationExportGuard()
|
|
|
{
|
|
|
release();
|
|
|
}
|
|
|
|
|
|
void release()
|
|
|
{
|
|
|
if(!m_bAcquired) {
|
|
|
return;
|
|
|
}
|
|
|
if(m_pControlBar != nullptr) {
|
|
|
m_pControlBar->setExportInProgress(false);
|
|
|
}
|
|
|
g_nPebiAnimationExportActive.fetchAndStoreOrdered(0);
|
|
|
m_bAcquired = false;
|
|
|
}
|
|
|
|
|
|
bool isAcquired() const
|
|
|
{
|
|
|
return m_bAcquired;
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
QPointer<nmWxPebiResultControlBar> m_pControlBar;
|
|
|
bool m_bAcquired;
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
class nmWxPebiResultRegionState
|
|
|
{
|
|
|
public:
|
|
|
enum State
|
|
|
{
|
|
|
State_Idle = 0,
|
|
|
State_Drawing,
|
|
|
State_Completed
|
|
|
};
|
|
|
|
|
|
enum PendingEvent
|
|
|
{
|
|
|
PendingEvent_None = 0,
|
|
|
PendingEvent_Completed,
|
|
|
PendingEvent_Cancelled,
|
|
|
PendingEvent_Invalid
|
|
|
};
|
|
|
|
|
|
nmWxPebiResultRegionState()
|
|
|
: m_eState(State_Idle),
|
|
|
m_ePendingEvent(PendingEvent_None),
|
|
|
m_bPendingEventQueued(false),
|
|
|
m_pLayer(nullptr),
|
|
|
m_nChangedObserverTag(0),
|
|
|
m_nCompletedObserverTag(0),
|
|
|
m_nCancelledObserverTag(0),
|
|
|
m_nInvalidObserverTag(0),
|
|
|
m_nFrozenBindingRevision(0),
|
|
|
m_nFrozenFrameIndex(-1),
|
|
|
m_nRegionBindingRevision(0),
|
|
|
m_nLastAttemptedFrameIndex(-1),
|
|
|
m_bStatisticsCalculated(false),
|
|
|
m_pDialog(nullptr),
|
|
|
m_pAveragePressureUnitCombo(nullptr),
|
|
|
m_pMinimumPressureUnitCombo(nullptr),
|
|
|
m_pMaximumPressureUnitCombo(nullptr),
|
|
|
m_pPressureTimeUnitCombo(nullptr),
|
|
|
m_pAveragePressureValue(nullptr),
|
|
|
m_pMinimumPressureValue(nullptr),
|
|
|
m_pMaximumPressureValue(nullptr),
|
|
|
m_pTimeValue(nullptr),
|
|
|
m_pSelectedCellValue(nullptr),
|
|
|
m_pWarningLabel(nullptr)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
State m_eState; ///< 当前窗口区域工具状态。
|
|
|
PendingEvent m_ePendingEvent; ///< 等待 Qt 事件循环处理的 VTK 终止事件。
|
|
|
bool m_bPendingEventQueued; ///< 是否已有一次安全收尾被排队。
|
|
|
QVector<QPointF> m_vecPendingPoints; ///< 完成回调复制出的候选世界坐标。
|
|
|
|
|
|
nmPebiRegionPressureCalculator m_oCalculator; ///< 独占相交缓存和压力计算器。
|
|
|
nmVTKPlanarPolygonLayer* m_pLayer; ///< 业务 Scene 所有的区域轮廓观察指针。
|
|
|
vtkSmartPointer<nmVTKPlanarPolygonInteractorStyle> m_pStyle; ///< 临时绘制样式。
|
|
|
vtkSmartPointer<vtkCallbackCommand> m_pCallback; ///< 不拥有窗口的 VTK 回调命令。
|
|
|
unsigned long m_nChangedObserverTag; ///< 点集变化观察者标识。
|
|
|
unsigned long m_nCompletedObserverTag; ///< 绘制完成观察者标识。
|
|
|
unsigned long m_nCancelledObserverTag; ///< 绘制取消观察者标识。
|
|
|
unsigned long m_nInvalidObserverTag; ///< 无效完成观察者标识。
|
|
|
|
|
|
QSharedPointer<const nmPebiResultSnapshot> m_pFrozenSnapshot; ///< 绘制开始时的快照强引用。
|
|
|
quint64 m_nFrozenBindingRevision; ///< 绘制开始时的成功绑定序号。
|
|
|
QString m_sFrozenSnapshotId; ///< 绘制开始时的快照稳定 ID。
|
|
|
QString m_sFrozenPropertyId; ///< 绘制开始时的显示属性 ID。
|
|
|
int m_nFrozenFrameIndex; ///< 绘制开始时的 pressure 帧。
|
|
|
|
|
|
quint64 m_nRegionBindingRevision; ///< 已完成区域所属成功绑定序号。
|
|
|
QString m_sRegionSnapshotId; ///< 已完成区域所属快照 ID。
|
|
|
QString m_sLastObservedPropertyId; ///< 最近一次状态信号观察到的显示属性。
|
|
|
int m_nLastAttemptedFrameIndex; ///< 最近一次尝试统计的 pressure 帧。
|
|
|
bool m_bStatisticsCalculated; ///< 最近一次统计调用是否成功。
|
|
|
QString m_sStatisticsError; ///< 最近一次统计调用失败说明。
|
|
|
nmPebiRegionPressureStatistics m_oStatistics; ///< 最近一次成功统计结果。
|
|
|
|
|
|
QDialog* m_pDialog; ///< ViewWidget 子对象,关闭时同步结束本次统计。
|
|
|
QComboBox* m_pAveragePressureUnitCombo; ///< 固定显示 MPa 的禁用单位框。
|
|
|
QComboBox* m_pMinimumPressureUnitCombo; ///< 压力下限固定单位框。
|
|
|
QComboBox* m_pMaximumPressureUnitCombo; ///< 压力上限固定单位框。
|
|
|
QComboBox* m_pPressureTimeUnitCombo; ///< 固定显示 h 的禁用单位框。
|
|
|
QLineEdit* m_pAveragePressureValue; ///< 只读区域平均压力。
|
|
|
QLineEdit* m_pMinimumPressureValue; ///< 只读区域压力下限。
|
|
|
QLineEdit* m_pMaximumPressureValue; ///< 只读区域压力上限。
|
|
|
QLineEdit* m_pTimeValue; ///< 只读求解时间显示值。
|
|
|
QLineEdit* m_pSelectedCellValue; ///< 只读相交单元数。
|
|
|
QLabel* m_pWarningLabel; ///< 仅在无结果或有忽略单元时显示的说明。
|
|
|
};
|
|
|
|
|
|
nmWxPebiResultFilterSettingsDlg::nmWxPebiResultFilterSettingsDlg(
|
|
|
nmPebiResultSceneController* pSceneController,
|
|
|
nmWxPebiResultControlBar* pControlBar,
|
|
|
QWidget* pParent)
|
|
|
: iDlgBase(pParent),
|
|
|
m_pSceneController(pSceneController),
|
|
|
m_pControlBar(pControlBar),
|
|
|
m_nExpectedBindingRevision(0),
|
|
|
m_nExpectedDisplayFrameIndex(-1),
|
|
|
m_pUnitGroup(nullptr),
|
|
|
m_bInitialized(false),
|
|
|
m_pEnableCheck(nullptr),
|
|
|
m_pPropertyCombo(nullptr),
|
|
|
m_pMinimumCheck(nullptr),
|
|
|
m_pMinimumEdit(nullptr),
|
|
|
m_pMaximumCheck(nullptr),
|
|
|
m_pMaximumEdit(nullptr),
|
|
|
m_pUnitCombo(nullptr),
|
|
|
m_pApplyButton(nullptr),
|
|
|
m_pOkButton(nullptr)
|
|
|
{
|
|
|
createUi();
|
|
|
m_bInitialized = initializeFromScene();
|
|
|
if(!m_bInitialized) {
|
|
|
m_pApplyButton->setEnabled(false);
|
|
|
m_pOkButton->setEnabled(false);
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Filtering settings"),
|
|
|
tr("Unable to read the current result filter settings."));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmWxPebiResultFilterSettingsDlg::~nmWxPebiResultFilterSettingsDlg()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultFilterSettingsDlg::createUi()
|
|
|
{
|
|
|
setWindowTitle(tr("Filtering settings"));
|
|
|
setModal(true);
|
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
|
|
|
m_pEnableCheck = new QCheckBox(tr("Enable filtering"), this);
|
|
|
m_pPropertyCombo = new QComboBox(this);
|
|
|
m_pPropertyCombo->setSizeAdjustPolicy(
|
|
|
QComboBox::AdjustToContentsOnFirstShow);
|
|
|
m_pPropertyCombo->setMinimumWidth(220);
|
|
|
|
|
|
m_pMinimumCheck = new QCheckBox(tr("Above min (>=)"), this);
|
|
|
m_pMinimumEdit = new QLineEdit(this);
|
|
|
m_pMinimumEdit->setAlignment(Qt::AlignRight);
|
|
|
m_pMaximumCheck = new QCheckBox(tr("Below max (<=)"), this);
|
|
|
m_pMaximumEdit = new QLineEdit(this);
|
|
|
m_pMaximumEdit->setAlignment(Qt::AlignRight);
|
|
|
m_pUnitCombo = new QComboBox(this);
|
|
|
m_pUnitCombo->setMinimumWidth(80);
|
|
|
m_pUnitCombo->setMaximumWidth(140);
|
|
|
m_pUnitCombo->hide();
|
|
|
m_pMinimumEdit->setMinimumWidth(150);
|
|
|
m_pMaximumEdit->setMinimumWidth(150);
|
|
|
|
|
|
QGridLayout* pFieldsLayout = new QGridLayout();
|
|
|
pFieldsLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
pFieldsLayout->setHorizontalSpacing(8);
|
|
|
pFieldsLayout->setVerticalSpacing(6);
|
|
|
pFieldsLayout->addWidget(m_pEnableCheck, 0, 0, 1, 3);
|
|
|
QLabel* pPropertyLabel = new QLabel(tr("Filter on"), this);
|
|
|
pPropertyLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
pFieldsLayout->addWidget(pPropertyLabel, 1, 0);
|
|
|
pFieldsLayout->addWidget(m_pPropertyCombo, 1, 1, 1, 2);
|
|
|
pFieldsLayout->addWidget(m_pMinimumCheck, 2, 0);
|
|
|
pFieldsLayout->addWidget(m_pMinimumEdit, 2, 1);
|
|
|
pFieldsLayout->addWidget(m_pUnitCombo, 2, 2, 2, 1,
|
|
|
Qt::AlignVCenter);
|
|
|
pFieldsLayout->addWidget(m_pMaximumCheck, 3, 0);
|
|
|
pFieldsLayout->addWidget(m_pMaximumEdit, 3, 1);
|
|
|
pFieldsLayout->setColumnStretch(1, 1);
|
|
|
|
|
|
// 普通布局不受平台按钮顺序策略影响,始终保持应用、确定、取消。
|
|
|
QHBoxLayout* pButtonLayout = new QHBoxLayout();
|
|
|
pButtonLayout->addStretch();
|
|
|
m_pApplyButton = new QPushButton(tr("Apply"), this);
|
|
|
m_pOkButton = new QPushButton(tr("OK"), this);
|
|
|
QPushButton* pCancelButton = new QPushButton(tr("Cancel"), this);
|
|
|
const QSize oFooterIconSize(16, 16);
|
|
|
m_pOkButton->setIcon(QIcon(resultViewIconPath("OK.png")));
|
|
|
pCancelButton->setIcon(QIcon(resultViewIconPath("Cancel.png")));
|
|
|
m_pOkButton->setIconSize(oFooterIconSize);
|
|
|
pCancelButton->setIconSize(oFooterIconSize);
|
|
|
m_pApplyButton->setMinimumWidth(72);
|
|
|
m_pOkButton->setMinimumWidth(72);
|
|
|
pCancelButton->setMinimumWidth(72);
|
|
|
m_pApplyButton->setAutoDefault(false);
|
|
|
m_pOkButton->setDefault(true);
|
|
|
pCancelButton->setAutoDefault(false);
|
|
|
pButtonLayout->setContentsMargins(4, 2, 4, 5);
|
|
|
pButtonLayout->setSpacing(6);
|
|
|
pButtonLayout->addWidget(m_pApplyButton);
|
|
|
pButtonLayout->addWidget(m_pOkButton);
|
|
|
pButtonLayout->addWidget(pCancelButton);
|
|
|
|
|
|
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
|
|
|
pMainLayout->setContentsMargins(12, 10, 12, 0);
|
|
|
pMainLayout->setSpacing(8);
|
|
|
pMainLayout->addLayout(pFieldsLayout);
|
|
|
pMainLayout->addLayout(pButtonLayout);
|
|
|
setMinimumWidth(430);
|
|
|
|
|
|
connect(m_pApplyButton, SIGNAL(clicked()),
|
|
|
this, SLOT(onApplyClicked()));
|
|
|
connect(m_pOkButton, SIGNAL(clicked()),
|
|
|
this, SLOT(onOkClicked()));
|
|
|
connect(pCancelButton, SIGNAL(clicked()),
|
|
|
this, SLOT(reject()));
|
|
|
connect(m_pPropertyCombo, SIGNAL(currentIndexChanged(int)),
|
|
|
this, SLOT(onPropertyChanged(int)));
|
|
|
connect(m_pUnitCombo, SIGNAL(currentIndexChanged(int)),
|
|
|
this, SLOT(onDisplayUnitChanged(int)));
|
|
|
connect(m_pEnableCheck, SIGNAL(toggled(bool)),
|
|
|
this, SLOT(refreshEditorEnabledState()));
|
|
|
connect(m_pMinimumCheck, SIGNAL(toggled(bool)),
|
|
|
this, SLOT(refreshEditorEnabledState()));
|
|
|
connect(m_pMaximumCheck, SIGNAL(toggled(bool)),
|
|
|
this, SLOT(refreshEditorEnabledState()));
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::initializeFromScene()
|
|
|
{
|
|
|
if(m_pSceneController == nullptr || m_pControlBar == nullptr ||
|
|
|
!m_pSceneController->hasSnapshot()) {
|
|
|
return false;
|
|
|
}
|
|
|
m_nExpectedBindingRevision =
|
|
|
m_pSceneController->getSnapshotBindingRevision();
|
|
|
m_sExpectedSnapshotId = m_pSceneController->getCurrentSnapshotId();
|
|
|
m_sExpectedDisplayPropertyId =
|
|
|
m_pSceneController->getCurrentPropertyId();
|
|
|
m_nExpectedDisplayFrameIndex =
|
|
|
m_pSceneController->getCurrentFrameIndex();
|
|
|
if(m_nExpectedBindingRevision == 0 ||
|
|
|
m_sExpectedSnapshotId.isEmpty() ||
|
|
|
m_sExpectedDisplayPropertyId.isEmpty() ||
|
|
|
m_nExpectedDisplayFrameIndex < 0 ||
|
|
|
!m_pSceneController->getFilterSettings(m_oBaselineSettings)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const bool bSignalsBlocked = m_pPropertyCombo->blockSignals(true);
|
|
|
const QStringList listPropertyIds =
|
|
|
m_pSceneController->getAvailablePropertyIds();
|
|
|
for(int nIndex = 0; nIndex < listPropertyIds.size(); ++nIndex) {
|
|
|
const QString& sPropertyId = listPropertyIds[nIndex];
|
|
|
double dRange[2] = { 0.0, 0.0 };
|
|
|
if(!m_pSceneController->getPropertyRange(sPropertyId, dRange)) {
|
|
|
m_pPropertyCombo->blockSignals(bSignalsBlocked);
|
|
|
return false;
|
|
|
}
|
|
|
m_mapPropertyRanges.insert(
|
|
|
sPropertyId, qMakePair(dRange[0], dRange[1]));
|
|
|
QString sDisplayName =
|
|
|
nmPebiResultPropertyDisplayText::getDisplayName(sPropertyId);
|
|
|
if(sDisplayName.isEmpty()) {
|
|
|
sDisplayName = sPropertyId;
|
|
|
}
|
|
|
m_pPropertyCombo->addItem(sDisplayName, sPropertyId);
|
|
|
}
|
|
|
m_pPropertyCombo->blockSignals(bSignalsBlocked);
|
|
|
if(m_pPropertyCombo->count() <= 0) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(m_oBaselineSettings.m_sPropertyId.isEmpty() ||
|
|
|
!m_mapPropertyRanges.contains(
|
|
|
m_oBaselineSettings.m_sPropertyId)) {
|
|
|
m_oBaselineSettings.m_sPropertyId =
|
|
|
m_sExpectedDisplayPropertyId;
|
|
|
const QPair<double, double> oRange =
|
|
|
m_mapPropertyRanges.value(
|
|
|
m_oBaselineSettings.m_sPropertyId);
|
|
|
m_oBaselineSettings.m_bMinimumEnabled = true;
|
|
|
m_oBaselineSettings.m_dMinimum = oRange.first;
|
|
|
m_oBaselineSettings.m_bMaximumEnabled = true;
|
|
|
m_oBaselineSettings.m_dMaximum = oRange.second;
|
|
|
}
|
|
|
return populateSettings(m_oBaselineSettings);
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::initializeUnitEditor(
|
|
|
const QString& sPropertyId)
|
|
|
{
|
|
|
nmPebiResultPropertyDescriptor oDescriptor;
|
|
|
if(!nmPebiResultPropertyCatalog::findDescriptor(
|
|
|
sPropertyId, oDescriptor)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const bool bSignalsBlocked = m_pUnitCombo->blockSignals(true);
|
|
|
m_pUnitCombo->clear();
|
|
|
m_pUnitGroup = nullptr;
|
|
|
m_sBaseUnit = oDescriptor.m_sBaseUnit;
|
|
|
m_sDisplayUnit = oDescriptor.m_sDefaultDisplayUnit.isEmpty()
|
|
|
? m_sBaseUnit : oDescriptor.m_sDefaultDisplayUnit;
|
|
|
if(m_sBaseUnit.isEmpty()) {
|
|
|
m_sDisplayUnit.clear();
|
|
|
m_pUnitCombo->setEnabled(false);
|
|
|
m_pUnitCombo->hide();
|
|
|
m_pUnitCombo->blockSignals(bSignalsBlocked);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
m_pUnitGroup = iUnitHelper::getUnitGroupByUnit(m_sBaseUnit);
|
|
|
QStringList listUnits;
|
|
|
if(m_pUnitGroup != nullptr &&
|
|
|
m_pUnitGroup->indexOf(m_sBaseUnit) >= 0) {
|
|
|
listUnits = m_pUnitGroup->getAllUnitNames();
|
|
|
}
|
|
|
const bool bHasUnitGroup = !listUnits.isEmpty() &&
|
|
|
listUnits.contains(m_sBaseUnit);
|
|
|
if(!bHasUnitGroup) {
|
|
|
m_pUnitGroup = nullptr;
|
|
|
listUnits.clear();
|
|
|
listUnits.append(m_sBaseUnit);
|
|
|
m_sDisplayUnit = m_sBaseUnit;
|
|
|
} else if(!listUnits.contains(m_sDisplayUnit)) {
|
|
|
m_sDisplayUnit = m_sBaseUnit;
|
|
|
}
|
|
|
|
|
|
m_pUnitCombo->addItems(listUnits);
|
|
|
m_pUnitCombo->setCurrentIndex(
|
|
|
m_pUnitCombo->findText(m_sDisplayUnit));
|
|
|
m_pUnitCombo->setEnabled(bHasUnitGroup);
|
|
|
m_pUnitCombo->show();
|
|
|
m_pUnitCombo->blockSignals(bSignalsBlocked);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::populateSettings(
|
|
|
const nmPebiResultFilterSettings& oSettings)
|
|
|
{
|
|
|
const bool bPropertySignalsBlocked =
|
|
|
m_pPropertyCombo->blockSignals(true);
|
|
|
const int nPropertyIndex = m_pPropertyCombo->findData(
|
|
|
oSettings.m_sPropertyId);
|
|
|
if(nPropertyIndex < 0 ||
|
|
|
!initializeUnitEditor(oSettings.m_sPropertyId)) {
|
|
|
m_pPropertyCombo->blockSignals(bPropertySignalsBlocked);
|
|
|
return false;
|
|
|
}
|
|
|
m_pPropertyCombo->setCurrentIndex(nPropertyIndex);
|
|
|
m_pPropertyCombo->blockSignals(bPropertySignalsBlocked);
|
|
|
|
|
|
double dDisplayMinimum = 0.0;
|
|
|
double dDisplayMaximum = 0.0;
|
|
|
if(!convertUnitValue(
|
|
|
oSettings.m_dMinimum, m_sBaseUnit,
|
|
|
m_sDisplayUnit, dDisplayMinimum) ||
|
|
|
!convertUnitValue(
|
|
|
oSettings.m_dMaximum, m_sBaseUnit,
|
|
|
m_sDisplayUnit, dDisplayMaximum)) {
|
|
|
return false;
|
|
|
}
|
|
|
m_pEnableCheck->setChecked(oSettings.m_bEnabled);
|
|
|
m_pMinimumCheck->setChecked(oSettings.m_bMinimumEnabled);
|
|
|
m_pMaximumCheck->setChecked(oSettings.m_bMaximumEnabled);
|
|
|
m_pMinimumEdit->setText(formatValue(dDisplayMinimum));
|
|
|
m_pMaximumEdit->setText(formatValue(dDisplayMaximum));
|
|
|
refreshEditorEnabledState();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultFilterSettingsDlg::onApplyClicked()
|
|
|
{
|
|
|
applyEditor();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultFilterSettingsDlg::onOkClicked()
|
|
|
{
|
|
|
if(applyEditor()) {
|
|
|
accept();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultFilterSettingsDlg::onPropertyChanged(int nIndex)
|
|
|
{
|
|
|
if(nIndex >= 0 &&
|
|
|
!setPropertyRange(
|
|
|
m_pPropertyCombo->itemData(nIndex).toString())) {
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Filtering settings"),
|
|
|
tr("Unable to convert the filter limits "
|
|
|
"to the selected unit."));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultFilterSettingsDlg::onDisplayUnitChanged(int nIndex)
|
|
|
{
|
|
|
if(nIndex < 0) {
|
|
|
return;
|
|
|
}
|
|
|
const QString sNewUnit = m_pUnitCombo->itemText(nIndex);
|
|
|
if(sNewUnit == m_sDisplayUnit) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
double dOldDisplayMinimum = 0.0;
|
|
|
double dOldDisplayMaximum = 0.0;
|
|
|
double dBaseMinimum = 0.0;
|
|
|
double dBaseMaximum = 0.0;
|
|
|
double dNewDisplayMinimum = 0.0;
|
|
|
double dNewDisplayMaximum = 0.0;
|
|
|
const bool bConverted =
|
|
|
parseFiniteValue(
|
|
|
m_pMinimumEdit->text(), dOldDisplayMinimum) &&
|
|
|
parseFiniteValue(
|
|
|
m_pMaximumEdit->text(), dOldDisplayMaximum) &&
|
|
|
convertUnitValue(
|
|
|
dOldDisplayMinimum, m_sDisplayUnit,
|
|
|
m_sBaseUnit, dBaseMinimum) &&
|
|
|
convertUnitValue(
|
|
|
dOldDisplayMaximum, m_sDisplayUnit,
|
|
|
m_sBaseUnit, dBaseMaximum) &&
|
|
|
convertUnitValue(
|
|
|
dBaseMinimum, m_sBaseUnit,
|
|
|
sNewUnit, dNewDisplayMinimum) &&
|
|
|
convertUnitValue(
|
|
|
dBaseMaximum, m_sBaseUnit,
|
|
|
sNewUnit, dNewDisplayMaximum);
|
|
|
if(!bConverted) {
|
|
|
const bool bSignalsBlocked = m_pUnitCombo->blockSignals(true);
|
|
|
m_pUnitCombo->setCurrentIndex(
|
|
|
m_pUnitCombo->findText(m_sDisplayUnit));
|
|
|
m_pUnitCombo->blockSignals(bSignalsBlocked);
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Filtering settings"),
|
|
|
tr("Unable to convert the filter limits "
|
|
|
"to the selected unit."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
m_sDisplayUnit = sNewUnit;
|
|
|
m_pMinimumEdit->setText(formatValue(dNewDisplayMinimum));
|
|
|
m_pMaximumEdit->setText(formatValue(dNewDisplayMaximum));
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultFilterSettingsDlg::refreshEditorEnabledState()
|
|
|
{
|
|
|
const bool bEnabled = m_pEnableCheck->isChecked();
|
|
|
m_pPropertyCombo->setEnabled(bEnabled);
|
|
|
m_pMinimumCheck->setEnabled(bEnabled);
|
|
|
m_pMaximumCheck->setEnabled(bEnabled);
|
|
|
m_pMinimumEdit->setEnabled(
|
|
|
bEnabled && m_pMinimumCheck->isChecked());
|
|
|
m_pMaximumEdit->setEnabled(
|
|
|
bEnabled && m_pMaximumCheck->isChecked());
|
|
|
m_pUnitCombo->setEnabled(
|
|
|
bEnabled && m_pUnitGroup != nullptr);
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::collectSettings(
|
|
|
nmPebiResultFilterSettings& oSettings,
|
|
|
QString& sError) const
|
|
|
{
|
|
|
sError.clear();
|
|
|
const int nPropertyIndex = m_pPropertyCombo->currentIndex();
|
|
|
if(nPropertyIndex < 0) {
|
|
|
sError = tr("A filter property must be selected.");
|
|
|
return false;
|
|
|
}
|
|
|
oSettings.m_bEnabled = m_pEnableCheck->isChecked();
|
|
|
oSettings.m_sPropertyId =
|
|
|
m_pPropertyCombo->itemData(nPropertyIndex).toString();
|
|
|
oSettings.m_bMinimumEnabled = m_pMinimumCheck->isChecked();
|
|
|
oSettings.m_bMaximumEnabled = m_pMaximumCheck->isChecked();
|
|
|
// 复选框只控制边界是否参与筛选;未启用边界的编辑值仍需保留,
|
|
|
// 避免重新打开对话框时退回设置对象的默认 0。
|
|
|
double dDisplayMinimum = 0.0;
|
|
|
double dDisplayMaximum = 0.0;
|
|
|
if(!parseFiniteValue(
|
|
|
m_pMinimumEdit->text(), dDisplayMinimum) ||
|
|
|
!parseFiniteValue(
|
|
|
m_pMaximumEdit->text(), dDisplayMaximum)) {
|
|
|
sError = tr("Filter limits must be finite numbers.");
|
|
|
return false;
|
|
|
}
|
|
|
if(!convertUnitValue(
|
|
|
dDisplayMinimum, m_sDisplayUnit,
|
|
|
m_sBaseUnit, oSettings.m_dMinimum) ||
|
|
|
!convertUnitValue(
|
|
|
dDisplayMaximum, m_sDisplayUnit,
|
|
|
m_sBaseUnit, oSettings.m_dMaximum)) {
|
|
|
sError = tr(
|
|
|
"Unable to convert the filter limits "
|
|
|
"to the selected unit.");
|
|
|
return false;
|
|
|
}
|
|
|
if(oSettings.m_bMinimumEnabled &&
|
|
|
oSettings.m_bMaximumEnabled &&
|
|
|
oSettings.m_dMinimum > oSettings.m_dMaximum) {
|
|
|
sError = tr("The minimum filter limit cannot exceed the maximum.");
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::applyEditor()
|
|
|
{
|
|
|
nmPebiResultPlaybackController* pPlaybackController =
|
|
|
m_pControlBar == nullptr ? nullptr :
|
|
|
m_pControlBar->getPlaybackController();
|
|
|
if(!m_bInitialized || m_pSceneController == nullptr ||
|
|
|
m_pControlBar == nullptr || pPlaybackController == nullptr ||
|
|
|
pPlaybackController->isPlaying() ||
|
|
|
m_pControlBar->isExportInProgress()) {
|
|
|
const QString sError = tr(
|
|
|
"Filtering is unavailable during playback or export.");
|
|
|
QMessageBox::warning(this, tr("Filtering settings"), sError);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmPebiResultFilterSettings oSettings;
|
|
|
QString sError;
|
|
|
if(!collectSettings(oSettings, sError) ||
|
|
|
!m_pSceneController->applyFilterSettings(
|
|
|
m_nExpectedBindingRevision,
|
|
|
m_sExpectedSnapshotId,
|
|
|
m_sExpectedDisplayPropertyId,
|
|
|
m_nExpectedDisplayFrameIndex,
|
|
|
oSettings, &sError)) {
|
|
|
if(sError.isEmpty()) {
|
|
|
sError = tr("Unable to apply the filter settings.");
|
|
|
}
|
|
|
QMessageBox::warning(this, tr("Filtering settings"), sError);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
m_oBaselineSettings = oSettings;
|
|
|
emit filterSettingsApplied();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::setPropertyRange(
|
|
|
const QString& sPropertyId)
|
|
|
{
|
|
|
if(!m_mapPropertyRanges.contains(sPropertyId) ||
|
|
|
!initializeUnitEditor(sPropertyId)) {
|
|
|
return false;
|
|
|
}
|
|
|
const QPair<double, double> oRange =
|
|
|
m_mapPropertyRanges.value(sPropertyId);
|
|
|
double dDisplayMinimum = 0.0;
|
|
|
double dDisplayMaximum = 0.0;
|
|
|
if(!convertUnitValue(
|
|
|
oRange.first, m_sBaseUnit,
|
|
|
m_sDisplayUnit, dDisplayMinimum) ||
|
|
|
!convertUnitValue(
|
|
|
oRange.second, m_sBaseUnit,
|
|
|
m_sDisplayUnit, dDisplayMaximum)) {
|
|
|
return false;
|
|
|
}
|
|
|
m_pMinimumCheck->setChecked(true);
|
|
|
m_pMaximumCheck->setChecked(true);
|
|
|
m_pMinimumEdit->setText(formatValue(dDisplayMinimum));
|
|
|
m_pMaximumEdit->setText(formatValue(dDisplayMaximum));
|
|
|
refreshEditorEnabledState();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
QString nmWxPebiResultFilterSettingsDlg::formatValue(double dValue) const
|
|
|
{
|
|
|
return QLocale::c().toString(dValue, 'g', 12);
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::parseFiniteValue(
|
|
|
const QString& sText,
|
|
|
double& dValue) const
|
|
|
{
|
|
|
bool bOk = false;
|
|
|
const double dCandidate = QLocale::c().toDouble(
|
|
|
sText.trimmed(), &bOk);
|
|
|
if(!bOk || !isFiniteFilterEditorValue(dCandidate)) {
|
|
|
return false;
|
|
|
}
|
|
|
dValue = dCandidate;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultFilterSettingsDlg::convertUnitValue(
|
|
|
double dSourceValue,
|
|
|
const QString& sSourceUnit,
|
|
|
const QString& sDestinationUnit,
|
|
|
double& dDestinationValue) const
|
|
|
{
|
|
|
if(!isFiniteFilterEditorValue(dSourceValue)) {
|
|
|
return false;
|
|
|
}
|
|
|
if(sSourceUnit == sDestinationUnit) {
|
|
|
dDestinationValue = dSourceValue;
|
|
|
return true;
|
|
|
}
|
|
|
if(sSourceUnit.isEmpty() || sDestinationUnit.isEmpty() ||
|
|
|
m_pUnitGroup == nullptr ||
|
|
|
m_pUnitGroup->indexOf(sSourceUnit) < 0 ||
|
|
|
m_pUnitGroup->indexOf(sDestinationUnit) < 0) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
int nDigit = 12;
|
|
|
double dCandidate = 0.0;
|
|
|
if(!m_pUnitGroup->convert(
|
|
|
sSourceUnit, dSourceValue,
|
|
|
sDestinationUnit, dCandidate, nDigit) ||
|
|
|
!isFiniteFilterEditorValue(dCandidate)) {
|
|
|
return false;
|
|
|
}
|
|
|
dDestinationValue = dCandidate;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/** @brief 外部三维模式的窗口私有状态,所有成员仅由 GUI 线程管理。 */
|
|
|
class nmWxPebiVolumeState
|
|
|
{
|
|
|
public:
|
|
|
/** @brief 初始化为空模式;控制器在通用渲染容器创建后绑定。 */
|
|
|
nmWxPebiVolumeState()
|
|
|
: m_pController(nullptr), m_pTask(nullptr), m_pProgress(nullptr),
|
|
|
m_pImportAction(nullptr), m_pReturnAction(nullptr), m_pButton(nullptr),
|
|
|
m_pSliceAction(nullptr), m_pSliceSettingsAction(nullptr),
|
|
|
m_pSliceDialog(nullptr), m_pSliceInteraction(nullptr),
|
|
|
m_bImported(false), m_bPendingSync(false), m_bRegionVisible(false),
|
|
|
m_bRegionDialogVisible(false) {}
|
|
|
nmVolumeSceneController* m_pController; ///< 窗口独占,必须在渲染容器之前析构。
|
|
|
nmVolumeImportTask* m_pTask; ///< 当前唯一导入线程,结束后才允许读取结果。
|
|
|
QProgressDialog* m_pProgress; ///< 临时进度框,窗口对象树拥有。
|
|
|
QAction* m_pImportAction; ///< 按钮菜单和右键菜单共享的导入动作。
|
|
|
QAction* m_pReturnAction; ///< 仅外部模型模式启用的返回动作。
|
|
|
QToolButton* m_pButton; ///< 唯一新增的常驻切片工具按钮。
|
|
|
QAction* m_pSliceAction; ///< 按钮菜单和右键菜单共享的切片显隐动作。
|
|
|
QAction* m_pSliceSettingsAction; ///< 打开浮动切片参数窗口的共享动作。
|
|
|
nmWxVolumeSliceSettingsDlg* m_pSliceDialog; ///< 按需创建并复用的非模态参数窗口。
|
|
|
nmVolumeSliceInteraction* m_pSliceInteraction; ///< 窗口独占,负责辅助平面及安全事件转发。
|
|
|
nmVTKViewState m_oSavedView; ///< 第一次进入外部模式前捕获的计算结果视图。
|
|
|
nmVTKViewCommandController::ViewCommands m_eSavedCommands; ///< 原结果提供的视图命令能力。
|
|
|
bool m_bImported; ///< 当前画面是否为外部三维模型。
|
|
|
bool m_bPendingSync; ///< 外部模式期间发生 Manager 变化,返回时重新读取。
|
|
|
bool m_bRegionVisible; ///< 原区域轮廓的实际可见状态。
|
|
|
bool m_bRegionDialogVisible; ///< 原区域统计面板是否可见。
|
|
|
};
|
|
|
|
|
|
nmWxPebiResultViewWidget::nmWxPebiResultViewWidget(
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
QWidget* pParent)
|
|
|
: QWidget(pParent),
|
|
|
m_pRenderContainer(nullptr),
|
|
|
m_pSceneController(nullptr),
|
|
|
m_pControlBar(nullptr),
|
|
|
m_pToolBar(nullptr),
|
|
|
m_pStatusLabel(nullptr),
|
|
|
m_pScalarBarAction(nullptr),
|
|
|
m_pScaleGridAction(nullptr),
|
|
|
m_pWellNamesAction(nullptr),
|
|
|
m_pFilterAction(nullptr),
|
|
|
m_pRegionPressureAction(nullptr),
|
|
|
m_pRegionState(new nmWxPebiResultRegionState),
|
|
|
m_pVolumeState(new nmWxPebiVolumeState),
|
|
|
m_bObservedSnapshotCurrent(false),
|
|
|
m_bObservedStateValid(false),
|
|
|
m_bSynchronizing(false),
|
|
|
m_bSyncPending(false)
|
|
|
{
|
|
|
createUi();
|
|
|
setDataManager(pDataManager);
|
|
|
}
|
|
|
|
|
|
nmWxPebiResultViewWidget::~nmWxPebiResultViewWidget()
|
|
|
{
|
|
|
// 先取消并断开队列回调,等待线程退出后才能释放窗口和相关 DLL 对象。
|
|
|
if(m_pVolumeState->m_pTask != nullptr) {
|
|
|
disconnect(m_pVolumeState->m_pTask, nullptr, this, nullptr);
|
|
|
m_pVolumeState->m_pTask->requestCancel();
|
|
|
m_pVolumeState->m_pTask->wait();
|
|
|
delete m_pVolumeState->m_pTask;
|
|
|
}
|
|
|
closeVolumeSliceSettings(true);
|
|
|
delete m_pVolumeState->m_pSliceDialog;
|
|
|
m_pVolumeState->m_pSliceDialog = nullptr;
|
|
|
delete m_pVolumeState->m_pSliceInteraction;
|
|
|
m_pVolumeState->m_pSliceInteraction = nullptr;
|
|
|
if(m_pControlBar != nullptr) m_pControlBar->setVolumeSceneController(nullptr);
|
|
|
delete m_pVolumeState->m_pController;
|
|
|
delete m_pVolumeState;
|
|
|
m_pVolumeState = nullptr;
|
|
|
disconnectDataManager();
|
|
|
destroyRegionPressureState();
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
if(m_pVolumeState->m_pTask != nullptr) m_pVolumeState->m_pTask->requestCancel();
|
|
|
disconnectDataManager();
|
|
|
if(isImportedVolumeMode()) {
|
|
|
// 分析上下文换绑时丢弃旧计算结果,但保留用户正在检查的外部模型。
|
|
|
clearRegionPressureSelection(false);
|
|
|
m_pSceneController->clear();
|
|
|
m_pDisplayedSnapshot.clear();
|
|
|
m_sDisplayedSnapshotId.clear();
|
|
|
m_pVolumeState->m_oSavedView = nmVTKViewState();
|
|
|
m_pVolumeState->m_bRegionVisible = false;
|
|
|
m_pVolumeState->m_bRegionDialogVisible = false;
|
|
|
m_pVolumeState->m_bPendingSync = true;
|
|
|
} else {
|
|
|
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(isImportedVolumeMode()) {
|
|
|
m_pVolumeState->m_bPendingSync = true;
|
|
|
return;
|
|
|
}
|
|
|
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_pVolumeState->m_pImportAction);
|
|
|
pMenu->addSeparator();
|
|
|
pMenu->addAction(m_pVolumeState->m_pSliceAction);
|
|
|
pMenu->addAction(m_pVolumeState->m_pSliceSettingsAction);
|
|
|
pMenu->addSeparator();
|
|
|
pMenu->addAction(m_pVolumeState->m_pReturnAction);
|
|
|
pMenu->addSeparator();
|
|
|
pMenu->addAction(m_pFilterAction);
|
|
|
pMenu->addAction(m_pRegionPressureAction);
|
|
|
pMenu->addSeparator();
|
|
|
pMenu->addAction(m_pScalarBarAction);
|
|
|
pMenu->addAction(m_pScaleGridAction);
|
|
|
pMenu->addAction(m_pWellNamesAction);
|
|
|
if(m_pControlBar != nullptr &&
|
|
|
m_pControlBar->getExportAnimationAction() != nullptr) {
|
|
|
pMenu->addSeparator();
|
|
|
pMenu->addAction(m_pControlBar->getExportAnimationAction());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onControlOperationFailed(
|
|
|
const QString& sError)
|
|
|
{
|
|
|
// 绑定失败优先显示;输入修改不再提示,也不屏蔽后续真实操作错误。
|
|
|
if(!isImportedVolumeMode() && !m_sBindingError.isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
m_sOperationError = sError;
|
|
|
refreshStatusBar();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onControlOperationSucceeded()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) {
|
|
|
// 底栏的属性切换只同步已应用属性,不覆盖浮动窗口里尚未提交的平面坐标。
|
|
|
if(m_pVolumeState->m_pSliceDialog != nullptr) {
|
|
|
m_pVolumeState->m_pSliceDialog->setAppliedProperty(
|
|
|
m_pVolumeState->m_pController->getSliceSettings().m_sPropertyId);
|
|
|
}
|
|
|
m_sOperationError.clear();
|
|
|
synchronizeVolumeActions();
|
|
|
refreshStatusBar();
|
|
|
return;
|
|
|
}
|
|
|
if(m_sOperationError.isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
// 这里只清除普通操作错误;刷新函数仍优先显示绑定失败。
|
|
|
m_sOperationError.clear();
|
|
|
refreshStatusBar();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onViewportSizeChanged(
|
|
|
int nWidth,
|
|
|
int nHeight)
|
|
|
{
|
|
|
if(m_pVolumeState->m_pController != nullptr) m_pVolumeState->m_pController->setViewportSize(nWidth, nHeight);
|
|
|
if(m_pSceneController != nullptr) {
|
|
|
// SceneController 缓存尺寸,即使当前尚未创建结果色标也不会丢失。
|
|
|
m_pSceneController->setViewportSize(nWidth, nHeight);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onShowFilterSettings()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) return;
|
|
|
nmPebiResultPlaybackController* pPlaybackController =
|
|
|
m_pControlBar == nullptr ? nullptr :
|
|
|
m_pControlBar->getPlaybackController();
|
|
|
if(m_pSceneController == nullptr || m_pControlBar == nullptr ||
|
|
|
pPlaybackController == nullptr ||
|
|
|
!m_pSceneController->hasSnapshot() ||
|
|
|
pPlaybackController->isPlaying() ||
|
|
|
m_pControlBar->isExportInProgress() ||
|
|
|
m_pControlBar->isExternalInteractionLocked()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmWxPebiResultFilterSettingsDlg oDialog(
|
|
|
m_pSceneController, m_pControlBar, this);
|
|
|
connect(&oDialog, SIGNAL(filterSettingsApplied()),
|
|
|
this, SLOT(onControlOperationSucceeded()));
|
|
|
oDialog.exec();
|
|
|
synchronizeBusinessActions();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onRegionPressureStatisticsTriggered()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) return;
|
|
|
if(m_pRegionState == nullptr || m_pControlBar == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 共享 QAction 根据当前状态分别表示:取消绘制、重显结果或开始新绘制。
|
|
|
if(isRegionPressureDrawing()) {
|
|
|
clearRegionPressureSelection(true);
|
|
|
onControlOperationSucceeded();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(m_pRegionState->m_eState ==
|
|
|
nmWxPebiResultRegionState::State_Completed) {
|
|
|
showRegionPressureStatisticsDialog();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmPebiResultPlaybackController* pPlaybackController =
|
|
|
m_pControlBar->getPlaybackController();
|
|
|
if(pPlaybackController == nullptr ||
|
|
|
pPlaybackController->isPlaying() ||
|
|
|
m_pControlBar->isExportInProgress() ||
|
|
|
m_pControlBar->isExternalInteractionLocked()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(m_pRenderContainer == nullptr ||
|
|
|
!canProjectRegionOntoXyPlane(
|
|
|
m_pRenderContainer->getRenderer())) {
|
|
|
onControlOperationFailed(tr(
|
|
|
"Switch to Top View or Isometric View before drawing a pressure region."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(!startRegionPressureDrawing()) {
|
|
|
onControlOperationFailed(tr(
|
|
|
"Unable to start region pressure drawing."));
|
|
|
} else {
|
|
|
onControlOperationSucceeded();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::handleRegionPolygonEvent(
|
|
|
vtkObject* pCaller,
|
|
|
unsigned long nEventId,
|
|
|
void* pClientData,
|
|
|
void* pCallData)
|
|
|
{
|
|
|
Q_UNUSED(pCallData);
|
|
|
nmWxPebiResultViewWidget* pView =
|
|
|
static_cast<nmWxPebiResultViewWidget*>(pClientData);
|
|
|
if(pView == nullptr || pView->m_pRegionState == nullptr ||
|
|
|
pView->m_pRegionState->m_eState !=
|
|
|
nmWxPebiResultRegionState::State_Drawing ||
|
|
|
pCaller != pView->m_pRegionState->m_pStyle.GetPointer()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmWxPebiResultRegionState* pState = pView->m_pRegionState;
|
|
|
if(nEventId ==
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonChangedEvent) {
|
|
|
nmVTKScene* pScene = pView->m_pRenderContainer == nullptr
|
|
|
? nullptr : pView->m_pRenderContainer->getScene();
|
|
|
if(pScene != nullptr && pState->m_pLayer != nullptr &&
|
|
|
pScene->getLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID)) == pState->m_pLayer) {
|
|
|
pState->m_pLayer->setPolygon(
|
|
|
pState->m_pStyle->getPoints(),
|
|
|
pState->m_pStyle->isCompleted());
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// VTK 回调内只复制小量状态;场景切换、对话框和统计计算延后到 Qt 事件循环。
|
|
|
nmWxPebiResultRegionState::PendingEvent ePendingEvent =
|
|
|
nmWxPebiResultRegionState::PendingEvent_None;
|
|
|
if(nEventId ==
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonCompletedEvent) {
|
|
|
ePendingEvent =
|
|
|
nmWxPebiResultRegionState::PendingEvent_Completed;
|
|
|
pState->m_vecPendingPoints = pState->m_pStyle->getPoints();
|
|
|
} else if(nEventId ==
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonCancelledEvent) {
|
|
|
ePendingEvent =
|
|
|
nmWxPebiResultRegionState::PendingEvent_Cancelled;
|
|
|
} else if(nEventId ==
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonInvalidEvent) {
|
|
|
ePendingEvent =
|
|
|
nmWxPebiResultRegionState::PendingEvent_Invalid;
|
|
|
} else {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 完成或取消优先于尚未处理的无效提示,且每次只排一个 Qt 收尾任务。
|
|
|
if(pState->m_bPendingEventQueued) {
|
|
|
if(pState->m_ePendingEvent ==
|
|
|
nmWxPebiResultRegionState::PendingEvent_Invalid &&
|
|
|
ePendingEvent !=
|
|
|
nmWxPebiResultRegionState::PendingEvent_Invalid) {
|
|
|
pState->m_ePendingEvent = ePendingEvent;
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
pState->m_ePendingEvent = ePendingEvent;
|
|
|
pState->m_bPendingEventQueued = true;
|
|
|
QMetaObject::invokeMethod(
|
|
|
pView, "processPendingRegionInteractionEvent",
|
|
|
Qt::QueuedConnection);
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::processPendingRegionInteractionEvent()
|
|
|
{
|
|
|
if(m_pRegionState == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const nmWxPebiResultRegionState::PendingEvent ePendingEvent =
|
|
|
m_pRegionState->m_ePendingEvent;
|
|
|
const QVector<QPointF> vecPoints =
|
|
|
m_pRegionState->m_vecPendingPoints;
|
|
|
m_pRegionState->m_ePendingEvent =
|
|
|
nmWxPebiResultRegionState::PendingEvent_None;
|
|
|
m_pRegionState->m_bPendingEventQueued = false;
|
|
|
m_pRegionState->m_vecPendingPoints.clear();
|
|
|
|
|
|
if(!isRegionPressureDrawing()) {
|
|
|
return;
|
|
|
}
|
|
|
if(ePendingEvent ==
|
|
|
nmWxPebiResultRegionState::PendingEvent_Invalid) {
|
|
|
onControlOperationFailed(tr(
|
|
|
"Draw a non-degenerate region with at least three points."));
|
|
|
return;
|
|
|
}
|
|
|
if(ePendingEvent ==
|
|
|
nmWxPebiResultRegionState::PendingEvent_Cancelled) {
|
|
|
clearRegionPressureSelection(true);
|
|
|
onControlOperationSucceeded();
|
|
|
return;
|
|
|
}
|
|
|
if(ePendingEvent !=
|
|
|
nmWxPebiResultRegionState::PendingEvent_Completed) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 绘制期间快照、属性或帧发生变化时,不能把旧坐标区域提交到新结果。
|
|
|
const bool bIdentityMatches =
|
|
|
m_pSceneController != nullptr &&
|
|
|
m_pSceneController->hasSnapshot() &&
|
|
|
!m_pDisplayedSnapshot.isNull() &&
|
|
|
m_pDisplayedSnapshot.data() ==
|
|
|
m_pRegionState->m_pFrozenSnapshot.data() &&
|
|
|
m_pSceneController->getSnapshotBindingRevision() ==
|
|
|
m_pRegionState->m_nFrozenBindingRevision &&
|
|
|
m_pSceneController->getCurrentSnapshotId() ==
|
|
|
m_pRegionState->m_sFrozenSnapshotId &&
|
|
|
m_sDisplayedSnapshotId ==
|
|
|
m_pRegionState->m_sFrozenSnapshotId &&
|
|
|
m_pSceneController->getCurrentPropertyId() ==
|
|
|
m_pRegionState->m_sFrozenPropertyId &&
|
|
|
m_pSceneController->getCurrentFrameIndex() ==
|
|
|
m_pRegionState->m_nFrozenFrameIndex;
|
|
|
if(!bIdentityMatches) {
|
|
|
clearRegionPressureSelection(true);
|
|
|
onControlOperationFailed(tr(
|
|
|
"The displayed result changed while the region was drawn."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Calculator::prepare() 事务式建立相交单元和静态权重缓存。
|
|
|
QString sError;
|
|
|
if(!m_pRegionState->m_oCalculator.prepare(
|
|
|
m_pRegionState->m_pFrozenSnapshot, vecPoints, &sError)) {
|
|
|
clearRegionPressureSelection(true);
|
|
|
onControlOperationFailed(tr(
|
|
|
"Unable to prepare the selected pressure region."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmPebiRegionPressureStatistics oStatistics;
|
|
|
if(!m_pRegionState->m_oCalculator.calculateFrame(
|
|
|
m_pRegionState->m_nFrozenFrameIndex,
|
|
|
oStatistics, &sError)) {
|
|
|
clearRegionPressureSelection(true);
|
|
|
onControlOperationFailed(tr(
|
|
|
"Unable to calculate region pressure statistics."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmVTKScene* pScene = m_pRenderContainer == nullptr
|
|
|
? nullptr : m_pRenderContainer->getScene();
|
|
|
if(pScene == nullptr || m_pRegionState->m_pLayer == nullptr ||
|
|
|
pScene->getLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID)) != m_pRegionState->m_pLayer ||
|
|
|
!m_pRegionState->m_pLayer->setPolygon(
|
|
|
m_pRegionState->m_oCalculator.getPolygonPoints(), true)) {
|
|
|
clearRegionPressureSelection(true);
|
|
|
onControlOperationFailed(tr(
|
|
|
"Unable to preserve the selected pressure region."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 几何、计算和轮廓全部成功后才将窗口状态提交为 Completed。
|
|
|
m_pRegionState->m_nRegionBindingRevision =
|
|
|
m_pRegionState->m_nFrozenBindingRevision;
|
|
|
m_pRegionState->m_sRegionSnapshotId =
|
|
|
m_pRegionState->m_sFrozenSnapshotId;
|
|
|
m_pRegionState->m_nLastAttemptedFrameIndex =
|
|
|
m_pRegionState->m_nFrozenFrameIndex;
|
|
|
m_pRegionState->m_bStatisticsCalculated = true;
|
|
|
m_pRegionState->m_sStatisticsError.clear();
|
|
|
m_pRegionState->m_oStatistics = oStatistics;
|
|
|
m_pRegionState->m_sLastObservedPropertyId =
|
|
|
QString::fromLatin1("pressure");
|
|
|
m_pRegionState->m_eState =
|
|
|
nmWxPebiResultRegionState::State_Completed;
|
|
|
stopRegionPressureDrawing();
|
|
|
onControlOperationSucceeded();
|
|
|
showRegionPressureStatisticsDialog();
|
|
|
synchronizeBusinessActions();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onResultDisplayStateChanged()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) return;
|
|
|
if(m_pRegionState != nullptr && m_pSceneController != nullptr &&
|
|
|
m_pRegionState->m_eState ==
|
|
|
nmWxPebiResultRegionState::State_Completed) {
|
|
|
const QString sCurrentPropertyId =
|
|
|
m_pSceneController->getCurrentPropertyId();
|
|
|
if(sCurrentPropertyId == QString::fromLatin1("pressure") &&
|
|
|
m_pRegionState->m_sLastObservedPropertyId !=
|
|
|
sCurrentPropertyId) {
|
|
|
// 从静态属性切回 pressure 时必须重新评估第 0 帧,即使曾统计过该帧。
|
|
|
m_pRegionState->m_nLastAttemptedFrameIndex = -1;
|
|
|
}
|
|
|
m_pRegionState->m_sLastObservedPropertyId = sCurrentPropertyId;
|
|
|
}
|
|
|
synchronizeBusinessActions();
|
|
|
updateRegionPressureStatistics();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onCloseRegionPressureStatistics()
|
|
|
{
|
|
|
if(m_pRegionState == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
clearRegionPressureSelection(true);
|
|
|
onControlOperationSucceeded();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onExportAnimationRequested()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) return;
|
|
|
nmPebiResultPlaybackController* pPlaybackController =
|
|
|
m_pControlBar == nullptr ? nullptr :
|
|
|
m_pControlBar->getPlaybackController();
|
|
|
QString sBackendError;
|
|
|
if(m_pSceneController == nullptr || m_pRenderContainer == nullptr ||
|
|
|
pPlaybackController == nullptr ||
|
|
|
pPlaybackController->isPlaying() ||
|
|
|
(m_pControlBar != nullptr &&
|
|
|
m_pControlBar->isExternalInteractionLocked()) ||
|
|
|
!m_pSceneController->hasSnapshot() ||
|
|
|
m_pDisplayedSnapshot.isNull() ||
|
|
|
!nmFFmpegVideoWriter::isBackendAvailable(&sBackendError)) {
|
|
|
if(!sBackendError.isEmpty()) {
|
|
|
QMessageBox::warning(this, tr("Export Animation"), sBackendError);
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
AnimationExportGuard oExportGuard(m_pControlBar);
|
|
|
if(!oExportGuard.isAcquired()) {
|
|
|
QMessageBox::information(
|
|
|
this, tr("Export Animation"),
|
|
|
tr("Another result animation export is already in progress."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const QSharedPointer<const nmPebiResultSnapshot> pFrozenSnapshot =
|
|
|
m_pDisplayedSnapshot;
|
|
|
const quint64 nFrozenBindingRevision =
|
|
|
m_pSceneController->getSnapshotBindingRevision();
|
|
|
const QString sFrozenSnapshotId =
|
|
|
m_pSceneController->getCurrentSnapshotId();
|
|
|
const QString sFrozenPropertyId =
|
|
|
m_pSceneController->getCurrentPropertyId();
|
|
|
const nmVTKMeshLayer::DisplayMode eFrozenMeshDisplayMode =
|
|
|
m_pSceneController->getMeshDisplayMode();
|
|
|
nmPebiResultDisplaySettings oFrozenDisplaySettings;
|
|
|
nmPebiResultFilterSettings oFrozenFilterSettings;
|
|
|
if(!m_pSceneController->getCurrentDisplaySettings(
|
|
|
oFrozenDisplaySettings) ||
|
|
|
!m_pSceneController->getFilterSettings(
|
|
|
oFrozenFilterSettings)) {
|
|
|
return;
|
|
|
}
|
|
|
const int nFrozenFilterFrameIndex =
|
|
|
m_pSceneController->getFilterFrameIndex();
|
|
|
const int nFrameCount = pFrozenSnapshot->getPropertyFrameCount(
|
|
|
sFrozenPropertyId);
|
|
|
if(nFrozenBindingRevision == 0 || sFrozenSnapshotId.isEmpty() ||
|
|
|
sFrozenPropertyId.isEmpty() || nFrameCount <= 1 ||
|
|
|
!nmVTKMeshLayer::isValidDisplayMode(eFrozenMeshDisplayMode) ||
|
|
|
(oFrozenFilterSettings.m_bEnabled &&
|
|
|
nFrozenFilterFrameIndex < 0) ||
|
|
|
!m_pSceneController->isPropertyTimeDependent(sFrozenPropertyId)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
QVector<double> vecFrameTimes;
|
|
|
vecFrameTimes.reserve(nFrameCount);
|
|
|
for(int nFrameIndex = 0; nFrameIndex < nFrameCount; ++nFrameIndex) {
|
|
|
double dTimeHours = 0.0;
|
|
|
if(!pFrozenSnapshot->getPropertyTimeAt(
|
|
|
sFrozenPropertyId, nFrameIndex, dTimeHours)) {
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Export Animation"),
|
|
|
tr("Unable to read the result animation timeline."));
|
|
|
return;
|
|
|
}
|
|
|
vecFrameTimes.append(dTimeHours);
|
|
|
}
|
|
|
|
|
|
nmWxPebiResultAnimationExportDlg oSettingsDialog(
|
|
|
vecFrameTimes, m_pRenderContainer->getViewportSize(), this);
|
|
|
if(oSettingsDialog.exec() != QDialog::Accepted) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 参数窗口内的文件浏览也会形成嵌套事件循环,开始前重新核对完整显示身份。
|
|
|
if(m_pDisplayedSnapshot.data() != pFrozenSnapshot.data() ||
|
|
|
m_pSceneController->getSnapshotBindingRevision() !=
|
|
|
nFrozenBindingRevision ||
|
|
|
m_pSceneController->getCurrentSnapshotId() != sFrozenSnapshotId ||
|
|
|
m_pSceneController->getCurrentPropertyId() != sFrozenPropertyId ||
|
|
|
m_pSceneController->getMeshDisplayMode() !=
|
|
|
eFrozenMeshDisplayMode) {
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Export Animation"),
|
|
|
tr("The displayed result changed before export started."));
|
|
|
return;
|
|
|
}
|
|
|
nmPebiResultDisplaySettings oCurrentDisplaySettings;
|
|
|
nmPebiResultFilterSettings oCurrentFilterSettings;
|
|
|
if(!m_pSceneController->getCurrentDisplaySettings(
|
|
|
oCurrentDisplaySettings) ||
|
|
|
oCurrentDisplaySettings != oFrozenDisplaySettings ||
|
|
|
!m_pSceneController->getFilterSettings(oCurrentFilterSettings) ||
|
|
|
oCurrentFilterSettings != oFrozenFilterSettings ||
|
|
|
m_pSceneController->getFilterFrameIndex() !=
|
|
|
nFrozenFilterFrameIndex) {
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Export Animation"),
|
|
|
tr("The displayed result changed before export started."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
vtkRenderer* pRenderer = m_pRenderContainer->getRenderer();
|
|
|
if(pRenderer == nullptr || pRenderer->GetActiveCamera() == nullptr) {
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Export Animation"),
|
|
|
tr("Unable to freeze the current result display."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmPebiResultAnimationExporter::Request oRequest;
|
|
|
oRequest.m_pSnapshot = pFrozenSnapshot;
|
|
|
oRequest.m_sPropertyId = sFrozenPropertyId;
|
|
|
oRequest.m_oDisplaySettings = oFrozenDisplaySettings;
|
|
|
oRequest.m_eMeshDisplayMode = eFrozenMeshDisplayMode;
|
|
|
oRequest.m_oFilterSettings = oFrozenFilterSettings;
|
|
|
oRequest.m_nFilterFrameIndex = nFrozenFilterFrameIndex;
|
|
|
oRequest.m_oExportSettings = oSettingsDialog.getSettings();
|
|
|
oRequest.m_sTargetFilePath = oSettingsDialog.getTargetFilePath();
|
|
|
oRequest.m_bScalarBarVisible =
|
|
|
m_pSceneController->isScalarBarVisible();
|
|
|
oRequest.m_bScaleGridVisible =
|
|
|
m_pSceneController->isScaleGridVisible();
|
|
|
oRequest.m_bWellNamesVisible =
|
|
|
m_pSceneController->areWellNamesVisible();
|
|
|
pRenderer->GetBackground(oRequest.m_dBackground);
|
|
|
pRenderer->GetBackground2(oRequest.m_dBackground2);
|
|
|
oRequest.m_bGradientBackground =
|
|
|
pRenderer->GetGradientBackground() != 0;
|
|
|
oRequest.m_pCamera = vtkSmartPointer<vtkCamera>::New();
|
|
|
oRequest.m_pCamera->DeepCopy(pRenderer->GetActiveCamera());
|
|
|
|
|
|
QProgressDialog oProgressDialog(
|
|
|
tr("Exporting result animation..."), tr("Cancel"),
|
|
|
0, oRequest.m_oExportSettings.getOutputFrameCount(), this);
|
|
|
oProgressDialog.setWindowTitle(tr("Export Animation"));
|
|
|
oProgressDialog.setWindowModality(Qt::ApplicationModal);
|
|
|
oProgressDialog.setMinimumDuration(0);
|
|
|
oProgressDialog.setAutoClose(false);
|
|
|
oProgressDialog.setAutoReset(false);
|
|
|
|
|
|
nmPebiResultAnimationExporter oExporter;
|
|
|
connect(&oExporter, SIGNAL(progressChanged(int,int)),
|
|
|
&oProgressDialog, SLOT(setValue(int)));
|
|
|
connect(&oProgressDialog, SIGNAL(canceled()),
|
|
|
&oExporter, SLOT(cancel()));
|
|
|
oProgressDialog.show();
|
|
|
|
|
|
QString sExportError;
|
|
|
const nmPebiResultAnimationExporter::ExportResult eResult =
|
|
|
oExporter.exportAnimation(oRequest, &sExportError);
|
|
|
oProgressDialog.close();
|
|
|
oExportGuard.release();
|
|
|
|
|
|
if(eResult == nmPebiResultAnimationExporter::Export_Succeeded) {
|
|
|
onControlOperationSucceeded();
|
|
|
QMessageBox::information(
|
|
|
this, tr("Export Animation"),
|
|
|
tr("The result animation was saved to:\n%1")
|
|
|
.arg(QDir::toNativeSeparators(
|
|
|
oRequest.m_sTargetFilePath)));
|
|
|
} else if(eResult == nmPebiResultAnimationExporter::Export_Failed) {
|
|
|
QMessageBox::warning(
|
|
|
this, tr("Export Animation"),
|
|
|
sExportError.isEmpty()
|
|
|
? tr("Unable to export the result animation.")
|
|
|
: sExportError);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onScalarBarToggled(bool bVisible)
|
|
|
{
|
|
|
if(isImportedVolumeMode()) {
|
|
|
m_pVolumeState->m_pController->setScalarBarVisible(bVisible);
|
|
|
return;
|
|
|
}
|
|
|
if(m_pControlBar != nullptr &&
|
|
|
m_pControlBar->isExternalInteractionLocked()) {
|
|
|
synchronizeBusinessActions();
|
|
|
return;
|
|
|
}
|
|
|
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
|
|
|
m_pSceneController->setScalarBarVisible(bVisible);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onScaleGridToggled(bool bVisible)
|
|
|
{
|
|
|
// 比例网格只属于计算结果;外部模型模式不修改挂起结果的显示偏好。
|
|
|
if(isImportedVolumeMode()) return;
|
|
|
if(m_pControlBar != nullptr &&
|
|
|
m_pControlBar->isExternalInteractionLocked()) {
|
|
|
synchronizeBusinessActions();
|
|
|
return;
|
|
|
}
|
|
|
if(m_pSceneController != nullptr && m_pSceneController->hasSnapshot()) {
|
|
|
m_pSceneController->setScaleGridVisible(bVisible);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onWellNamesToggled(bool bVisible)
|
|
|
{
|
|
|
if(isImportedVolumeMode()) return;
|
|
|
if(m_pControlBar != nullptr &&
|
|
|
m_pControlBar->isExternalInteractionLocked()) {
|
|
|
synchronizeBusinessActions();
|
|
|
return;
|
|
|
}
|
|
|
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_ShowRotationCenterMarker |
|
|
|
nmVTKViewCommandController::ViewCommand_SaveImage |
|
|
|
nmVTKViewCommandController::ViewCommand_CopyImage |
|
|
|
nmVTKViewCommandController::ViewCommand_Print |
|
|
|
nmVTKViewCommandController::ViewCommand_PrintPreview);
|
|
|
m_pSceneController = new nmPebiResultSceneController(m_pRenderContainer);
|
|
|
m_pVolumeState->m_pController = new nmVolumeSceneController(m_pRenderContainer);
|
|
|
m_pVolumeState->m_pSliceInteraction = new nmVolumeSliceInteraction(m_pRenderContainer, this);
|
|
|
connect(m_pVolumeState->m_pSliceInteraction, SIGNAL(previewChanged(QString,nmVolumeSliceSettings)),
|
|
|
this, SLOT(onVolumeSlicePreview(QString,nmVolumeSliceSettings)));
|
|
|
connect(m_pVolumeState->m_pSliceInteraction, SIGNAL(applyRequested(QString,nmVolumeSliceSettings)),
|
|
|
this, SLOT(onVolumeSliceDragApplied(QString,nmVolumeSliceSettings)));
|
|
|
connect(m_pRenderContainer, SIGNAL(rotationCenterPickingChanged(bool)),
|
|
|
this, SLOT(synchronizeSliceInteraction()));
|
|
|
connect(m_pRenderContainer, SIGNAL(viewportSizeChanged(int,int)),
|
|
|
this, SLOT(onViewportSizeChanged(int,int)));
|
|
|
const QSize oInitialViewportSize =
|
|
|
m_pRenderContainer->getViewportSize();
|
|
|
onViewportSizeChanged(
|
|
|
oInitialViewportSize.width(), oInitialViewportSize.height());
|
|
|
|
|
|
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_pScalarBarAction = new QAction(
|
|
|
QIcon(resultViewIconPath("NmColorScale.png")),
|
|
|
tr("Color Scale"), this);
|
|
|
m_pScaleGridAction = new QAction(
|
|
|
QIcon(resultViewIconPath("NmScaleGrid.png")),
|
|
|
tr("Scale Grid"), this);
|
|
|
m_pWellNamesAction = new QAction(
|
|
|
QIcon(resultViewIconPath("NmShowWellNames.png")),
|
|
|
tr("Well Names"), this);
|
|
|
m_pFilterAction = new QAction(
|
|
|
QIcon(resultViewIconPath("NmFilter.png")),
|
|
|
tr("Data Filtering..."), this);
|
|
|
m_pFilterAction->setToolTip(m_pFilterAction->text());
|
|
|
m_pRegionPressureAction = new QAction(
|
|
|
QIcon(resultViewIconPath("NmPolygon.png")),
|
|
|
tr("Region Average Pressure"), this);
|
|
|
m_pRegionPressureAction->setToolTip(
|
|
|
m_pRegionPressureAction->text());
|
|
|
|
|
|
QAction* const pBusinessActions[] = {
|
|
|
m_pScalarBarAction, m_pScaleGridAction, m_pWellNamesAction
|
|
|
};
|
|
|
for(int nIndex = 0; nIndex < 3; ++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();
|
|
|
m_pToolBar->addAction(m_pFilterAction);
|
|
|
m_pToolBar->addAction(m_pRegionPressureAction);
|
|
|
for(int nIndex = 0; nIndex < 3; ++nIndex) {
|
|
|
m_pToolBar->addAction(pBusinessActions[nIndex]);
|
|
|
}
|
|
|
m_pToolBar->addSeparator();
|
|
|
if(pCopyAction != nullptr) m_pToolBar->addAction(pCopyAction);
|
|
|
}
|
|
|
|
|
|
// 只增加一个常驻按钮。小图标直接绘制,避免部署时丢失额外资源文件。
|
|
|
QPixmap oSliceIcon(32, 32);
|
|
|
oSliceIcon.fill(Qt::transparent);
|
|
|
{
|
|
|
QPainter oPainter(&oSliceIcon);
|
|
|
oPainter.setRenderHint(QPainter::Antialiasing);
|
|
|
oPainter.setPen(QPen(QColor(65, 85, 105), 1.5));
|
|
|
oPainter.drawRect(7, 5, 18, 22);
|
|
|
oPainter.setBrush(QColor(60, 155, 215, 160));
|
|
|
QPolygon oPlane;
|
|
|
oPlane << QPoint(3, 20) << QPoint(20, 11) << QPoint(29, 14) << QPoint(12, 24);
|
|
|
oPainter.drawPolygon(oPlane);
|
|
|
}
|
|
|
m_pVolumeState->m_pButton = new QToolButton(m_pToolBar);
|
|
|
m_pVolumeState->m_pButton->setObjectName(QLatin1String("volumeSliceButton"));
|
|
|
m_pVolumeState->m_pButton->setIcon(QIcon(oSliceIcon));
|
|
|
m_pVolumeState->m_pButton->setIconSize(m_pToolBar->iconSize());
|
|
|
m_pVolumeState->m_pButton->setText(tr("Slice"));
|
|
|
m_pVolumeState->m_pButton->setToolTip(tr("Slice"));
|
|
|
m_pVolumeState->m_pButton->setAutoRaise(true);
|
|
|
m_pVolumeState->m_pButton->setPopupMode(QToolButton::InstantPopup);
|
|
|
QMenu* pSliceMenu = new QMenu(m_pVolumeState->m_pButton);
|
|
|
m_pVolumeState->m_pImportAction = new QAction(tr("Import 3D Model..."), this);
|
|
|
m_pVolumeState->m_pImportAction->setObjectName(QLatin1String("importVolumeAction"));
|
|
|
m_pVolumeState->m_pReturnAction = new QAction(tr("Return to Calculation Result"), this);
|
|
|
m_pVolumeState->m_pReturnAction->setObjectName(QLatin1String("returnCalculationAction"));
|
|
|
m_pVolumeState->m_pSliceAction = new QAction(tr("Show Slice"), this);
|
|
|
m_pVolumeState->m_pSliceAction->setObjectName(QLatin1String("showVolumeSliceAction"));
|
|
|
m_pVolumeState->m_pSliceAction->setCheckable(true);
|
|
|
m_pVolumeState->m_pSliceSettingsAction = new QAction(tr("Slice Settings..."), this);
|
|
|
m_pVolumeState->m_pSliceSettingsAction->setObjectName(QLatin1String("volumeSliceSettingsAction"));
|
|
|
pSliceMenu->addAction(m_pVolumeState->m_pImportAction);
|
|
|
pSliceMenu->addSeparator();
|
|
|
pSliceMenu->addAction(m_pVolumeState->m_pSliceAction);
|
|
|
pSliceMenu->addAction(m_pVolumeState->m_pSliceSettingsAction);
|
|
|
pSliceMenu->addSeparator();
|
|
|
pSliceMenu->addAction(m_pVolumeState->m_pReturnAction);
|
|
|
m_pVolumeState->m_pButton->setMenu(pSliceMenu);
|
|
|
m_pToolBar->addWidget(m_pVolumeState->m_pButton);
|
|
|
connect(m_pVolumeState->m_pImportAction, SIGNAL(triggered()), this, SLOT(onImportVolumeRequested()));
|
|
|
connect(m_pVolumeState->m_pReturnAction, SIGNAL(triggered()), this, SLOT(onReturnToCalculationResult()));
|
|
|
connect(m_pVolumeState->m_pSliceAction, SIGNAL(toggled(bool)), this, SLOT(onVolumeSliceToggled(bool)));
|
|
|
connect(m_pVolumeState->m_pSliceSettingsAction, SIGNAL(triggered()), this, SLOT(onShowVolumeSliceSettings()));
|
|
|
|
|
|
connect(m_pScalarBarAction, SIGNAL(toggled(bool)),
|
|
|
this, SLOT(onScalarBarToggled(bool)));
|
|
|
connect(m_pScaleGridAction, SIGNAL(toggled(bool)),
|
|
|
this, SLOT(onScaleGridToggled(bool)));
|
|
|
connect(m_pWellNamesAction, SIGNAL(toggled(bool)),
|
|
|
this, SLOT(onWellNamesToggled(bool)));
|
|
|
connect(m_pFilterAction, SIGNAL(triggered()),
|
|
|
this, SLOT(onShowFilterSettings()));
|
|
|
connect(m_pRegionPressureAction, SIGNAL(triggered()),
|
|
|
this, SLOT(onRegionPressureStatisticsTriggered()));
|
|
|
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()));
|
|
|
connect(m_pControlBar, SIGNAL(exportAnimationRequested()),
|
|
|
this, SLOT(onExportAnimationRequested()));
|
|
|
connect(m_pControlBar, SIGNAL(exportInProgressChanged(bool)),
|
|
|
this, SLOT(synchronizeBusinessActions()));
|
|
|
if(m_pControlBar->getPlaybackController() != nullptr) {
|
|
|
connect(m_pControlBar->getPlaybackController(), SIGNAL(stateChanged()),
|
|
|
this, SLOT(onResultDisplayStateChanged()));
|
|
|
}
|
|
|
pMainLayout->addWidget(m_pControlBar);
|
|
|
|
|
|
synchronizeBusinessActions();
|
|
|
m_pRenderContainer->showEmptyState(tr("No result data."));
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::synchronizeOnce()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) {
|
|
|
// 正式结果变化不能抢占正在查看的外部模型;返回时读取 Manager 最新快照。
|
|
|
m_pVolumeState->m_bPendingSync = true;
|
|
|
return;
|
|
|
}
|
|
|
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();
|
|
|
// 只有新快照已经成功发布后才清旧区域;失败回滚必须完整保留旧统计。
|
|
|
clearRegionPressureSelection(true);
|
|
|
// 新成功绑定序号会让 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();
|
|
|
}
|
|
|
clearRegionPressureSelection(false);
|
|
|
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);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultViewWidget::startRegionPressureDrawing()
|
|
|
{
|
|
|
nmPebiResultPlaybackController* pPlaybackController =
|
|
|
m_pControlBar == nullptr ? nullptr :
|
|
|
m_pControlBar->getPlaybackController();
|
|
|
if(m_pRegionState == nullptr || m_pSceneController == nullptr ||
|
|
|
m_pRenderContainer == nullptr || m_pControlBar == nullptr ||
|
|
|
pPlaybackController == nullptr ||
|
|
|
m_pRegionState->m_eState !=
|
|
|
nmWxPebiResultRegionState::State_Idle ||
|
|
|
!m_pSceneController->hasSnapshot() ||
|
|
|
m_pDisplayedSnapshot.isNull() ||
|
|
|
m_pSceneController->getCurrentPropertyId() !=
|
|
|
QString::fromLatin1("pressure") ||
|
|
|
m_pSceneController->getCurrentFrameIndex() < 0 ||
|
|
|
pPlaybackController->isPlaying() ||
|
|
|
m_pControlBar->isExportInProgress() ||
|
|
|
m_pControlBar->isExternalInteractionLocked() ||
|
|
|
!canProjectRegionOntoXyPlane(
|
|
|
m_pRenderContainer->getRenderer()) ||
|
|
|
!ensureRegionPressureLayer()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 冻结绘制起点的完整显示身份,完成时会逐项复核。
|
|
|
m_pRegionState->m_pFrozenSnapshot = m_pDisplayedSnapshot;
|
|
|
m_pRegionState->m_nFrozenBindingRevision =
|
|
|
m_pSceneController->getSnapshotBindingRevision();
|
|
|
m_pRegionState->m_sFrozenSnapshotId =
|
|
|
m_pSceneController->getCurrentSnapshotId();
|
|
|
m_pRegionState->m_sFrozenPropertyId =
|
|
|
m_pSceneController->getCurrentPropertyId();
|
|
|
m_pRegionState->m_nFrozenFrameIndex =
|
|
|
m_pSceneController->getCurrentFrameIndex();
|
|
|
if(m_pRegionState->m_nFrozenBindingRevision == 0 ||
|
|
|
m_pRegionState->m_sFrozenSnapshotId.isEmpty() ||
|
|
|
m_pRegionState->m_sFrozenSnapshotId != m_sDisplayedSnapshotId) {
|
|
|
m_pRegionState->m_pFrozenSnapshot.clear();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 观察者只在本次绘制事务内有效,退出时必须在销毁回调前全部解除。
|
|
|
m_pRegionState->m_pStyle =
|
|
|
vtkSmartPointer<nmVTKPlanarPolygonInteractorStyle>::New();
|
|
|
m_pRegionState->m_pCallback =
|
|
|
vtkSmartPointer<vtkCallbackCommand>::New();
|
|
|
m_pRegionState->m_pCallback->SetClientData(this);
|
|
|
m_pRegionState->m_pCallback->SetCallback(
|
|
|
&nmWxPebiResultViewWidget::handleRegionPolygonEvent);
|
|
|
m_pRegionState->m_nChangedObserverTag =
|
|
|
m_pRegionState->m_pStyle->AddObserver(
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonChangedEvent,
|
|
|
m_pRegionState->m_pCallback);
|
|
|
m_pRegionState->m_nCompletedObserverTag =
|
|
|
m_pRegionState->m_pStyle->AddObserver(
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonCompletedEvent,
|
|
|
m_pRegionState->m_pCallback);
|
|
|
m_pRegionState->m_nCancelledObserverTag =
|
|
|
m_pRegionState->m_pStyle->AddObserver(
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonCancelledEvent,
|
|
|
m_pRegionState->m_pCallback);
|
|
|
m_pRegionState->m_nInvalidObserverTag =
|
|
|
m_pRegionState->m_pStyle->AddObserver(
|
|
|
nmVTKPlanarPolygonInteractorStyle::PolygonInvalidEvent,
|
|
|
m_pRegionState->m_pCallback);
|
|
|
|
|
|
if(!m_pRenderContainer->installInteractionStyle(
|
|
|
m_pRegionState->m_pStyle)) {
|
|
|
stopRegionPressureDrawing();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
m_pRegionState->m_oCalculator.clear();
|
|
|
m_pRegionState->m_pLayer->clear();
|
|
|
if(m_pRegionState->m_pDialog != nullptr) {
|
|
|
m_pRegionState->m_pDialog->hide();
|
|
|
}
|
|
|
// 交互样式安装成功后再锁定底栏,避免失败路径遗留禁用控件。
|
|
|
m_pRegionState->m_eState =
|
|
|
nmWxPebiResultRegionState::State_Drawing;
|
|
|
m_pControlBar->setExternalInteractionLocked(true);
|
|
|
synchronizeBusinessActions();
|
|
|
m_pRenderContainer->renderView();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::stopRegionPressureDrawing()
|
|
|
{
|
|
|
if(m_pRegionState == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 先移除 VTK 观察者并清空 ClientData,再恢复默认相机交互。
|
|
|
if(m_pRegionState->m_pStyle != nullptr) {
|
|
|
if(m_pRegionState->m_nChangedObserverTag != 0) {
|
|
|
m_pRegionState->m_pStyle->RemoveObserver(
|
|
|
m_pRegionState->m_nChangedObserverTag);
|
|
|
}
|
|
|
if(m_pRegionState->m_nCompletedObserverTag != 0) {
|
|
|
m_pRegionState->m_pStyle->RemoveObserver(
|
|
|
m_pRegionState->m_nCompletedObserverTag);
|
|
|
}
|
|
|
if(m_pRegionState->m_nCancelledObserverTag != 0) {
|
|
|
m_pRegionState->m_pStyle->RemoveObserver(
|
|
|
m_pRegionState->m_nCancelledObserverTag);
|
|
|
}
|
|
|
if(m_pRegionState->m_nInvalidObserverTag != 0) {
|
|
|
m_pRegionState->m_pStyle->RemoveObserver(
|
|
|
m_pRegionState->m_nInvalidObserverTag);
|
|
|
}
|
|
|
}
|
|
|
if(m_pRegionState->m_pCallback != nullptr) {
|
|
|
m_pRegionState->m_pCallback->SetClientData(nullptr);
|
|
|
}
|
|
|
|
|
|
m_pRegionState->m_nChangedObserverTag = 0;
|
|
|
m_pRegionState->m_nCompletedObserverTag = 0;
|
|
|
m_pRegionState->m_nCancelledObserverTag = 0;
|
|
|
m_pRegionState->m_nInvalidObserverTag = 0;
|
|
|
if(m_pRenderContainer != nullptr &&
|
|
|
m_pRegionState->m_pStyle != nullptr) {
|
|
|
m_pRenderContainer->restoreDefaultInteraction();
|
|
|
}
|
|
|
m_pRegionState->m_pStyle = nullptr;
|
|
|
m_pRegionState->m_pCallback = nullptr;
|
|
|
m_pRegionState->m_ePendingEvent =
|
|
|
nmWxPebiResultRegionState::PendingEvent_None;
|
|
|
m_pRegionState->m_bPendingEventQueued = false;
|
|
|
m_pRegionState->m_vecPendingPoints.clear();
|
|
|
m_pRegionState->m_pFrozenSnapshot.clear();
|
|
|
m_pRegionState->m_nFrozenBindingRevision = 0;
|
|
|
m_pRegionState->m_sFrozenSnapshotId.clear();
|
|
|
m_pRegionState->m_sFrozenPropertyId.clear();
|
|
|
m_pRegionState->m_nFrozenFrameIndex = -1;
|
|
|
if(m_pControlBar != nullptr) {
|
|
|
m_pControlBar->setExternalInteractionLocked(false);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::clearRegionPressureSelection(
|
|
|
bool bRenderView)
|
|
|
{
|
|
|
if(m_pRegionState == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmVTKScene* pScene = m_pRenderContainer == nullptr
|
|
|
? nullptr : m_pRenderContainer->getScene();
|
|
|
// 裸指针只在 Scene 仍持有同一图层时可用,失败回滚后不访问旧对象。
|
|
|
const bool bLayerIsCurrent = pScene != nullptr &&
|
|
|
m_pRegionState->m_pLayer != nullptr &&
|
|
|
pScene->getLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID)) ==
|
|
|
m_pRegionState->m_pLayer;
|
|
|
const bool bHadVisibleRegion =
|
|
|
m_pRegionState->m_eState !=
|
|
|
nmWxPebiResultRegionState::State_Idle ||
|
|
|
(bLayerIsCurrent &&
|
|
|
!m_pRegionState->m_pLayer->getPoints().isEmpty());
|
|
|
stopRegionPressureDrawing();
|
|
|
|
|
|
if(bLayerIsCurrent) {
|
|
|
m_pRegionState->m_pLayer->clear();
|
|
|
} else {
|
|
|
m_pRegionState->m_pLayer = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pRegionState->m_oCalculator.clear();
|
|
|
m_pRegionState->m_eState =
|
|
|
nmWxPebiResultRegionState::State_Idle;
|
|
|
m_pRegionState->m_nRegionBindingRevision = 0;
|
|
|
m_pRegionState->m_sRegionSnapshotId.clear();
|
|
|
m_pRegionState->m_sLastObservedPropertyId.clear();
|
|
|
m_pRegionState->m_nLastAttemptedFrameIndex = -1;
|
|
|
m_pRegionState->m_bStatisticsCalculated = false;
|
|
|
m_pRegionState->m_sStatisticsError.clear();
|
|
|
m_pRegionState->m_oStatistics.clear();
|
|
|
if(m_pRegionState->m_pDialog != nullptr) {
|
|
|
m_pRegionState->m_pDialog->hide();
|
|
|
}
|
|
|
synchronizeBusinessActions();
|
|
|
if(bRenderView && bHadVisibleRegion &&
|
|
|
m_pRenderContainer != nullptr) {
|
|
|
m_pRenderContainer->renderView();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::destroyRegionPressureState()
|
|
|
{
|
|
|
if(m_pRegionState == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
clearRegionPressureSelection(false);
|
|
|
nmVTKScene* pScene = m_pRenderContainer == nullptr
|
|
|
? nullptr : m_pRenderContainer->getScene();
|
|
|
if(pScene != nullptr && m_pRegionState->m_pLayer != nullptr &&
|
|
|
pScene->getLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID)) == m_pRegionState->m_pLayer) {
|
|
|
pScene->removeLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID));
|
|
|
}
|
|
|
m_pRegionState->m_pLayer = nullptr;
|
|
|
if(m_pRegionState->m_pDialog != nullptr) {
|
|
|
delete m_pRegionState->m_pDialog;
|
|
|
m_pRegionState->m_pDialog = nullptr;
|
|
|
}
|
|
|
delete m_pRegionState;
|
|
|
m_pRegionState = nullptr;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultViewWidget::ensureRegionPressureLayer()
|
|
|
{
|
|
|
if(m_pRegionState == nullptr || m_pRenderContainer == nullptr ||
|
|
|
m_pRenderContainer->getScene() == nullptr) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmVTKScene* pScene = m_pRenderContainer->getScene();
|
|
|
nmVTKRenderLayer* pExisting = pScene->getLayer(
|
|
|
QString::fromLatin1(PEBI_REGION_PRESSURE_LAYER_ID));
|
|
|
if(pExisting != nullptr) {
|
|
|
return m_pRegionState->m_pLayer != nullptr &&
|
|
|
pExisting == m_pRegionState->m_pLayer;
|
|
|
}
|
|
|
|
|
|
m_pRegionState->m_pLayer = nullptr;
|
|
|
nmVTKPlanarPolygonLayer* pCandidate =
|
|
|
new nmVTKPlanarPolygonLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID));
|
|
|
if(!pScene->addLayer(pCandidate)) {
|
|
|
delete pCandidate;
|
|
|
return false;
|
|
|
}
|
|
|
m_pRegionState->m_pLayer = pCandidate;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultViewWidget::isRegionPressureDrawing() const
|
|
|
{
|
|
|
return m_pRegionState != nullptr &&
|
|
|
m_pRegionState->m_eState ==
|
|
|
nmWxPebiResultRegionState::State_Drawing;
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::updateRegionPressureStatistics()
|
|
|
{
|
|
|
// 静态属性下冻结最后一帧结果;只有显示 pressure 时才跟随帧变化重算。
|
|
|
if(m_pRegionState == nullptr || m_pSceneController == nullptr ||
|
|
|
m_pRegionState->m_eState !=
|
|
|
nmWxPebiResultRegionState::State_Completed ||
|
|
|
!m_pRegionState->m_oCalculator.isPrepared() ||
|
|
|
!m_pSceneController->hasSnapshot() ||
|
|
|
m_pSceneController->getSnapshotBindingRevision() !=
|
|
|
m_pRegionState->m_nRegionBindingRevision ||
|
|
|
m_pSceneController->getCurrentSnapshotId() !=
|
|
|
m_pRegionState->m_sRegionSnapshotId ||
|
|
|
m_pSceneController->getCurrentPropertyId() !=
|
|
|
QString::fromLatin1("pressure")) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const int nFrameIndex = m_pSceneController->getCurrentFrameIndex();
|
|
|
if(nFrameIndex < 0 ||
|
|
|
nFrameIndex == m_pRegionState->m_nLastAttemptedFrameIndex) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 先登记尝试帧,保证一个状态信号周期内同一帧最多进行一轮重计算。
|
|
|
m_pRegionState->m_nLastAttemptedFrameIndex = nFrameIndex;
|
|
|
nmPebiRegionPressureStatistics oStatistics;
|
|
|
QString sError;
|
|
|
m_pRegionState->m_bStatisticsCalculated =
|
|
|
m_pRegionState->m_oCalculator.calculateFrame(
|
|
|
nFrameIndex, oStatistics, &sError);
|
|
|
m_pRegionState->m_sStatisticsError = sError;
|
|
|
if(m_pRegionState->m_bStatisticsCalculated) {
|
|
|
m_pRegionState->m_oStatistics = oStatistics;
|
|
|
}
|
|
|
refreshRegionPressureStatisticsDialog();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::ensureRegionPressureStatisticsDialog()
|
|
|
{
|
|
|
if(m_pRegionState == nullptr || m_pRegionState->m_pDialog != nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
iDlgBase* pDialog = new iDlgBase(this);
|
|
|
pDialog->setWindowTitle(tr("Region Average Pressure"));
|
|
|
pDialog->setWindowModality(Qt::NonModal);
|
|
|
pDialog->setModal(false);
|
|
|
pDialog->setAttribute(Qt::WA_DeleteOnClose, false);
|
|
|
pDialog->setWindowFlags(
|
|
|
pDialog->windowFlags() &
|
|
|
~Qt::WindowContextHelpButtonHint);
|
|
|
|
|
|
QVBoxLayout* pMainLayout = new QVBoxLayout(pDialog);
|
|
|
pMainLayout->setContentsMargins(12, 10, 12, 10);
|
|
|
pMainLayout->setSpacing(8);
|
|
|
|
|
|
QGroupBox* pStatisticsGroup = new QGroupBox(
|
|
|
tr("Statistics"), pDialog);
|
|
|
QGridLayout* pStatisticsLayout = new QGridLayout(pStatisticsGroup);
|
|
|
pStatisticsLayout->setContentsMargins(8, 15, 8, 10);
|
|
|
pStatisticsLayout->setHorizontalSpacing(6);
|
|
|
pStatisticsLayout->setVerticalSpacing(6);
|
|
|
pStatisticsLayout->setColumnStretch(0, 0);
|
|
|
pStatisticsLayout->setColumnStretch(1, 8);
|
|
|
pStatisticsLayout->setColumnStretch(2, 5);
|
|
|
|
|
|
m_pRegionState->m_pAveragePressureUnitCombo =
|
|
|
createRegionUnitCombo(pStatisticsGroup,
|
|
|
QString::fromLatin1("MPa"));
|
|
|
m_pRegionState->m_pMinimumPressureUnitCombo =
|
|
|
createRegionUnitCombo(pStatisticsGroup,
|
|
|
QString::fromLatin1("MPa"));
|
|
|
m_pRegionState->m_pMaximumPressureUnitCombo =
|
|
|
createRegionUnitCombo(pStatisticsGroup,
|
|
|
QString::fromLatin1("MPa"));
|
|
|
m_pRegionState->m_pPressureTimeUnitCombo =
|
|
|
createRegionUnitCombo(pStatisticsGroup,
|
|
|
QString::fromLatin1("h"));
|
|
|
|
|
|
int nRow = 0;
|
|
|
m_pRegionState->m_pAveragePressureValue = addRegionStatisticsRow(
|
|
|
pStatisticsLayout, nRow++, tr("Region average pressure"),
|
|
|
pStatisticsGroup,
|
|
|
m_pRegionState->m_pAveragePressureUnitCombo);
|
|
|
m_pRegionState->m_pAveragePressureValue->setToolTip(tr(
|
|
|
"Weighted by cell area, thickness and porosity."));
|
|
|
m_pRegionState->m_pMinimumPressureValue = addRegionStatisticsRow(
|
|
|
pStatisticsLayout, nRow++, tr("Minimum pressure"),
|
|
|
pStatisticsGroup,
|
|
|
m_pRegionState->m_pMinimumPressureUnitCombo);
|
|
|
m_pRegionState->m_pMaximumPressureValue = addRegionStatisticsRow(
|
|
|
pStatisticsLayout, nRow++, tr("Maximum pressure"),
|
|
|
pStatisticsGroup,
|
|
|
m_pRegionState->m_pMaximumPressureUnitCombo);
|
|
|
m_pRegionState->m_pTimeValue = addRegionStatisticsRow(
|
|
|
pStatisticsLayout, nRow++, tr("Pressure time"),
|
|
|
pStatisticsGroup,
|
|
|
m_pRegionState->m_pPressureTimeUnitCombo);
|
|
|
m_pRegionState->m_pSelectedCellValue = addRegionStatisticsRow(
|
|
|
pStatisticsLayout, nRow++, tr("Selected cells"),
|
|
|
pStatisticsGroup);
|
|
|
m_pRegionState->m_pWarningLabel = new QLabel(pStatisticsGroup);
|
|
|
m_pRegionState->m_pWarningLabel->setWordWrap(true);
|
|
|
m_pRegionState->m_pWarningLabel->setStyleSheet(
|
|
|
"QLabel { color: #a32626; }");
|
|
|
m_pRegionState->m_pWarningLabel->hide();
|
|
|
pStatisticsLayout->addWidget(
|
|
|
m_pRegionState->m_pWarningLabel, nRow++, 0, 1, 3);
|
|
|
pMainLayout->addWidget(pStatisticsGroup);
|
|
|
|
|
|
QHBoxLayout* pButtonLayout = new QHBoxLayout;
|
|
|
pButtonLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
pButtonLayout->setSpacing(6);
|
|
|
pButtonLayout->addStretch(1);
|
|
|
QPushButton* pCloseButton = new QPushButton(tr("Close"), pDialog);
|
|
|
const QSize oFooterIconSize(16, 16);
|
|
|
pCloseButton->setIcon(QIcon(resultViewIconPath("Close.png")));
|
|
|
pCloseButton->setIconSize(oFooterIconSize);
|
|
|
pCloseButton->setMinimumWidth(72);
|
|
|
pCloseButton->setDefault(true);
|
|
|
pButtonLayout->addWidget(pCloseButton);
|
|
|
pMainLayout->addLayout(pButtonLayout);
|
|
|
|
|
|
connect(pCloseButton, SIGNAL(clicked()),
|
|
|
this, SLOT(onCloseRegionPressureStatistics()));
|
|
|
connect(pDialog, SIGNAL(rejected()),
|
|
|
this, SLOT(onCloseRegionPressureStatistics()));
|
|
|
m_pRegionState->m_pDialog = pDialog;
|
|
|
pDialog->setMinimumWidth(460);
|
|
|
pDialog->adjustSize();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::refreshRegionPressureStatisticsDialog()
|
|
|
{
|
|
|
if(m_pRegionState == nullptr || m_pRegionState->m_pDialog == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const QString sUnavailable = QString::fromLatin1("--");
|
|
|
if(!m_pRegionState->m_bStatisticsCalculated) {
|
|
|
QLineEdit* const pValues[] = {
|
|
|
m_pRegionState->m_pAveragePressureValue,
|
|
|
m_pRegionState->m_pMinimumPressureValue,
|
|
|
m_pRegionState->m_pMaximumPressureValue,
|
|
|
m_pRegionState->m_pTimeValue,
|
|
|
m_pRegionState->m_pSelectedCellValue
|
|
|
};
|
|
|
const int nValueCount = sizeof(pValues) / sizeof(pValues[0]);
|
|
|
for(int nIndex = 0; nIndex < nValueCount; ++nIndex) {
|
|
|
pValues[nIndex]->setText(sUnavailable);
|
|
|
}
|
|
|
m_pRegionState->m_pWarningLabel->setText(
|
|
|
tr("Unable to calculate region average pressure."));
|
|
|
m_pRegionState->m_pWarningLabel->show();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const nmPebiRegionPressureStatistics& oStatistics =
|
|
|
m_pRegionState->m_oStatistics;
|
|
|
|
|
|
m_pRegionState->m_pTimeValue->setText(
|
|
|
formatRegionValue(oStatistics.m_dPressureTimeHours));
|
|
|
m_pRegionState->m_pSelectedCellValue->setText(
|
|
|
QString::number(static_cast<qlonglong>(
|
|
|
oStatistics.m_nIntersectingCellCount)));
|
|
|
if(oStatistics.m_bAvailable) {
|
|
|
m_pRegionState->m_pAveragePressureValue->setText(
|
|
|
formatRegionValue(
|
|
|
oStatistics.m_dPoreVolumeWeightedPressureMpa));
|
|
|
m_pRegionState->m_pMinimumPressureValue->setText(
|
|
|
formatRegionValue(oStatistics.m_dMinimumPressureMpa));
|
|
|
m_pRegionState->m_pMaximumPressureValue->setText(
|
|
|
formatRegionValue(oStatistics.m_dMaximumPressureMpa));
|
|
|
} else {
|
|
|
m_pRegionState->m_pAveragePressureValue->setText(sUnavailable);
|
|
|
m_pRegionState->m_pMinimumPressureValue->setText(sUnavailable);
|
|
|
m_pRegionState->m_pMaximumPressureValue->setText(sUnavailable);
|
|
|
}
|
|
|
|
|
|
if(oStatistics.m_nIntersectingCellCount == 0) {
|
|
|
m_pRegionState->m_pWarningLabel->setText(
|
|
|
tr("No cells intersect the selected region."));
|
|
|
m_pRegionState->m_pWarningLabel->show();
|
|
|
} else if(!oStatistics.m_bAvailable) {
|
|
|
m_pRegionState->m_pWarningLabel->setText(tr(
|
|
|
"No valid cells are available for region average pressure."));
|
|
|
m_pRegionState->m_pWarningLabel->show();
|
|
|
} else if(oStatistics.m_nIgnoredCellCount > 0) {
|
|
|
m_pRegionState->m_pWarningLabel->setText(tr(
|
|
|
"%1 selected cells were excluded because their data is invalid.").arg(
|
|
|
static_cast<qlonglong>(
|
|
|
oStatistics.m_nIgnoredCellCount)));
|
|
|
m_pRegionState->m_pWarningLabel->show();
|
|
|
} else {
|
|
|
m_pRegionState->m_pWarningLabel->clear();
|
|
|
m_pRegionState->m_pWarningLabel->hide();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::showRegionPressureStatisticsDialog()
|
|
|
{
|
|
|
nmVTKScene* pScene = m_pRenderContainer == nullptr
|
|
|
? nullptr : m_pRenderContainer->getScene();
|
|
|
if(m_pRegionState == nullptr || m_pSceneController == nullptr ||
|
|
|
m_pRegionState->m_eState !=
|
|
|
nmWxPebiResultRegionState::State_Completed ||
|
|
|
!m_pRegionState->m_oCalculator.isPrepared() ||
|
|
|
!m_pSceneController->hasSnapshot() ||
|
|
|
m_pSceneController->getSnapshotBindingRevision() !=
|
|
|
m_pRegionState->m_nRegionBindingRevision ||
|
|
|
m_pSceneController->getCurrentSnapshotId() !=
|
|
|
m_pRegionState->m_sRegionSnapshotId ||
|
|
|
pScene == nullptr || m_pRegionState->m_pLayer == nullptr ||
|
|
|
pScene->getLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID)) != m_pRegionState->m_pLayer) {
|
|
|
return;
|
|
|
}
|
|
|
ensureRegionPressureStatisticsDialog();
|
|
|
synchronizeBusinessActions();
|
|
|
refreshRegionPressureStatisticsDialog();
|
|
|
if(m_pRegionState->m_pDialog != nullptr) {
|
|
|
m_pRegionState->m_pDialog->show();
|
|
|
m_pRegionState->m_pDialog->raise();
|
|
|
m_pRegionState->m_pDialog->activateWindow();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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()
|
|
|
{
|
|
|
synchronizeVolumeActions();
|
|
|
if(isImportedVolumeMode()) return;
|
|
|
if(m_pSceneController == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
setActionCheckedWithoutSignal(
|
|
|
m_pScalarBarAction, m_pSceneController->isScalarBarVisible());
|
|
|
setActionCheckedWithoutSignal(
|
|
|
m_pScaleGridAction, m_pSceneController->isScaleGridVisible());
|
|
|
setActionCheckedWithoutSignal(
|
|
|
m_pWellNamesAction, m_pSceneController->areWellNamesVisible());
|
|
|
|
|
|
const bool bEnabled = m_pSceneController->hasSnapshot();
|
|
|
nmPebiResultPlaybackController* pPlaybackController =
|
|
|
m_pControlBar == nullptr ? nullptr :
|
|
|
m_pControlBar->getPlaybackController();
|
|
|
const bool bPlaying = pPlaybackController != nullptr &&
|
|
|
pPlaybackController->isPlaying();
|
|
|
const bool bExporting = m_pControlBar != nullptr &&
|
|
|
m_pControlBar->isExportInProgress();
|
|
|
const bool bExternallyLocked = m_pControlBar != nullptr &&
|
|
|
m_pControlBar->isExternalInteractionLocked();
|
|
|
const bool bDrawing = isRegionPressureDrawing();
|
|
|
nmVTKScene* pScene = m_pRenderContainer == nullptr
|
|
|
? nullptr : m_pRenderContainer->getScene();
|
|
|
const bool bCompleted = m_pRegionState != nullptr &&
|
|
|
m_pRegionState->m_eState ==
|
|
|
nmWxPebiResultRegionState::State_Completed &&
|
|
|
m_pRegionState->m_oCalculator.isPrepared() &&
|
|
|
bEnabled &&
|
|
|
m_pSceneController->getSnapshotBindingRevision() ==
|
|
|
m_pRegionState->m_nRegionBindingRevision &&
|
|
|
m_pSceneController->getCurrentSnapshotId() ==
|
|
|
m_pRegionState->m_sRegionSnapshotId &&
|
|
|
pScene != nullptr && m_pRegionState->m_pLayer != nullptr &&
|
|
|
pScene->getLayer(QString::fromLatin1(
|
|
|
PEBI_REGION_PRESSURE_LAYER_ID)) ==
|
|
|
m_pRegionState->m_pLayer;
|
|
|
const bool bBusinessVisibilityEnabled = bEnabled && !bDrawing;
|
|
|
m_pScalarBarAction->setEnabled(bBusinessVisibilityEnabled);
|
|
|
m_pScaleGridAction->setEnabled(bBusinessVisibilityEnabled);
|
|
|
m_pWellNamesAction->setEnabled(bBusinessVisibilityEnabled);
|
|
|
const bool bFilterEnabled = bEnabled &&
|
|
|
pPlaybackController != nullptr &&
|
|
|
!bPlaying && !bExporting && !bExternallyLocked && !bDrawing;
|
|
|
if(m_pFilterAction != nullptr) {
|
|
|
m_pFilterAction->setEnabled(bFilterEnabled);
|
|
|
}
|
|
|
if(m_pRegionPressureAction != nullptr) {
|
|
|
const bool bCanStart = bEnabled &&
|
|
|
m_pSceneController->getCurrentPropertyId() ==
|
|
|
QString::fromLatin1("pressure");
|
|
|
const bool bRegionEnabled = bDrawing || bCompleted ||
|
|
|
(!bExternallyLocked && !bPlaying && !bExporting &&
|
|
|
bCanStart);
|
|
|
m_pRegionPressureAction->setEnabled(bRegionEnabled);
|
|
|
const QString sActionText = bDrawing
|
|
|
? tr("Cancel Region Drawing")
|
|
|
: tr("Region Average Pressure");
|
|
|
m_pRegionPressureAction->setText(sActionText);
|
|
|
m_pRegionPressureAction->setToolTip(sActionText);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::refreshStatusBar()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) {
|
|
|
const bool bError = !m_sOperationError.isEmpty();
|
|
|
const QString sStatus = bError ? m_sOperationError :
|
|
|
(m_pVolumeState->m_pController->isSliceEmpty() ? tr("The slice plane does not intersect the model.") : QString());
|
|
|
m_pStatusLabel->setStyleSheet(bError ? "QLabel { color: #a32626; background: #fdecec; }"
|
|
|
: "QLabel { color: #725514; background: #fff7df; }");
|
|
|
m_pStatusLabel->setText(sStatus);
|
|
|
m_pStatusLabel->setVisible(!sStatus.isEmpty());
|
|
|
return;
|
|
|
}
|
|
|
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_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;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultViewWidget::isImportedVolumeMode() const
|
|
|
{
|
|
|
return m_pVolumeState != nullptr && m_pVolumeState->m_bImported;
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultViewWidget::canImportVolume() const
|
|
|
{
|
|
|
if(m_pVolumeState == nullptr || m_pVolumeState->m_pTask != nullptr ||
|
|
|
m_pControlBar == nullptr || m_pControlBar->isExportInProgress() ||
|
|
|
isRegionPressureDrawing()) return false;
|
|
|
nmPebiResultPlaybackController* pPlayback = m_pControlBar->getPlaybackController();
|
|
|
return (pPlayback == nullptr || !pPlayback->isPlaying()) &&
|
|
|
(isImportedVolumeMode() || !m_pControlBar->isExternalInteractionLocked());
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::synchronizeVolumeActions()
|
|
|
{
|
|
|
if(m_pVolumeState == nullptr || m_pVolumeState->m_pImportAction == nullptr) return;
|
|
|
m_pVolumeState->m_pImportAction->setEnabled(canImportVolume());
|
|
|
m_pVolumeState->m_pImportAction->setText(isImportedVolumeMode()
|
|
|
? tr("Replace 3D Model...") : tr("Import 3D Model..."));
|
|
|
m_pVolumeState->m_pReturnAction->setEnabled(isImportedVolumeMode() && m_pVolumeState->m_pTask == nullptr);
|
|
|
const bool bSliceAvailable = isImportedVolumeMode() && m_pVolumeState->m_pTask == nullptr && m_pVolumeState->m_pController->hasData();
|
|
|
m_pVolumeState->m_pSliceAction->setEnabled(bSliceAvailable);
|
|
|
m_pVolumeState->m_pSliceSettingsAction->setEnabled(bSliceAvailable);
|
|
|
setActionCheckedWithoutSignal(m_pVolumeState->m_pSliceAction, isImportedVolumeMode() && m_pVolumeState->m_pController->isSliceEnabled());
|
|
|
if(m_pVolumeState->m_pSliceDialog != nullptr) m_pVolumeState->m_pSliceDialog->setEnabled(bSliceAvailable);
|
|
|
synchronizeSliceInteraction();
|
|
|
if(!isImportedVolumeMode()) return;
|
|
|
nmVolumeSceneController* pVolume = m_pVolumeState->m_pController;
|
|
|
// 导入任务期间冻结底栏;平时只启用当前三维场景支持的显示及属性选择。
|
|
|
m_pControlBar->setExternalInteractionLocked(m_pVolumeState->m_pTask != nullptr);
|
|
|
m_pControlBar->synchronizeFromScene();
|
|
|
setActionCheckedWithoutSignal(m_pScalarBarAction, pVolume->isScalarBarVisible());
|
|
|
m_pScalarBarAction->setEnabled(pVolume->hasScalarProperty());
|
|
|
// 与筛选等结果专用功能一致,保留比例网格入口,仅在外部三维模式下禁用。
|
|
|
m_pScaleGridAction->setEnabled(false);
|
|
|
m_pWellNamesAction->setEnabled(false);
|
|
|
m_pFilterAction->setEnabled(false);
|
|
|
m_pRegionPressureAction->setEnabled(false);
|
|
|
m_pControlBar->getExportAnimationAction()->setEnabled(false);
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onImportVolumeRequested()
|
|
|
{
|
|
|
if(!canImportVolume()) return;
|
|
|
const QString sFilePath = QFileDialog::getOpenFileName(this, tr("Import 3D Model"),
|
|
|
QString(), tr("VTK Volume Mesh (*.vtk *.vtu)"));
|
|
|
if(!sFilePath.isEmpty()) importVolumeFile(sFilePath);
|
|
|
}
|
|
|
|
|
|
bool nmWxPebiResultViewWidget::importVolumeFile(const QString& sFilePath)
|
|
|
{
|
|
|
if(!isResultViewGuiThread() || sFilePath.isEmpty() || !canImportVolume()) return false;
|
|
|
m_pVolumeState->m_pTask = new nmVolumeImportTask(sFilePath, this);
|
|
|
m_pVolumeState->m_pProgress = new QProgressDialog(tr("Loading 3D model..."), tr("Cancel"), 0, 100, this);
|
|
|
m_pVolumeState->m_pProgress->setObjectName(QLatin1String("volumeImportProgress"));
|
|
|
m_pVolumeState->m_pProgress->setWindowModality(Qt::WindowModal);
|
|
|
m_pVolumeState->m_pProgress->setMinimumDuration(0);
|
|
|
m_pVolumeState->m_pProgress->setAutoClose(false);
|
|
|
m_pVolumeState->m_pProgress->setAutoReset(false);
|
|
|
connect(m_pVolumeState->m_pProgress, SIGNAL(canceled()), this, SLOT(onCancelVolumeImport()));
|
|
|
connect(m_pVolumeState->m_pTask, SIGNAL(progressChanged(int)),
|
|
|
m_pVolumeState->m_pProgress, SLOT(setValue(int)), Qt::QueuedConnection);
|
|
|
connect(m_pVolumeState->m_pTask, SIGNAL(finished()),
|
|
|
this, SLOT(onVolumeImportFinished()), Qt::QueuedConnection);
|
|
|
m_pVolumeState->m_pProgress->show();
|
|
|
synchronizeBusinessActions();
|
|
|
m_pVolumeState->m_pTask->start();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onCancelVolumeImport()
|
|
|
{
|
|
|
if(m_pVolumeState->m_pTask != nullptr) m_pVolumeState->m_pTask->requestCancel();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onVolumeImportFinished()
|
|
|
{
|
|
|
nmVolumeImportTask* pTask = m_pVolumeState->m_pTask;
|
|
|
if(pTask == nullptr || sender() != pTask) return;
|
|
|
// finished 发出后仍等待线程完全退出,确保结果和字符串已不再被工作线程改写。
|
|
|
pTask->wait();
|
|
|
const bool bCanceled = pTask->isCanceled();
|
|
|
const QSharedPointer<const nmVolumeData> pData = pTask->getResult();
|
|
|
QString sError = pTask->getError();
|
|
|
disconnect(pTask, nullptr, nullptr, nullptr);
|
|
|
delete pTask;
|
|
|
m_pVolumeState->m_pTask = nullptr;
|
|
|
// 模态进度更新可能嵌套处理 Qt 事件;延迟删除,避免在 setValue 栈内析构。
|
|
|
m_pVolumeState->m_pProgress->hide();
|
|
|
m_pVolumeState->m_pProgress->deleteLater();
|
|
|
m_pVolumeState->m_pProgress = nullptr;
|
|
|
if(bCanceled) {
|
|
|
synchronizeBusinessActions();
|
|
|
return;
|
|
|
}
|
|
|
nmVTKViewState oPreviousView;
|
|
|
m_pRenderContainer->captureViewState(oPreviousView);
|
|
|
if(pData.isNull() || !m_pVolumeState->m_pController->setData(pData, &sError)) {
|
|
|
m_sOperationError = sError.isEmpty() ? tr("Unable to display the 3D model.") : sError;
|
|
|
synchronizeBusinessActions();
|
|
|
// 空结果页也应显示导入错误,而不是把错误藏在不可见的旧结果状态中。
|
|
|
m_pStatusLabel->setStyleSheet("QLabel { color: #a32626; background: #fdecec; }");
|
|
|
m_pStatusLabel->setText(m_sOperationError);
|
|
|
m_pStatusLabel->show();
|
|
|
return;
|
|
|
}
|
|
|
// 候选模型绑定成功后才释放旧切片交互;失败、取消路径均保留原截面及草稿。
|
|
|
closeVolumeSliceSettings(true);
|
|
|
if(!isImportedVolumeMode()) {
|
|
|
m_pVolumeState->m_oSavedView = oPreviousView;
|
|
|
m_pVolumeState->m_eSavedCommands = m_pRenderContainer->getViewCommandController()->getCapabilities();
|
|
|
m_pControlBar->stopPlayback();
|
|
|
stopRegionPressureDrawing();
|
|
|
// 区域图层由 Scene 所有;先核实对象身份,避免访问已被外部清理的观察指针。
|
|
|
nmVTKScene* pScene = m_pRenderContainer->getScene();
|
|
|
const bool bHasRegionLayer = pScene != nullptr && m_pRegionState->m_pLayer != nullptr &&
|
|
|
pScene->getLayer(QString::fromLatin1(PEBI_REGION_PRESSURE_LAYER_ID)) == m_pRegionState->m_pLayer;
|
|
|
m_pVolumeState->m_bRegionVisible = bHasRegionLayer && m_pRegionState->m_pLayer->isVisible();
|
|
|
m_pVolumeState->m_bRegionDialogVisible = m_pRegionState->m_pDialog != nullptr && m_pRegionState->m_pDialog->isVisible();
|
|
|
if(bHasRegionLayer) m_pRegionState->m_pLayer->setVisible(false);
|
|
|
if(m_pRegionState->m_pDialog != nullptr) m_pRegionState->m_pDialog->hide();
|
|
|
m_pSceneController->setSuspended(true);
|
|
|
}
|
|
|
m_pRenderContainer->setViewCommandCapabilities(m_pVolumeState->m_eSavedCommands |
|
|
|
nmVTKViewCommandController::ViewCommand_Right |
|
|
|
nmVTKViewCommandController::ViewCommand_Isometric);
|
|
|
m_pVolumeState->m_bImported = true;
|
|
|
m_pControlBar->setVolumeSceneController(m_pVolumeState->m_pController);
|
|
|
m_pControlBar->show();
|
|
|
m_pRenderContainer->restoreDefaultInteraction();
|
|
|
m_pVolumeState->m_pController->activate(true);
|
|
|
m_sOperationError.clear();
|
|
|
synchronizeBusinessActions();
|
|
|
refreshStatusBar();
|
|
|
m_pRenderContainer->showReadyState();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onReturnToCalculationResult()
|
|
|
{
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr) return;
|
|
|
const QSharedPointer<const nmPebiResultSnapshot> pLatest = m_pDataManager.isNull()
|
|
|
? QSharedPointer<const nmPebiResultSnapshot>() : m_pDataManager->getPebiResultSnapshot();
|
|
|
const bool bSameSnapshot = !pLatest.isNull() && pLatest == m_pDisplayedSnapshot &&
|
|
|
pLatest->getSnapshotId() == m_sDisplayedSnapshotId && m_pSceneController->hasSnapshot();
|
|
|
if(!pLatest.isNull() && !bSameSnapshot) {
|
|
|
QString sError;
|
|
|
// PEBI 仍处于挂起状态,候选绑定既不显示图层,也不移动当前三维相机。
|
|
|
if(!m_pSceneController->setSnapshot(pLatest, &sError)) {
|
|
|
m_sOperationError = tr("Unable to return to the latest calculation result: %1").arg(sError);
|
|
|
refreshStatusBar();
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
closeVolumeSliceSettings(true);
|
|
|
m_pVolumeState->m_pController->hide();
|
|
|
m_pRenderContainer->setRotationCenterPickTarget(nullptr);
|
|
|
m_pVolumeState->m_bImported = false;
|
|
|
m_pRenderContainer->setViewCommandCapabilities(m_pVolumeState->m_eSavedCommands);
|
|
|
if(pLatest.isNull()) {
|
|
|
m_pSceneController->setSuspended(false);
|
|
|
m_pVolumeState->m_pController->clear();
|
|
|
clearSceneAndControls(tr("No result data."));
|
|
|
} else {
|
|
|
if(!bSameSnapshot) clearRegionPressureSelection(false);
|
|
|
m_pSceneController->setSuspended(false);
|
|
|
if(bSameSnapshot) {
|
|
|
m_pRenderContainer->restoreViewState(m_pVolumeState->m_oSavedView);
|
|
|
nmVTKScene* pScene = m_pRenderContainer->getScene();
|
|
|
if(pScene != nullptr && m_pRegionState->m_pLayer != nullptr &&
|
|
|
pScene->getLayer(QString::fromLatin1(PEBI_REGION_PRESSURE_LAYER_ID)) == m_pRegionState->m_pLayer) {
|
|
|
m_pRegionState->m_pLayer->setVisible(m_pVolumeState->m_bRegionVisible);
|
|
|
}
|
|
|
if(m_pVolumeState->m_bRegionDialogVisible && m_pRegionState->m_pDialog != nullptr) m_pRegionState->m_pDialog->show();
|
|
|
} else {
|
|
|
m_pRenderContainer->fitTopViewWithoutRender();
|
|
|
m_pRenderContainer->resetRotationCenterWithoutRender();
|
|
|
}
|
|
|
m_pVolumeState->m_pController->clear();
|
|
|
m_pDisplayedSnapshot = pLatest;
|
|
|
m_sDisplayedSnapshotId = pLatest->getSnapshotId();
|
|
|
rememberObservedState(pLatest, m_sDisplayedSnapshotId, m_pDataManager->isPebiResultSnapshotCurrent());
|
|
|
m_sBindingError.clear();
|
|
|
m_sOperationError.clear();
|
|
|
m_pRenderContainer->showReadyState();
|
|
|
}
|
|
|
m_pVolumeState->m_bPendingSync = false;
|
|
|
m_pVolumeState->m_oSavedView = nmVTKViewState();
|
|
|
m_pControlBar->setVolumeSceneController(nullptr);
|
|
|
m_pControlBar->setExternalInteractionLocked(false);
|
|
|
m_pControlBar->show();
|
|
|
m_pControlBar->synchronizeFromScene();
|
|
|
synchronizeBusinessActions();
|
|
|
refreshStatusBar();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::closeVolumeSliceSettings(bool bReleaseData)
|
|
|
{
|
|
|
if(m_pVolumeState == nullptr) return;
|
|
|
if(m_pVolumeState->m_pSliceInteraction != nullptr) m_pVolumeState->m_pSliceInteraction->unbind();
|
|
|
nmWxVolumeSliceSettingsDlg* pDialog = m_pVolumeState->m_pSliceDialog;
|
|
|
if(pDialog != nullptr) {
|
|
|
// 场景切换由外层统一 Render,不能由 hideEvent 在事务中途触发一次空白画面。
|
|
|
const bool bBlocked = pDialog->blockSignals(true);
|
|
|
pDialog->hide();
|
|
|
if(bReleaseData) pDialog->setData(QSharedPointer<const nmVolumeData>(), nmVolumeSliceSettings());
|
|
|
pDialog->blockSignals(bBlocked);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::synchronizeSliceInteraction()
|
|
|
{
|
|
|
if(m_pVolumeState == nullptr || m_pVolumeState->m_pSliceInteraction == nullptr) return;
|
|
|
nmWxVolumeSliceSettingsDlg* pDialog = m_pVolumeState->m_pSliceDialog;
|
|
|
const bool bEnabled = isImportedVolumeMode() && m_pVolumeState->m_pTask == nullptr &&
|
|
|
m_pVolumeState->m_pController->isSliceEnabled() && pDialog != nullptr && pDialog->isVisible() &&
|
|
|
m_pVolumeState->m_pController->getSliceSettings().m_bShowPlane && !m_pRenderContainer->isRotationCenterPicking();
|
|
|
const bool bWasEnabled = m_pVolumeState->m_pSliceInteraction->isEnabled();
|
|
|
m_pVolumeState->m_pSliceInteraction->setEnabled(bEnabled);
|
|
|
if(bWasEnabled != m_pVolumeState->m_pSliceInteraction->isEnabled()) {
|
|
|
// 拾取信号也会在场景绑定过程中发出,不能在该调用栈内立即 Render。
|
|
|
QMetaObject::invokeMethod(this, "refreshVolumeSliceView", Qt::QueuedConnection);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::refreshVolumeSliceView()
|
|
|
{
|
|
|
if(isImportedVolumeMode()) m_pRenderContainer->renderView();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onVolumeSliceToggled(bool bEnabled)
|
|
|
{
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr) { synchronizeVolumeActions(); return; }
|
|
|
QString sError;
|
|
|
if(!m_pVolumeState->m_pController->setSliceEnabled(bEnabled, &sError)) {
|
|
|
m_sOperationError = sError.isEmpty() ? tr("Unable to display the slice.") : sError;
|
|
|
} else {
|
|
|
m_sOperationError.clear();
|
|
|
if(!bEnabled) {
|
|
|
closeVolumeSliceSettings(false);
|
|
|
m_pRenderContainer->renderView();
|
|
|
}
|
|
|
}
|
|
|
synchronizeVolumeActions();
|
|
|
refreshStatusBar();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onShowVolumeSliceSettings()
|
|
|
{
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr) return;
|
|
|
nmVolumeSceneController* pController = m_pVolumeState->m_pController;
|
|
|
QString sError;
|
|
|
if(!pController->setSliceEnabled(true, &sError)) {
|
|
|
m_sOperationError = sError.isEmpty() ? tr("Unable to display the slice.") : sError;
|
|
|
refreshStatusBar();
|
|
|
return;
|
|
|
}
|
|
|
if(m_pVolumeState->m_pSliceDialog == nullptr) {
|
|
|
nmWxVolumeSliceSettingsDlg* pDialog = new nmWxVolumeSliceSettingsDlg(this);
|
|
|
m_pVolumeState->m_pSliceDialog = pDialog;
|
|
|
connect(pDialog, SIGNAL(applyRequested()), this, SLOT(onApplyVolumeSlice()));
|
|
|
connect(pDialog, SIGNAL(previewChanged()), this, SLOT(onVolumeSliceDraftChanged()));
|
|
|
connect(pDialog, SIGNAL(displayOptionsChanged(bool,bool)), this, SLOT(onVolumeSliceDisplayChanged(bool,bool)));
|
|
|
connect(pDialog, SIGNAL(dialogHidden()), this, SLOT(onVolumeSliceDialogHidden()));
|
|
|
}
|
|
|
nmWxVolumeSliceSettingsDlg* pDialog = m_pVolumeState->m_pSliceDialog;
|
|
|
if(!pDialog->isVisible()) {
|
|
|
const QSharedPointer<const nmVolumeData> pData = pController->getData();
|
|
|
if(pData.isNull()) return;
|
|
|
const nmVolumeSliceSettings oSettings = pController->getSliceSettings();
|
|
|
pDialog->setData(pData, oSettings);
|
|
|
double dBounds[6];
|
|
|
pData->getBounds(dBounds);
|
|
|
m_pVolumeState->m_pSliceInteraction->bind(pData->getDataId(), dBounds, oSettings);
|
|
|
}
|
|
|
pDialog->show();
|
|
|
pDialog->raise();
|
|
|
pDialog->activateWindow();
|
|
|
m_sOperationError.clear();
|
|
|
synchronizeVolumeActions();
|
|
|
refreshStatusBar();
|
|
|
m_pRenderContainer->renderView();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onApplyVolumeSlice()
|
|
|
{
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr ||
|
|
|
m_pVolumeState->m_pSliceDialog == nullptr || !m_pVolumeState->m_pSliceDialog->isVisible()) return;
|
|
|
nmVolumeSliceSettings oSettings;
|
|
|
QString sError;
|
|
|
if(!m_pVolumeState->m_pSliceDialog->getDraft(oSettings, &sError)) {
|
|
|
m_pVolumeState->m_pSliceDialog->setError(sError);
|
|
|
return;
|
|
|
}
|
|
|
if(!m_pVolumeState->m_pController->applySliceSettings(oSettings, &sError)) {
|
|
|
m_sOperationError = sError.isEmpty() ? tr("Unable to display the slice.") : sError;
|
|
|
m_pVolumeState->m_pSliceDialog->setError(m_sOperationError);
|
|
|
} else {
|
|
|
m_sOperationError.clear();
|
|
|
const nmVolumeSliceSettings oApplied = m_pVolumeState->m_pController->getSliceSettings();
|
|
|
m_pVolumeState->m_pSliceDialog->setAppliedSettings(oApplied);
|
|
|
m_pVolumeState->m_pSliceInteraction->setPreview(oApplied);
|
|
|
}
|
|
|
synchronizeVolumeActions();
|
|
|
refreshStatusBar();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onVolumeSliceDraftChanged()
|
|
|
{
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr || m_pVolumeState->m_pSliceDialog == nullptr) return;
|
|
|
nmVolumeSliceSettings oDraft;
|
|
|
if(m_pVolumeState->m_pSliceDialog->getDraft(oDraft)) {
|
|
|
m_pVolumeState->m_pSliceInteraction->setPreview(oDraft);
|
|
|
m_pRenderContainer->renderView();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onVolumeSliceDisplayChanged(bool bShowPlane, bool bShowSource)
|
|
|
{
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr) return;
|
|
|
m_pVolumeState->m_pController->setSliceDisplayOptions(bShowPlane, bShowSource);
|
|
|
synchronizeSliceInteraction();
|
|
|
m_pRenderContainer->renderView();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onVolumeSliceDialogHidden()
|
|
|
{
|
|
|
if(m_pVolumeState == nullptr || m_pVolumeState->m_pSliceInteraction == nullptr) return;
|
|
|
m_pVolumeState->m_pSliceInteraction->unbind();
|
|
|
m_pRenderContainer->renderView();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onVolumeSlicePreview(const QString& sDataId, const nmVolumeSliceSettings& oSettings)
|
|
|
{
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr ||
|
|
|
!m_pVolumeState->m_pController->isSliceEnabled() || m_pVolumeState->m_pSliceDialog == nullptr ||
|
|
|
!m_pVolumeState->m_pSliceDialog->isVisible()) return;
|
|
|
const QSharedPointer<const nmVolumeData> pData = m_pVolumeState->m_pController->getData();
|
|
|
if(pData.isNull() || pData->getDataId() != sDataId) return;
|
|
|
m_pVolumeState->m_pSliceDialog->setPlanePreview(oSettings);
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultViewWidget::onVolumeSliceDragApplied(const QString& sDataId, const nmVolumeSliceSettings& oSettings)
|
|
|
{
|
|
|
Q_UNUSED(oSettings);
|
|
|
if(!isImportedVolumeMode() || m_pVolumeState->m_pTask != nullptr || !m_pVolumeState->m_pController->isSliceEnabled()) return;
|
|
|
const QSharedPointer<const nmVolumeData> pData = m_pVolumeState->m_pController->getData();
|
|
|
if(pData.isNull() || pData->getDataId() != sDataId) return;
|
|
|
onApplyVolumeSlice();
|
|
|
}
|