You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp

1482 lines
58 KiB
C++

#include "nmWxNumericalDesign.h"
#include <QScrollArea>
#include <QCoreApplication>
#include <QGroupBox>
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
#include <QRadioButton>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QMessageBox>
#include "iUnitGroup.h"
#include "iUnitWxBinder.h"
#include "mModuleDefines.h"
#include "nmWxIncludeOtherWells.h"
#include "nmWxTimeDependentSkin.h"
#include "nmWxAutomaticFitting.h"
#include "nmWxParameterProperty.h"
#include "nmWxForecast.h"
#include "nmWxSensitive.h"
#include "nmDataWellBase.h"
#include "nmDataReservoir.h"
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSnapshot.h"
#include "nmDataTimeStepSetting.h"
#include "nmDataDiagnostic.h"
namespace
{
const char* const s_pTimeGrowthExponentId = "NM_TimeGrowthExponent";
const char* const s_pDtMinId = "NM_DtMin";
const char* const s_pDtMaxId = "NM_DtMax";
nmParameterFieldInfo createTimeStepFieldInfo(
const char* pParameterId,
bool bUnitEnabled)
{
nmParameterFieldInfo oFieldInfo;
oFieldInfo.m_sParameterId = QLatin1String(pParameterId);
oFieldInfo.m_sBaseParameterId = QLatin1String(pParameterId);
oFieldInfo.m_eEditorType = NM_PARAMETER_EDITOR_NUMBER;
oFieldInfo.m_bUnitEnabled = bUnitEnabled;
return oFieldInfo;
}
// 判断其他 Map 井是否具备进入 Include Other Wells 候选集的数值井型。
bool isSupportedAdditionalWell(nmDataWellBase* pWellData)
{
if(pWellData == nullptr || pWellData->getWellCode().isEmpty()) {
return false;
}
const NM_WELL_MODEL eWellType = pWellData->getWellType();
return eWellType == Vertical_Well ||
eWellType == Vertical_Fractured_Well ||
eWellType == Horizontal_Fractured_Well;
}
// 可配置性看框架记录本身,不能看当前是否已把三个相都设为“无”,
// 否则用户清空分相后将无法再次打开对话框恢复选择。
bool hasAvailableRateTarget(nmDataWellBase* pWellData)
{
if(pWellData == nullptr ||
!nmIsValidWellCategory(pWellData->getWellCategory())) {
return false;
}
return pWellData->hasAvailableFlowRate();
}
}
nmWxNumericalDesign* nmWxNumericalDesign::s_pCurrentInstance = nullptr;
static bool isNumericalDesignDebugOptionsVisible()
{
#ifdef QT_DEBUG
return true;
#endif
return false;
}
// 显示当前模型类型
static QString solverModelDisplayName(NM_SOLVER_MODEL_TYPE eType)
{
switch(eType) {
case SMT_Oil_ConstPvt:
return nmWxNumericalDesign::tr("Oil single-phase constant pvt");
case SMT_Oil_VariablePvt:
return nmWxNumericalDesign::tr("Oil single-phase variable pvt");
case SMT_Water_ConstPvt:
return nmWxNumericalDesign::tr("Water single-phase constant pvt");
case SMT_Water_VariablePvt:
return nmWxNumericalDesign::tr("Water single-phase variable pvt");
case SMT_Gas_VariablePvt:
return nmWxNumericalDesign::tr("Gas single-phase variable pvt");
case SMT_Oil_Gas_TwoPhase:
return nmWxNumericalDesign::tr(
"Oil-gas two-phase (solver unavailable)");
case SMT_Oil_Water_TwoPhase:
return nmWxNumericalDesign::tr("Oil-water two-phase");
case SMT_Gas_Water_TwoPhase:
return nmWxNumericalDesign::tr(
"Gas-water two-phase (solver unavailable)");
case SMT_Oil_Gas_Water_ThreePhase:
return nmWxNumericalDesign::tr(
"Oil-gas-water three-phase (solver unavailable)");
default:
return QString();
}
}
nmWxNumericalDesign::nmWxNumericalDesign(
nmDataAnalyzeManager* pDataManager,
QWidget* pParent)
: iDlgBase(pParent),
m_pDataManager(pDataManager),
m_pTimeGrowthExponentField(nullptr),
m_pDtMinField(nullptr),
m_pDtMaxField(nullptr)
{
Q_ASSERT(m_pDataManager != nullptr);
// 设置当前实例为静态指针
s_pCurrentInstance = this;
m_pModelAndResultGroup = nullptr;
m_pResultWellCombo = nullptr;
m_pCurrentModelCombo = nullptr;
m_bFillingResultWellCombo = false;
m_bFillingCurrentModelCombo = false;
// 单位绑定器必须先切换到数值试井参数系列,再创建时间步字段。
setParaSrcTag(s_Nm_Serie);
// 初始化图标路径
m_sIconDir = QCoreApplication::applicationDirPath();
m_sIconDir = m_sIconDir.section('/', 0, -2); // 获取上一级目录
m_sIconDir += "/Res/Icon/";
// 设置窗口的尺寸策略为可扩展
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// 创建滚动区域
QScrollArea* pScrollArea = new QScrollArea(this);
pScrollArea->setWidgetResizable(true);
QWidget* pScrollWidget = new QWidget();
pScrollWidget->setObjectName("nmNumericalDesignScrollWidget");
pScrollWidget->setStyleSheet("#nmNumericalDesignScrollWidget { background-color: white; }");
QVBoxLayout* pScrollLayout = new QVBoxLayout(pScrollWidget);
// 初始化所有UI组件
initMainOptionsGroup(); // 初始化主选项组
initModelAndResultGroup();
initOutputGroup(); // 初始化输出组 (新)
initAdvancedGroup(); // 初始化高级组 (新)
initTimeSteppingGroup(); // 初始化时间步进组
initNumericalSettingsGroup(); // 初始化数值设置组 (新)
initPebiSolverSettingsGroup(); // 初始化PEBI求解器设置组
initButton(); // 初始化生成按钮
// 将各个组添加到滚动区域的布局中
pScrollLayout->addWidget(m_pMainOptionsGroup);
pScrollLayout->addWidget(m_pModelAndResultGroup);
pScrollLayout->addWidget(m_pOutputGroup);
pScrollLayout->addWidget(m_pAdvancedGroup);
pScrollLayout->addWidget(m_pTimeSteppingGroup);
pScrollLayout->addWidget(m_pNumericalSettingsGroup); // 新的数值设置组
pScrollLayout->addWidget(m_pPebiSolverSettingsGroup);
pScrollLayout->addStretch(); // 添加拉伸器,使内容靠上
pScrollWidget->setLayout(pScrollLayout); // 设置滚动容器的布局
pScrollArea->setWidget(pScrollWidget); // 将滚动容器设置给滚动区域
// 设置主布局并添加滚动区域
m_pMainLayout = new QVBoxLayout(this); // 窗口的主布局
m_pMainLayout->addWidget(pScrollArea); // 将滚动区域添加到主布局
m_pMainLayout->addLayout(m_pGenerateLayout); // 将生成按钮布局添加到主布局底部
updateUiFromData(); // 更新时间步进组的组件值
// 设置信号槽连接
setupConnections();
//对于没有功能的组件release下隐藏debug下显示
bool bDebugOptionsVisible = isNumericalDesignDebugOptionsVisible();
m_pImposePiCheck->setVisible(bDebugOptionsVisible);
m_pShowAveragePressureCheck->setVisible(bDebugOptionsVisible);
m_pOutputGroup->setVisible(bDebugOptionsVisible);
m_pUseWellIntakeCheck->setVisible(bDebugOptionsVisible);
m_pRedefineKrPcCheck->setVisible(bDebugOptionsVisible);
m_pRedefineKrPcIconButton->setVisible(bDebugOptionsVisible);
m_pApplyUnconsolidationCheck->setVisible(bDebugOptionsVisible);
m_pApplyUnconsolidationIconButton->setVisible(bDebugOptionsVisible);
m_pUseDFNCheck->setVisible(bDebugOptionsVisible);
m_pNumericalSettingsGroup->setVisible(bDebugOptionsVisible);
m_pForecastBtn->setVisible(bDebugOptionsVisible);
m_pSensitiveBtn->setVisible(bDebugOptionsVisible);
}
nmWxNumericalDesign::~nmWxNumericalDesign()
{
// 所有子控件将由Qt的父子对象机制自动删除
// 如果当前实例是静态指针指向的实例,则清空静态指针
if(s_pCurrentInstance == this) {
s_pCurrentInstance = nullptr;
}
}
bool nmWxNumericalDesign::bindUnitField(
QLineEdit* pLineEdit,
QComboBox* pUnitComboBox,
const QString& sBaseParameterId)
{
return bindUnitPairWxs(pLineEdit,
pUnitComboBox,
sBaseParameterId,
false);
}
void nmWxNumericalDesign::setUnitBaseValue(
QLineEdit* pLineEdit,
double dBaseValue)
{
if(m_pUnitBinder != nullptr) {
m_pUnitBinder->setBaseValue(pLineEdit, dBaseValue);
}
}
double nmWxNumericalDesign::unitBaseValue(QLineEdit* pLineEdit) const
{
return m_pUnitBinder != nullptr
? m_pUnitBinder->getBaseValue(pLineEdit) : 0.0;
}
bool nmWxNumericalDesign::isUnitInputValid(QLineEdit* pLineEdit)
{
return m_pUnitBinder != nullptr &&
m_pUnitBinder->isInputOK(pLineEdit, true, nullptr);
}
QString nmWxNumericalDesign::currentUnit(QLineEdit* pLineEdit) const
{
return m_pUnitBinder != nullptr
? m_pUnitBinder->getCurrentUnit(pLineEdit) : QString();
}
iUnitGroup* nmWxNumericalDesign::unitGroup(QLineEdit* pLineEdit) const
{
return m_pUnitBinder != nullptr
? m_pUnitBinder->getUnitGroup(pLineEdit) : nullptr;
}
// 激活当前成果面板,并刷新查看井结果下拉框
void nmWxNumericalDesign::activateCurrentInstance()
{
// 一个QMdiArea中可以同时打开多个成果窗口每个成果都有自己的数值解面板。
// 已经打开过的成果再次被激活时不会重新构造面板,因此需要在切换时更新静态指针。
s_pCurrentInstance = this;
// 调用前已经切换到当前成果此处统一刷新该Dock中的全部数据控件。
updateUiFromData();
}
// 外部刷新当前查看井下拉框
void nmWxNumericalDesign::notifyResultWellSelectorChanged(
nmDataAnalyzeManager* pDataManager,
const QString& sWellInstanceId)
{
// 静态活动面板可能已经切换到另一个成果,只刷新所属数据管理器一致的实例。
if(s_pCurrentInstance == nullptr ||
s_pCurrentInstance->m_pDataManager != pDataManager) {
return;
}
s_pCurrentInstance->fillResultWellCombo(sWellInstanceId);
}
// 初始化模型与结果切换控件
void nmWxNumericalDesign::initModelAndResultGroup()
{
m_pModelAndResultGroup = new QGroupBox(tr("Model and result"), this);
QGridLayout* pGridLayout = new QGridLayout(m_pModelAndResultGroup);
pGridLayout->setContentsMargins(8, 15, 8, 10);
pGridLayout->setHorizontalSpacing(6);
pGridLayout->setVerticalSpacing(6);
QLabel* pCurrentModelLabel = new QLabel(
tr("Current model"), m_pModelAndResultGroup);
QLabel* pResultWellLabel = new QLabel(
tr("Result well"), m_pModelAndResultGroup);
pCurrentModelLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
pResultWellLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
m_pCurrentModelCombo = new QComboBox(m_pModelAndResultGroup);
m_pResultWellCombo = new QComboBox(m_pModelAndResultGroup);
QList<QComboBox*> listCombos;
listCombos << m_pCurrentModelCombo << m_pResultWellCombo;
for(int nIndex = 0; nIndex < listCombos.size(); ++nIndex) {
listCombos[nIndex]->setFixedHeight(nmParameterField::editorHeight());
listCombos[nIndex]->setMinimumWidth(
nmParameterField::editorMinimumWidth());
listCombos[nIndex]->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Fixed);
}
// 按当前成果回填模型和结果井
updateCurrentModelDisplay();
fillResultWellCombo();
pGridLayout->addWidget(pCurrentModelLabel, 0, 0);
pGridLayout->addWidget(m_pCurrentModelCombo, 0, 1);
pGridLayout->addWidget(pResultWellLabel, 1, 0);
pGridLayout->addWidget(m_pResultWellCombo, 1, 1);
pGridLayout->setColumnStretch(0, 0);
pGridLayout->setColumnStretch(1, 1);
m_pModelAndResultGroup->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Preferred);
}
// 按当前成果对应的数据管理器刷新模型下拉框选中项
void nmWxNumericalDesign::updateCurrentModelDisplay()
{
if(m_pCurrentModelCombo == nullptr) {
return;
}
m_bFillingCurrentModelCombo = true;
m_pCurrentModelCombo->clear();
nmDataAnalyzeManager* pManager = m_pDataManager;
if(pManager == nullptr) {
m_pCurrentModelCombo->setEnabled(false);
m_bFillingCurrentModelCombo = false;
return;
}
// 根据储层相态过滤可选模型类型
nmDataReservoir* pReservoir = pManager->getReservoirData();
NM_PHASE_TYPE ePhase = (pReservoir != nullptr) ? pReservoir->getPhaseType() : PHASE_UNKNOWN;
NM_SOLVER_MODEL_TYPE eCurrentType = pManager->getSolverModelType();
const bool bCurrentModelSupported =
nmIsSupportedPebiSolverModel(eCurrentType);
if(!bCurrentModelSupported) {
// 未开放模型仍显示分析页传入的真实相态,但不允许用户切换。
const QString sCurrentModelName = solverModelDisplayName(eCurrentType);
if(!sCurrentModelName.isEmpty()) {
m_pCurrentModelCombo->addItem(sCurrentModelName, eCurrentType);
}
} else {
switch(ePhase) {
case PHASE_Oil:
m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Oil_ConstPvt), SMT_Oil_ConstPvt);
m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Oil_VariablePvt), SMT_Oil_VariablePvt);
break;
case PHASE_Water:
m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Water_ConstPvt), SMT_Water_ConstPvt);
m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Water_VariablePvt), SMT_Water_VariablePvt);
break;
case PHASE_Gas:
m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Gas_VariablePvt), SMT_Gas_VariablePvt);
break;
case PHASE_Oil_Water:
m_pCurrentModelCombo->addItem(solverModelDisplayName(SMT_Oil_Water_TwoPhase), SMT_Oil_Water_TwoPhase);
break;
default:
break;
}
}
int nIndex = m_pCurrentModelCombo->findData(eCurrentType);
if(nIndex >= 0) {
m_pCurrentModelCombo->setCurrentIndex(nIndex);
}
m_pCurrentModelCombo->setEnabled(
bCurrentModelSupported && m_pCurrentModelCombo->count() > 0);
m_bFillingCurrentModelCombo = false;
}
// 按最后成功快照刷新结果井下拉框
void nmWxNumericalDesign::fillResultWellCombo(
const QString& sSelectedWellInstanceId)
{
if(m_pResultWellCombo == nullptr) {
return;
}
nmDataAnalyzeManager* pManager = m_pDataManager;
m_bFillingResultWellCombo = true;
m_pResultWellCombo->clear();
if(pManager == nullptr) {
m_pResultWellCombo->setEnabled(false);
m_bFillingResultWellCombo = false;
return;
}
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
pManager->getPebiResultSnapshot();
if(pSnapshot.isNull()) {
m_pResultWellCombo->setEnabled(false);
m_bFillingResultWellCombo = false;
return;
}
// 名称和顺序始终来自求解时快照;实时 UUID 不存在时只增加删除标记。
const QStringList& listWellIds =
pSnapshot->getDisplayWellInstanceIds();
for(int nWellIndex = 0; nWellIndex < listWellIds.size(); ++nWellIndex) {
const QString& sWellInstanceId = listWellIds[nWellIndex];
const nmPebiResultWellSnapshot* pWell =
pSnapshot->findWell(sWellInstanceId);
if(pWell == nullptr) {
continue;
}
QString sDisplayName = pWell->m_sWellName;
if(pManager->findWellByInstanceId(sWellInstanceId) == nullptr) {
sDisplayName = tr("%1 (Deleted)").arg(sDisplayName);
}
m_pResultWellCombo->addItem(sDisplayName, sWellInstanceId);
}
QString sCurrentWellInstanceId = sSelectedWellInstanceId;
if(sCurrentWellInstanceId.isEmpty()) {
sCurrentWellInstanceId =
pManager->getCurrentResultWellInstanceId();
}
int nIndex = m_pResultWellCombo->findData(sCurrentWellInstanceId);
if(nIndex < 0 && m_pResultWellCombo->count() > 0) {
nIndex = 0;
}
if(nIndex >= 0) {
m_pResultWellCombo->setCurrentIndex(nIndex);
pManager->setCurrentResultWellInstanceId(
m_pResultWellCombo->itemData(nIndex).toString());
}
m_pResultWellCombo->setEnabled(m_pResultWellCombo->count() > 1);
m_bFillingResultWellCombo = false;
}
void nmWxNumericalDesign::initMainOptionsGroup()
{
m_pMainOptionsGroup = new QGroupBox(tr("Main options"), this);
QGridLayout* pGridLayout = new QGridLayout(m_pMainOptionsGroup); // 使用 QGridLayout
pGridLayout->setContentsMargins(8, 15, 8, 10);
pGridLayout->setHorizontalSpacing(6);
pGridLayout->setVerticalSpacing(6);
// 第一行: Include other wells + Gear Icon (左侧), Reset from diagnostic (右侧)
m_pIncludeOtherWellsCheck = new QCheckBox(tr("Include other wells"), this);
if(m_pDataManager != nullptr) {
m_pIncludeOtherWellsCheck->setChecked(
m_pDataManager->getIncludeOtherWells());
}
m_pOptionsGearButton = new QPushButton(this);
m_pOptionsGearButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign1.png"));
m_pOptionsGearButton->setIconSize(QSize(20, 20));
m_pOptionsGearButton->setFixedSize(22, 22);
m_pOptionsGearButton->setEnabled(m_pIncludeOtherWellsCheck->isChecked());
m_pResetDiagnosticButton = new QPushButton(tr("Reset from diagnostic"), this);
m_pResetDiagnosticButton->setFixedHeight(nmParameterField::editorHeight());
m_pResetDiagnosticButton->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Fixed);
// 为左侧的复选框和齿轮创建一个QHBoxLayout以便它们可以紧密排列
QHBoxLayout* pIncludeWellsLayout = new QHBoxLayout();
pIncludeWellsLayout->addWidget(m_pIncludeOtherWellsCheck);
pIncludeWellsLayout->addWidget(m_pOptionsGearButton);
pIncludeWellsLayout->addStretch(); // 将它们推到左侧
pGridLayout->addLayout(pIncludeWellsLayout, 0, 0, Qt::AlignLeft); // 第0行第0列左对齐
pGridLayout->addWidget(m_pResetDiagnosticButton, 0, 1);
// 第二行: Impose pi (左侧), Reset from analytical (右侧)
m_pImposePiCheck = new QCheckBox(tr("Impose pi"), this);
m_pImposePiCheck->setChecked(true); // 默认选中
m_pResetAnalyticalButton = new QPushButton(tr("Reset from analytical"), this);
m_pResetAnalyticalButton->setFixedHeight(nmParameterField::editorHeight());
m_pResetAnalyticalButton->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Fixed);
pGridLayout->addWidget(m_pImposePiCheck, 1, 0, Qt::AlignLeft); // 第1行第0列左对齐
pGridLayout->addWidget(m_pResetAnalyticalButton, 1, 1);
// 第三行: Show average pressure (左侧), 右侧留空
m_pShowAveragePressureCheck = new QCheckBox(tr("Show average pressure"), this);
m_pShowAveragePressureCheck->setChecked(false); // 默认未选中
pGridLayout->addWidget(m_pShowAveragePressureCheck, 2, 0, Qt::AlignLeft); // 第2行第0列左对齐
pGridLayout->setColumnStretch(0, 0);
pGridLayout->setColumnStretch(1, 1);
m_pMainOptionsGroup->setLayout(pGridLayout); // 设置分组框布局
m_pMainOptionsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
void nmWxNumericalDesign::initOutputGroup()
{
m_pOutputGroup = new QGroupBox(tr("Output"), this);
QGridLayout* pGridLayout = new QGridLayout(m_pOutputGroup);
// 设置网格的边距和间距,确保内容不会太贴近边缘
pGridLayout->setContentsMargins(10, 10, 10, 10); // 调整内边距
pGridLayout->setHorizontalSpacing(10); // 水平间距
pGridLayout->setVerticalSpacing(5); // 垂直间距
// 第一行: Output result fields (复选框) + Automatic (单选按钮)
m_pOutputResultFieldsCheck = new QCheckBox(tr("Output result fields"), this);
m_pOutputResultFieldsCheck->setChecked(true); // 默认选中
m_pOutputAutomaticRadio = new QRadioButton(tr("Automatic"), this);
m_pOutputAutomaticRadio->setChecked(true); // 默认选中Automatic
// 根据 m_pOutputResultFieldsCheck 的初始状态设置单选按钮的初始启用状态
bool isOutputChecked = m_pOutputResultFieldsCheck->isChecked();
m_pOutputAutomaticRadio->setEnabled(isOutputChecked); //
// 将 Output result fields 放在第0行第0列
pGridLayout->addWidget(m_pOutputResultFieldsCheck, 0, 0, Qt::AlignLeft);
// 将 Automatic 放在第0行第1列
pGridLayout->addWidget(m_pOutputAutomaticRadio, 0, 1, Qt::AlignLeft);
// 第二行: Manual (单选按钮) 和 齿轮图标,挨在一起,并且整体在右边
m_pOutputManualRadio = new QRadioButton(tr("Manual"), this);
m_pOutputManualIconButton = new QPushButton(this);
m_pOutputManualIconButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign1.png")); // 确认图标路径正确
m_pOutputManualIconButton->setIconSize(QSize(20, 20));
m_pOutputManualIconButton->setFixedSize(22, 22);
// m_pOutputManualIconButton 的初始启用状态将由两个条件决定:
// 1. m_pOutputResultFieldsCheck 是否被勾选
// 2. m_pOutputManualRadio 是否被选中
// 这里先根据 m_pOutputResultFieldsCheck 设置一个初始值,后续连接会完善逻辑
m_pOutputManualIconButton->setEnabled(isOutputChecked && m_pOutputManualRadio->isChecked()); //
// 将 Manual 和 齿轮图标放在一个 QHBoxLayout 中,让它们挨在一起
QHBoxLayout* pManualAndIconButtonLayout = new QHBoxLayout();
pManualAndIconButtonLayout->addWidget(m_pOutputManualRadio);
pManualAndIconButtonLayout->addWidget(m_pOutputManualIconButton);
pManualAndIconButtonLayout->addStretch(); // 使它们紧靠在一起,并推到该布局的左侧
// 将包含 Manual 和图标的 QHBoxLayout 放在第1行第1列并右对齐
pGridLayout->addLayout(pManualAndIconButtonLayout, 1, 1, Qt::AlignRight);
// 第三行: Output well drainage results (复选框)
m_pOutputWellDrainageCheck = new QCheckBox(tr("Output well drainage results"), this);
m_pOutputWellDrainageCheck->setChecked(false); // 默认未选中
m_pOutputWellDrainageCheck->setEnabled(isOutputChecked); // 初始状态也受主复选框控制
// 跨越所有列0到1共2列左对齐
pGridLayout->addWidget(m_pOutputWellDrainageCheck, 2, 0, 1, 2, Qt::AlignLeft);
// 设置列拉伸:
// 第0列 (Output result fields): 不拉伸,只占其内容宽度
pGridLayout->setColumnStretch(0, 0);
// 第1列 (Automatic / Manual & Icon group): 拉伸,以提供足够的空间并将右侧的 Manual & Icon group 推到右边
pGridLayout->setColumnStretch(1, 1);
m_pOutputGroup->setLayout(pGridLayout);
m_pOutputGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
}
void nmWxNumericalDesign::initAdvancedGroup()
{
m_pAdvancedGroup = new QGroupBox(tr("Advanced"), this); // 创建高级分组框
QVBoxLayout* pMainVLayout = new QVBoxLayout(m_pAdvancedGroup); // 高级组的垂直布局
// 时间相关复选框和关联的时钟图标按钮
QHBoxLayout* pTimeDependentLayout = new QHBoxLayout(); // 为时间相关复选框和图标创建水平布局
m_pTimeDependentCheck = new QCheckBox(tr("Time dependent"), this); // "时间相关"复选框
m_pTimeDependentCheck->setChecked(false); // 默认未选中
m_pTimeDependentIconButton = new QPushButton(this); // 时钟图标按钮
m_pTimeDependentIconButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign3.png")); // 使用时钟图标
m_pTimeDependentIconButton->setIconSize(QSize(20, 20));
m_pTimeDependentIconButton->setFixedSize(22, 22);
m_pTimeDependentIconButton->setEnabled(false); // 默认禁用,根据复选框状态启用
pTimeDependentLayout->addWidget(m_pTimeDependentCheck); // 添加复选框
pTimeDependentLayout->addWidget(m_pTimeDependentIconButton); // 添加图标按钮
pTimeDependentLayout->addStretch(); // 添加拉伸器,将它们推到左侧
pMainVLayout->addLayout(pTimeDependentLayout); // 将这个水平布局添加到主垂直布局
m_pUseWellIntakeCheck = new QCheckBox(tr("Use well intake"), this); // "使用井筒进水"复选框
m_pUseWellIntakeCheck->setChecked(false); // 默认未选中
pMainVLayout->addWidget(m_pUseWellIntakeCheck); // 添加到主垂直布局
QHBoxLayout* pRedefineKrPcLayout = new QHBoxLayout(); // "重新定义 KrPc"的水平布局
m_pRedefineKrPcCheck = new QCheckBox(tr("Redefine KrPc in hydraulic fractures"), this); // 复选框
m_pRedefineKrPcIconButton = new QPushButton(this); // 图标按钮
m_pRedefineKrPcIconButton->setIcon(QIcon(m_sIconDir + "KK.png")); // 根据图片,这个图标应该是曲线图
m_pRedefineKrPcIconButton->setIconSize(QSize(40, 40));
m_pRedefineKrPcIconButton->setFixedSize(42, 42);
m_pRedefineKrPcIconButton->setEnabled(false); // 默认禁用
pRedefineKrPcLayout->addWidget(m_pRedefineKrPcCheck);
pRedefineKrPcLayout->addWidget(m_pRedefineKrPcIconButton);
pRedefineKrPcLayout->addStretch(); // 添加拉伸器
pMainVLayout->addLayout(pRedefineKrPcLayout); // 添加到主垂直布局
QHBoxLayout* pApplyUnconsolidationLayout = new QHBoxLayout(); // "应用未固结"的水平布局
m_pApplyUnconsolidationCheck = new QCheckBox(tr("Apply unconsolidation in hydraulic fractures"), this); // 复选框
m_pApplyUnconsolidationIconButton = new QPushButton(this); // 图标按钮
m_pApplyUnconsolidationIconButton->setIcon(QIcon(m_sIconDir + "SS.png")); // 根据图片,这个图标应该是方块压缩
m_pApplyUnconsolidationIconButton->setIconSize(QSize(47, 42));
m_pApplyUnconsolidationIconButton->setFixedSize(49, 44);
m_pApplyUnconsolidationIconButton->setEnabled(false); // 默认禁用
pApplyUnconsolidationLayout->addWidget(m_pApplyUnconsolidationCheck);
pApplyUnconsolidationLayout->addWidget(m_pApplyUnconsolidationIconButton);
pApplyUnconsolidationLayout->addStretch(); // 添加拉伸器
pMainVLayout->addLayout(pApplyUnconsolidationLayout); // 添加到主垂直布局
m_pUseDFNCheck = new QCheckBox(tr("Use DFN"), this); // "使用 DFN"复选框
m_pUseDFNCheck->setChecked(false); // 默认未选中
pMainVLayout->addWidget(m_pUseDFNCheck); // 添加到主垂直布局
m_pAdvancedGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // 设置尺寸策略
}
void nmWxNumericalDesign::initTimeSteppingGroup()
{
m_pTimeSteppingGroup = new QGroupBox(tr("Time stepping"), this);
QGridLayout* pFieldLayout = new QGridLayout(m_pTimeSteppingGroup);
pFieldLayout->setContentsMargins(8, 15, 8, 10);
pFieldLayout->setHorizontalSpacing(6);
pFieldLayout->setVerticalSpacing(6);
pFieldLayout->setColumnStretch(0, 0);
pFieldLayout->setColumnStretch(1, 8);
pFieldLayout->setColumnStretch(2, 5);
pFieldLayout->setColumnMinimumWidth(
1, nmParameterField::editorMinimumWidth());
pFieldLayout->setColumnMinimumWidth(
2, nmParameterField::unitMinimumWidth());
// 第一步按稳定参数ID创建名称、数值和单位三列字段。
m_pTimeGrowthExponentField = new nmParameterField(
createTimeStepFieldInfo(s_pTimeGrowthExponentId, false),
this,
m_pTimeSteppingGroup);
m_pDtMinField = new nmParameterField(
createTimeStepFieldInfo(s_pDtMinId, true),
this,
m_pTimeSteppingGroup);
m_pDtMaxField = new nmParameterField(
createTimeStepFieldInfo(s_pDtMaxId, true),
this,
m_pTimeSteppingGroup);
// 第二步逐项接入NM参数定义和框架单位绑定器。
QString sError;
bool bInitialized =
m_pTimeGrowthExponentField->initialize(
pFieldLayout, 0, 0, sError) &&
m_pDtMinField->initialize(
pFieldLayout, 1, 0, sError) &&
m_pDtMaxField->initialize(
pFieldLayout, 2, 0, sError);
if(!bInitialized) {
m_pTimeSteppingGroup->setEnabled(false);
m_pTimeSteppingGroup->setToolTip(sError);
}
m_pTimeGrowthExponentField->setValueRightAligned(true);
m_pDtMinField->setValueRightAligned(true);
m_pDtMaxField->setValueRightAligned(true);
m_pTimeSteppingGroup->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Preferred);
}
void nmWxNumericalDesign::initNumericalSettingsGroup()
{
m_pNumericalSettingsGroup = new QGroupBox(tr("Numerical settings"), this); // 创建数值设置分组框
QVBoxLayout* pMainVLayout = new QVBoxLayout(m_pNumericalSettingsGroup); // 主垂直布局,用于放置子布局
// 第一行Use automatic settings 单选按钮
// 直接添加到主垂直布局
m_pUseAutomaticSettingsRadio = new QRadioButton(tr("Use automatic settings"), this); // "使用自动设置"单选按钮
m_pUseAutomaticSettingsRadio->setChecked(true); // 默认选中"使用自动设置"
pMainVLayout->addWidget(m_pUseAutomaticSettingsRadio);
// 第二行Use custom settings 单选按钮 + 齿轮图标
QHBoxLayout* pCustomSettingsHLayout = new QHBoxLayout(); // 为第二行创建一个水平布局
m_pUseCustomSettingsRadio = new QRadioButton(tr("Use custom settings"), this); // "使用自定义设置"单选按钮
m_pCustomSettingsIconButton = new QPushButton(this); // 自定义设置旁边的图标按钮
m_pCustomSettingsIconButton->setIcon(QIcon(m_sIconDir + "NmAnalDesign1.png")); // 占位符图标
m_pCustomSettingsIconButton->setIconSize(QSize(20, 20));
m_pCustomSettingsIconButton->setFixedSize(22, 22);
m_pCustomSettingsIconButton->setEnabled(false); // 默认禁用,当选择"使用自定义设置"时启用
// 这个值可以根据实际显示效果调整
pCustomSettingsHLayout->addWidget(m_pUseCustomSettingsRadio);
pCustomSettingsHLayout->addWidget(m_pCustomSettingsIconButton);
pCustomSettingsHLayout->addStretch(); // 添加拉伸器,确保按钮和图标靠左对齐
pMainVLayout->addLayout(pCustomSettingsHLayout); // 将第二行的水平布局添加到主垂直布局
m_pNumericalSettingsGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // 设置尺寸策略
}
void nmWxNumericalDesign::initPebiSolverSettingsGroup()
{
m_pPebiSolverSettingsGroup = new QGroupBox(tr("Solver settings"), this);
QGridLayout* pGridLayout = new QGridLayout(m_pPebiSolverSettingsGroup);
pGridLayout->setContentsMargins(8, 15, 8, 10);
pGridLayout->setHorizontalSpacing(6);
pGridLayout->setVerticalSpacing(6);
QLabel* pSolverTypeLabel = new QLabel(tr("Solver library"), this);
QLabel* pOmpThreadsLabel = new QLabel(tr("OpenMP threads"), this);
QLabel* pIluReuseStepsLabel = new QLabel(tr("ILU reuse steps"), this);
pSolverTypeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
pOmpThreadsLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
pIluReuseStepsLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
m_pPebiSolverTypeCombo = new QComboBox(this);
m_pPebiSolverTypeCombo->addItem(tr("CPU accelerated solver"),
nmDataAnalyzeManager::PebiSolverCpuAccelerated);
m_pPebiSolverTypeCombo->addItem(tr("Original solver"),
nmDataAnalyzeManager::PebiSolverOriginal);
m_pPebiOmpThreadsCombo = new QComboBox(this);
m_pPebiOmpThreadsCombo->addItem("1", 1);
m_pPebiOmpThreadsCombo->addItem("2", 2);
m_pPebiOmpThreadsCombo->addItem("4", 4);
m_pPebiOmpThreadsCombo->addItem("8", 8);
m_pPebiOmpThreadsCombo->addItem("16", 16);
m_pPebiIluReuseStepsSpin = new QSpinBox(this);
m_pPebiIluReuseStepsSpin->setRange(1, 100);
m_pPebiSolverTypeCombo->setFixedHeight(nmParameterField::editorHeight());
m_pPebiOmpThreadsCombo->setFixedHeight(nmParameterField::editorHeight());
m_pPebiIluReuseStepsSpin->setFixedHeight(nmParameterField::editorHeight());
m_pPebiSolverTypeCombo->setMinimumWidth(
nmParameterField::editorMinimumWidth());
m_pPebiOmpThreadsCombo->setMinimumWidth(
nmParameterField::editorMinimumWidth());
m_pPebiIluReuseStepsSpin->setMinimumWidth(
nmParameterField::editorMinimumWidth());
m_pPebiSolverTypeCombo->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Fixed);
m_pPebiOmpThreadsCombo->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Fixed);
m_pPebiIluReuseStepsSpin->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Fixed);
pGridLayout->addWidget(pSolverTypeLabel, 0, 0);
pGridLayout->addWidget(m_pPebiSolverTypeCombo, 0, 1);
pGridLayout->addWidget(pOmpThreadsLabel, 1, 0);
pGridLayout->addWidget(m_pPebiOmpThreadsCombo, 1, 1);
pGridLayout->addWidget(pIluReuseStepsLabel, 2, 0);
pGridLayout->addWidget(m_pPebiIluReuseStepsSpin, 2, 1);
pGridLayout->setColumnStretch(0, 0);
pGridLayout->setColumnStretch(1, 1);
m_pPebiSolverSettingsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
void nmWxNumericalDesign::updatePebiSolverSettingEnabled()
{
bool bAccelerationEnabled = m_pPebiSolverTypeCombo->itemData(
m_pPebiSolverTypeCombo->currentIndex()).toInt()
== nmDataAnalyzeManager::PebiSolverCpuAccelerated;
m_pPebiOmpThreadsCombo->setEnabled(bAccelerationEnabled);
m_pPebiIluReuseStepsSpin->setEnabled(bAccelerationEnabled);
}
void nmWxNumericalDesign::updatePebiSolverSettingsToData()
{
nmDataAnalyzeManager* pManager = m_pDataManager;
if(pManager == nullptr) {
return;
}
pManager->setPebiSolverType(m_pPebiSolverTypeCombo->itemData(
m_pPebiSolverTypeCombo->currentIndex()).toInt());
pManager->setPebiOmpThreads(m_pPebiOmpThreadsCombo->itemData(
m_pPebiOmpThreadsCombo->currentIndex()).toInt());
pManager->setPebiIluReuseSteps(m_pPebiIluReuseStepsSpin->value());
}
void nmWxNumericalDesign::initButton()
{
// 创建自动拟合按钮
m_pAutomaticfittingBtn = new QPushButton(tr("Automatic fitting"), this);
m_pAutomaticfittingBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// 创建预测按钮
m_pForecastBtn = new QPushButton(tr("Forecast"), this);
m_pForecastBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// 创建敏感性分析按钮
m_pSensitiveBtn = new QPushButton(tr("Sensitive"), this);
m_pSensitiveBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// 创建右下角按钮
m_pGenerateButton = new QPushButton(tr("Generate"), this); // "生成"按钮
m_pGenerateButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 固定尺寸
// 创建按钮布局
m_pGenerateLayout = new QHBoxLayout(); // 水平布局
m_pGenerateLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); // 左侧添加拉伸器
m_pGenerateLayout->addWidget(m_pAutomaticfittingBtn);
m_pGenerateLayout->addSpacing(10);
m_pGenerateLayout->addWidget(m_pForecastBtn);
m_pGenerateLayout->addSpacing(10);
m_pGenerateLayout->addWidget(m_pSensitiveBtn);
m_pGenerateLayout->addSpacing(10);
m_pGenerateLayout->addWidget(m_pGenerateButton);
}
void nmWxNumericalDesign::setupConnections()
{
// 连接生成按钮的点击信号到槽函数
connect(m_pGenerateButton, SIGNAL(clicked()), this, SLOT(onGenerateClicked()));
connect(m_pResultWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onResultWellChanged(int)));
connect(m_pCurrentModelCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentModelChanged(int)));
// 主选项组连接
// 连接齿轮图标按钮的槽函数
connect(m_pOptionsGearButton, SIGNAL(clicked()), this, SLOT(onOptionsIconClicked()));
connect(m_pAutomaticfittingBtn, SIGNAL(clicked()), this, SLOT(onAutomaticFittingClicked()));
connect(m_pForecastBtn, SIGNAL(clicked()), this, SLOT(onForecastClicked()));
connect(m_pSensitiveBtn, SIGNAL(clicked()), this, SLOT(onSensitiveClicked()));
// 更新齿轮图标按钮的启用状态
connect(m_pIncludeOtherWellsCheck, SIGNAL(toggled(bool)), m_pOptionsGearButton, SLOT(setEnabled(bool)));
connect(m_pIncludeOtherWellsCheck, SIGNAL(toggled(bool)),
this, SLOT(onIncludeOtherWellsToggled(bool)));
// Map井增删或产量、压力目标变化后立即更新多井选项是否可用。
if(m_pDataManager != nullptr) {
connect(m_pDataManager, SIGNAL(dataChanged()),
this, SLOT(onWellCollectionChanged()), Qt::UniqueConnection);
// createWell() 会在井对象登记时先发送 dataChanged(),此时 WellCode、
// 坐标和历史流量可能尚未填充。sigWellAdded 表示井已完整建立,必须再次
// 刷新 Include Other Wells才能正确识别刚加入 Map 的有产量井。
connect(m_pDataManager,
SIGNAL(sigWellAdded(QString,QString,QStringList)),
this, SLOT(onWellCollectionChanged()), Qt::UniqueConnection);
}
connect(m_pResetDiagnosticButton, SIGNAL(clicked()), this, SLOT(onResetFromDiagnosticClicked())); // "从诊断复位"按钮连接
connect(m_pResetAnalyticalButton, SIGNAL(clicked()), this, SLOT(onResetFromAnalyticalClicked())); // "从解析复位"按钮连接
// 输出组连接
// 新增连接m_pOutputResultFieldsCheck 控制其他相关组件的启用/禁用
connect(m_pOutputResultFieldsCheck, SIGNAL(toggled(bool)), this, SLOT(onOutputResultFieldsToggled(bool))); //
connect(m_pOutputAutomaticRadio, SIGNAL(toggled(bool)), this, SLOT(onOutputResultFieldsAutomaticToggled(bool))); // "自动"单选按钮连接
connect(m_pOutputManualRadio, SIGNAL(toggled(bool)), this, SLOT(onOutputResultFieldsManualToggled(bool))); // "手动"单选按钮连接
connect(m_pOutputManualIconButton, SIGNAL(clicked()), this, SLOT(onManualOutputSettingsClicked())); // "手动"图标按钮连接
// 高级组连接
// 在 setupConnections() 中连接 pTimeDependentIconButton 的信号槽,或者在这里连接
connect(m_pTimeDependentCheck, SIGNAL(toggled(bool)), m_pTimeDependentIconButton, SLOT(setEnabled(bool)));
connect(m_pTimeDependentCheck, SIGNAL(toggled(bool)), this, SLOT(onTimeDependentCheck(bool)));
// 如果这个图标按钮有自己的点击逻辑,需要连接到槽函数
connect(m_pTimeDependentIconButton, SIGNAL(clicked()), this, SLOT(onTimeIconClicked()));
// "重新定义 KrPc"图标按钮的启用状态取决于复选框
connect(m_pRedefineKrPcCheck, SIGNAL(toggled(bool)), m_pRedefineKrPcIconButton, SLOT(setEnabled(bool)));
connect(m_pRedefineKrPcIconButton, SIGNAL(clicked()), this, SLOT(onRedefineKrPcIconClicked())); // 图标按钮点击连接
// "应用未固结"图标按钮的启用状态取决于复选框
connect(m_pApplyUnconsolidationCheck, SIGNAL(toggled(bool)), m_pApplyUnconsolidationIconButton, SLOT(setEnabled(bool)));
connect(m_pApplyUnconsolidationIconButton, SIGNAL(clicked()), this, SLOT(onApplyUnconsolidationIconClicked())); // 图标按钮点击连接
// 时间步字段输入过程中只刷新校验,编辑完成后再整体提交。
QList<nmParameterField*> listTimeStepFields;
listTimeStepFields << m_pTimeGrowthExponentField
<< m_pDtMinField
<< m_pDtMaxField;
for(int nIndex = 0; nIndex < listTimeStepFields.size(); ++nIndex) {
QLineEdit* pLineEdit = listTimeStepFields[nIndex] != nullptr
? listTimeStepFields[nIndex]->valueEditor() : nullptr;
if(pLineEdit == nullptr) {
continue;
}
connect(pLineEdit, SIGNAL(textChanged(QString)),
this, SLOT(onTimeStepValueChanged(QString)));
connect(pLineEdit, SIGNAL(editingFinished()),
this, SLOT(onTimeStepEditingFinished()));
}
// 数值设置组连接
// "自定义设置"图标按钮的启用状态取决于"使用自定义设置"单选按钮
connect(m_pUseCustomSettingsRadio, SIGNAL(toggled(bool)), m_pCustomSettingsIconButton, SLOT(setEnabled(bool)));
connect(m_pCustomSettingsIconButton, SIGNAL(clicked()), this, SLOT(onCustomNumericalSettingsClicked())); // 图标按钮点击连接
// PEBI求解器设置组连接
connect(m_pPebiSolverTypeCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onPebiSolverTypeChanged(int)));
connect(m_pPebiOmpThreadsCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onPebiSolverSettingsChanged()));
connect(m_pPebiIluReuseStepsSpin, SIGNAL(valueChanged(int)),
this, SLOT(onPebiSolverSettingsChanged()));
}
void nmWxNumericalDesign::onPebiSolverTypeChanged(int index)
{
if(index < 0) {
return;
}
updatePebiSolverSettingEnabled();
updatePebiSolverSettingsToData();
}
void nmWxNumericalDesign::onPebiSolverSettingsChanged()
{
updatePebiSolverSettingsToData();
}
void nmWxNumericalDesign::onTimeStepValueChanged(const QString& sText)
{
Q_UNUSED(sText);
double dTimeGrowthExponent = 0.0;
double dDtMin = 0.0;
double dDtMax = 0.0;
QString sError;
collectTimeStepValues(dTimeGrowthExponent,
dDtMin,
dDtMax,
sError);
}
void nmWxNumericalDesign::onTimeStepEditingFinished()
{
QString sError;
if(!updateDataFromUi(sError)) {
focusFirstInvalidTimeStepField();
}
}
void nmWxNumericalDesign::onTimeDependentCheck(bool checked)
{
// 更新当前井的时间变表皮状态
nmDataWellBase* pCurrentWell = m_pDataManager != nullptr
? m_pDataManager->getCurWellData() : nullptr;
if(pCurrentWell) {
pCurrentWell->setTimeDependentSkin(checked);
}
}
// 切换查看井后同步当前井,并刷新结果曲线
void nmWxNumericalDesign::onResultWellChanged(int index)
{
if(m_bFillingResultWellCombo || index < 0 || m_pResultWellCombo == nullptr) {
return;
}
const QString sWellInstanceId =
m_pResultWellCombo->itemData(index).toString();
if(sWellInstanceId.isEmpty()) {
return;
}
nmDataAnalyzeManager* pManager = m_pDataManager;
if(pManager != nullptr) {
// 结果井切换只更新查看状态,不改变主分析井和参数编辑井。
pManager->setCurrentResultWellInstanceId(sWellInstanceId);
}
// 复用主窗口已有的井结果刷新逻辑
emit sigResultWellChanged(sWellInstanceId);
}
void nmWxNumericalDesign::onIncludeOtherWellsToggled(bool bChecked)
{
if(m_pDataManager == nullptr) {
return;
}
// 总开关只控制有效计算井集合;已经勾选的井保留,重新打开时无需再次选择。
m_pDataManager->setIncludeOtherWells(bChecked);
fillResultWellCombo();
}
void nmWxNumericalDesign::onWellCollectionChanged()
{
// 这里只刷新多井入口避免Map编辑时覆盖用户尚未提交的其他数值设置。
updateIncludeOtherWellsAvailability();
// 删除井不清除快照;重新填充后保留 UUID 选择并显示“已删除”状态。
fillResultWellCombo();
}
// 当前模型下拉框切换槽,将用户选择的模型类型同步到数据管理器
void nmWxNumericalDesign::onCurrentModelChanged(int index)
{
if(m_bFillingCurrentModelCombo || index < 0 || m_pCurrentModelCombo == nullptr) {
return;
}
NM_SOLVER_MODEL_TYPE eType = static_cast<NM_SOLVER_MODEL_TYPE>(
m_pCurrentModelCombo->itemData(index).toInt());
if(eType <= 0) {
return;
}
nmDataAnalyzeManager* pManager = m_pDataManager;
if(pManager != nullptr) {
pManager->setSolverModelType(eType);
}
}
void nmWxNumericalDesign::onGenerateClicked()
{
QString sError;
if(!updateDataFromUi(sError)) {
focusFirstInvalidTimeStepField();
QMessageBox::warning(this, tr("Invalid time step"), sError);
return;
}
emit sigGenerateClicked();
}
void nmWxNumericalDesign::onResetFromDiagnosticClicked()
{
nmDataAnalyzeManager* pManager = m_pDataManager;
if(!pManager) {
QMessageBox::warning(this, tr("Warning"), tr("No data manager available."));
return;
}
if(pManager->resetFromDiagnostic()) {
// 通知参数面板更新显示
nmWxParameterProperty::notifyUpdateTable();
} else {
QMessageBox::warning(this, tr("Warning"),
tr("Failed to reset from diagnostic. Please check if log-log data is available."));
}
}
void nmWxNumericalDesign::onResetFromAnalyticalClicked()
{
nmDataAnalyzeManager* pManager = m_pDataManager;
if(!pManager) {
QMessageBox::warning(this, tr("Warning"), tr("No data manager available."));
return;
}
if(pManager->resetFromAnalytical()) {
// 通知参数面板更新显示
nmWxParameterProperty::notifyUpdateTable();
} else {
QMessageBox::warning(this, tr("Warning"), tr("Failed to reset to analytical defaults."));
}
}
void nmWxNumericalDesign::onManualOutputSettingsClicked()
{
// 占位符:显示消息框
QMessageBox::information(this, tr("Output Settings"), tr("Manual output settings clicked."));
}
void nmWxNumericalDesign::onRedefineKrPcIconClicked()
{
// 占位符:显示消息框
QMessageBox::information(this, tr("Advanced Settings"), tr("Redefine KrPc settings clicked."));
}
void nmWxNumericalDesign::onApplyUnconsolidationIconClicked()
{
// 占位符:显示消息框
QMessageBox::information(this, tr("Advanced Settings"), tr("Apply unconsolidation settings clicked."));
}
void nmWxNumericalDesign::onCustomNumericalSettingsClicked()
{
// 占位符:显示消息框
QMessageBox::information(this, tr("Numerical Settings"), tr("Custom numerical settings clicked."));
}
// 针对高级组中的"时间相关"按钮的槽函数
void nmWxNumericalDesign::onTimeIconClicked()
{
nmWxTimeDependentSkin::showForCurrentWell();
}
void nmWxNumericalDesign::onOutputResultFieldsAutomaticToggled(bool checked)
{
// 当"自动"单选按钮被选中时,禁用手动输出设置按钮
if(checked) {
m_pOutputManualIconButton->setEnabled(false);
}
}
void nmWxNumericalDesign::onOutputResultFieldsManualToggled(bool checked)
{
// 当"手动"单选按钮被选中时,启用手动输出设置按钮
if(checked) {
m_pOutputManualIconButton->setEnabled(true);
}
}
void nmWxNumericalDesign::onOptionsIconClicked()
{
nmWxIncludeOtherWells dlg(m_pDataManager, this); // 创建"包含其他井"对话框实例
if(m_pDataManager == nullptr) {
return;
}
if(dlg.exec() == QDialog::Accepted) {
QVector<WellDataRow> vecWellRows = dlg.getWellData();
QVector<nmCalculationWellRef> vecIncludedWells;
bool bIncludedMembershipChanged = false;
bool bIncludedCurveSelectionChanged = false;
// 第一步:先把每一行的压力、公共流量记录、分相开关和流动段写回。
// 这些状态属于井本身;未勾选井也保留配置,方便下次直接启用。
for(int nRowIndex = 0; nRowIndex < vecWellRows.size(); ++nRowIndex) {
const WellDataRow& oWellRow = vecWellRows[nRowIndex];
if(oWellRow.m_bIsPrimary) {
continue;
}
nmDataWellBase* pWellData =
m_pDataManager->findWellByCode(oWellRow.m_sWellCode);
if(pWellData == nullptr) {
continue;
}
const bool bWasIncluded =
m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
oWellRow.m_sWellCode);
const bool bWillBeIncluded = oWellRow.m_bIsIncluded &&
oWellRow.canCalculate();
bIncludedMembershipChanged = bIncludedMembershipChanged ||
bWasIncluded != bWillBeIncluded;
// 必须在写回前冻结差异;写回后井对象已无法提供旧的记录和分相状态。
const bool bCurveSelectionChanged =
pWellData->getSelectedPressureGaugeCode() !=
oWellRow.m_sPressureGaugeCode ||
pWellData->getSelectedFlowGaugeCode() !=
oWellRow.m_sFlowGaugeCode ||
pWellData->getUseOilRate() != oWellRow.m_bUseOilRate ||
pWellData->getUseGasRate() != oWellRow.m_bUseGasRate ||
pWellData->getUseWaterRate() != oWellRow.m_bUseWaterRate ||
pWellData->getIndexF() != oWellRow.m_nFlowSegmentIndex;
// 先让真实井保存原当前记录,再用对话框工作副本覆盖完整记录级索引。
pWellData->selectPressureGaugeCode(
oWellRow.m_sPressureGaugeCode);
if(pWellData->getSelectedFlowGaugeCode() !=
oWellRow.m_sFlowGaugeCode) {
pWellData->switchSelectedFlowGaugeCode(
oWellRow.m_sFlowGaugeCode);
}
pWellData->setFlowRecords(oWellRow.m_vecFlowRecords);
pWellData->setUseOilRate(oWellRow.m_bUseOilRate);
pWellData->setUseGasRate(oWellRow.m_bUseGasRate);
pWellData->setUseWaterRate(oWellRow.m_bUseWaterRate);
pWellData->setIndexF(oWellRow.m_nFlowSegmentIndex);
if(bCurveSelectionChanged) {
m_pDataManager->updateWellHistoryData(pWellData);
if(bWasIncluded || bWillBeIncluded) {
bIncludedCurveSelectionChanged = true;
}
}
if(bWillBeIncluded) {
vecIncludedWells.append(
nmCalculationWellRef(oWellRow.m_sWellCode,
NM_CaseWell_RateControlled));
}
}
// 第二步:井集合改变会使网格和结果一起失效;仅更换曲线或分相时
// 保留现有网格,只使旧求解结果失效。
m_pDataManager->setIncludedCalculationWells(vecIncludedWells);
m_pDataManager->setIncludeOtherWells(
m_pIncludeOtherWellsCheck->isChecked());
if(bIncludedCurveSelectionChanged &&
!bIncludedMembershipChanged) {
m_pDataManager->invalidatePebiResults();
}
nmWxParameterProperty::notifyUpdateTable();
fillResultWellCombo();
}
}
void nmWxNumericalDesign::onOutputResultFieldsToggled(bool checked)
{
// 根据主复选框的状态启用或禁用相关组件
m_pOutputAutomaticRadio->setEnabled(checked); //
m_pOutputManualRadio->setEnabled(checked); //
// 特别处理 m_pOutputManualIconButton
// 它只有在 m_pOutputResultFieldsCheck 勾选 且 m_pOutputManualRadio 选中时才启用
if(checked && m_pOutputManualRadio->isChecked()) { //
m_pOutputManualIconButton->setEnabled(true); //
} else {
m_pOutputManualIconButton->setEnabled(false); //
}
}
void nmWxNumericalDesign::updateUiFromData()
{
nmDataAnalyzeManager* pManager = m_pDataManager;
if(pManager == nullptr) {
return;
}
// 第一步:刷新与当前成果、模型和井相关的选择控件。
updateIncludeOtherWellsAvailability();
fillResultWellCombo();
updateCurrentModelDisplay();
updateTimeDependentSkinDisplay();
// 第二步:业务层始终保存小时基准值,单位绑定器负责转换显示。
nmDataTimeStepSetting* pTimeStepSetting = pManager->getTimeStep();
if(pTimeStepSetting != nullptr) {
m_pTimeGrowthExponentField->setBaseValue(
pTimeStepSetting->getTimeGrowthExponent().getValue());
m_pDtMinField->setBaseValue(
pTimeStepSetting->getMinDeltaTAttribute().getValue());
m_pDtMaxField->setBaseValue(
pTimeStepSetting->getMaxDeltaTAttribute().getValue());
}
// 第三步回填PEBI设置时屏蔽即时提交信号。
m_pPebiSolverTypeCombo->blockSignals(true);
m_pPebiOmpThreadsCombo->blockSignals(true);
m_pPebiIluReuseStepsSpin->blockSignals(true);
int nSolverTypeIndex = m_pPebiSolverTypeCombo->findData(pManager->getPebiSolverType());
if(nSolverTypeIndex < 0) {
nSolverTypeIndex = m_pPebiSolverTypeCombo->findData(
nmDataAnalyzeManager::PebiSolverCpuAccelerated);
}
m_pPebiSolverTypeCombo->setCurrentIndex(nSolverTypeIndex);
int nOmpThreadsIndex = m_pPebiOmpThreadsCombo->findData(pManager->getPebiOmpThreads());
if(nOmpThreadsIndex < 0) {
nOmpThreadsIndex = m_pPebiOmpThreadsCombo->findData(4);
}
m_pPebiOmpThreadsCombo->setCurrentIndex(nOmpThreadsIndex);
m_pPebiIluReuseStepsSpin->setValue(pManager->getPebiIluReuseSteps());
m_pPebiSolverTypeCombo->blockSignals(false);
m_pPebiOmpThreadsCombo->blockSignals(false);
m_pPebiIluReuseStepsSpin->blockSignals(false);
updatePebiSolverSettingEnabled();
}
void nmWxNumericalDesign::updateIncludeOtherWellsAvailability()
{
if(m_pDataManager == nullptr ||
m_pIncludeOtherWellsCheck == nullptr ||
m_pOptionsGearButton == nullptr) {
return;
}
// 第一步:只有参考井以外、已经加入 Map 且有有效产量制度的井才是
// Include Other Wells 候选。无产量观察井不控制这个开关。
bool bHasEligibleOtherWell = false;
const QString sPrimaryWellCode = m_pDataManager->getPrimaryWellCode();
QVector<nmDataWellBase*> vecMapWells = m_pDataManager->getWellDataList();
for(int nIndex = 0; nIndex < vecMapWells.size(); ++nIndex) {
nmDataWellBase* pWellData = vecMapWells[nIndex];
if(!isSupportedAdditionalWell(pWellData) ||
pWellData->getWellCode() == sPrimaryWellCode) {
continue;
}
const bool bHasRateTarget = hasAvailableRateTarget(pWellData);
if(bHasRateTarget) {
bHasEligibleOtherWell = true;
break;
}
}
// 第二步:没有有产量候选井时关闭该开关;自动观察井仍会继续参与网格和计算。
if(!bHasEligibleOtherWell && m_pDataManager->getIncludeOtherWells()) {
m_pDataManager->setIncludeOtherWells(false);
}
// 第三步:屏蔽界面信号后同步状态;齿轮只在总开关已打开时可用。
const bool bIncludeChecked =
bHasEligibleOtherWell && m_pDataManager->getIncludeOtherWells();
const bool bSignalsWereBlocked =
m_pIncludeOtherWellsCheck->blockSignals(true);
m_pIncludeOtherWellsCheck->setEnabled(bHasEligibleOtherWell);
m_pIncludeOtherWellsCheck->setChecked(bIncludeChecked);
m_pIncludeOtherWellsCheck->blockSignals(bSignalsWereBlocked);
m_pOptionsGearButton->setEnabled(
bHasEligibleOtherWell && bIncludeChecked);
}
bool nmWxNumericalDesign::updateDataFromUi(QString& sError)
{
nmDataAnalyzeManager* pManager = m_pDataManager;
if(pManager == nullptr) {
sError = tr("No data manager available.");
return false;
}
nmDataTimeStepSetting* pTimeStepSetting = pManager->getTimeStep();
if(pTimeStepSetting == nullptr) {
sError = tr("Time step settings are unavailable.");
return false;
}
// 第一步:先读取全部基准值,任何错误都不能产生部分写入。
double dTimeGrowthExponent = 0.0;
double dDtMin = 0.0;
double dDtMax = 0.0;
if(!collectTimeStepValues(dTimeGrowthExponent,
dDtMin,
dDtMax,
sError)) {
return false;
}
// 第二步:单位绑定器返回小时基准值,数据单位及持久化结构保持不变。
const double dOldTimeGrowthExponent =
pTimeStepSetting->getTimeGrowthExponent().getValue().toDouble();
const double dOldDtMin =
pTimeStepSetting->getMinDeltaTAttribute().getValue().toDouble();
const double dOldDtMax =
pTimeStepSetting->getMaxDeltaTAttribute().getValue().toDouble();
pTimeStepSetting->getTimeGrowthExponent().setValue(
dTimeGrowthExponent);
pTimeStepSetting->getMinDeltaTAttribute().setValue(dDtMin);
pTimeStepSetting->getMaxDeltaTAttribute().setValue(dDtMax);
// 第三步:时间离散改变时旧结果失效,但二维 PEBI 网格仍可直接复用。
if(!qFuzzyCompare(dOldTimeGrowthExponent, dTimeGrowthExponent) ||
!qFuzzyCompare(dOldDtMin, dDtMin) ||
!qFuzzyCompare(dOldDtMax, dDtMax)) {
pManager->invalidatePebiResults();
}
updatePebiSolverSettingsToData();
sError.clear();
return true;
}
bool nmWxNumericalDesign::collectTimeStepValues(
double& dTimeGrowthExponent,
double& dDtMin,
double& dDtMax,
QString& sError)
{
if(m_pTimeGrowthExponentField == nullptr ||
m_pDtMinField == nullptr ||
m_pDtMaxField == nullptr) {
sError = tr("Time step fields are unavailable.");
return false;
}
// 第一步:清除上一次跨字段校验错误,再执行各字段格式和范围校验。
m_pTimeGrowthExponentField->setExternalValidationError(QString());
m_pDtMinField->setExternalValidationError(QString());
m_pDtMaxField->setExternalValidationError(QString());
bool bGrowthValid = false;
bool bDtMinValid = false;
bool bDtMaxValid = false;
QVariant oGrowthValue =
m_pTimeGrowthExponentField->baseValue(bGrowthValid);
QVariant oDtMinValue = m_pDtMinField->baseValue(bDtMinValid);
QVariant oDtMaxValue = m_pDtMaxField->baseValue(bDtMaxValid);
if(!bGrowthValid || !bDtMinValid || !bDtMaxValid ||
!oGrowthValue.isValid() ||
!oDtMinValue.isValid() ||
!oDtMaxValue.isValid()) {
sError = tr("Please correct the invalid time step values.");
return false;
}
dTimeGrowthExponent = oGrowthValue.toDouble();
dDtMin = oDtMinValue.toDouble();
dDtMax = oDtMaxValue.toDouble();
// 第二步:单字段合法后再校验最小、最大时间步的业务关系。
if(dDtMin >= dDtMax) {
sError = tr("Dt min must be less than Dt max.");
m_pDtMaxField->setExternalValidationError(sError);
return false;
}
sError.clear();
return true;
}
void nmWxNumericalDesign::focusFirstInvalidTimeStepField()
{
QList<nmParameterField*> listFields;
listFields << m_pTimeGrowthExponentField
<< m_pDtMinField
<< m_pDtMaxField;
for(int nIndex = 0; nIndex < listFields.size(); ++nIndex) {
nmParameterField* pField = listFields[nIndex];
if(pField != nullptr && !pField->validate() &&
pField->valueEditor() != nullptr) {
pField->valueEditor()->setFocus();
pField->valueEditor()->selectAll();
return;
}
}
}
void nmWxNumericalDesign::updateTimeDependentSkinDisplay()
{
if(m_pTimeDependentCheck == nullptr ||
m_pTimeDependentIconButton == nullptr) {
return;
}
nmDataWellBase* pCurrentWell = m_pDataManager != nullptr
? m_pDataManager->getCurWellData() : nullptr;
bool bTimeDependent = pCurrentWell != nullptr &&
pCurrentWell->isTimeDependentSkin();
bool bSignalsWereBlocked = m_pTimeDependentCheck->blockSignals(true);
m_pTimeDependentCheck->setChecked(bTimeDependent);
m_pTimeDependentCheck->setEnabled(pCurrentWell != nullptr);
m_pTimeDependentCheck->blockSignals(bSignalsWereBlocked);
m_pTimeDependentIconButton->setEnabled(bTimeDependent);
}
void nmWxNumericalDesign::onAutomaticFittingClicked()
{
nmWxAutomaticFitting* dlg = new nmWxAutomaticFitting;
dlg->show();
}
void nmWxNumericalDesign::onForecastClicked()
{
nmWxForecast* dlg = new nmWxForecast();
dlg->show();
}
void nmWxNumericalDesign::onSensitiveClicked()
{
nmWxSensitive* dlg = new nmWxSensitive();
dlg->show();
}
// 实现通知方法
void nmWxNumericalDesign::notifyTimeDependentSkinChanged(const QString& wellName, bool checked)
{
// 检查当前是否有活动实例
if(!s_pCurrentInstance) {
return;
}
// 使用活动Dock所属的数据管理器避免多成果窗口间读取到其他成果的数据。
nmDataAnalyzeManager* pDataManager = s_pCurrentInstance->m_pDataManager;
nmDataWellBase* pCurrentWell = pDataManager != nullptr
? pDataManager->getCurWellData() : nullptr;
if(!pCurrentWell) {
return;
}
// 检查是否是同一口井
if(pCurrentWell->getWellName() == wellName) {
// 阻塞信号,避免循环触发
s_pCurrentInstance->m_pTimeDependentCheck->blockSignals(true);
// 更新复选框状态
s_pCurrentInstance->m_pTimeDependentCheck->setChecked(checked);
// 恢复信号
s_pCurrentInstance->m_pTimeDependentCheck->blockSignals(false);
// 同时更新图标按钮的启用状态
s_pCurrentInstance->m_pTimeDependentIconButton->setEnabled(checked);
}
}