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

1431 lines
56 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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 "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;
}
}
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_Water_TwoPhase:
return nmWxNumericalDesign::tr("Oil-water two-phase");
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& sWellCode)
{
// 静态活动面板可能已经切换到另一个成果,只刷新所属数据管理器一致的实例。
if(s_pCurrentInstance == nullptr ||
s_pCurrentInstance->m_pDataManager != pDataManager) {
return;
}
s_pCurrentInstance->fillResultWellCombo(sWellCode);
}
// 初始化模型与结果切换控件
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();
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(true);
m_bFillingCurrentModelCombo = false;
}
// 按参与计算井刷新下拉框
void nmWxNumericalDesign::fillResultWellCombo(const QString& sSelectedWellCode)
{
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;
}
QVector<QString> vecResultWellCodes;
QVector<nmSolverWellRef> vecSolverOrder =
pManager->hasPebiResults()
? pManager->getResultSolverWellOrder()
: pManager->getSolverWellOrder();
// 第一步:有结果时优先按最后结果顺序,否则按当前网格顺序收集结果井。
// 无流量观察井仍在求解器顺序中,但不能出现在本下拉框。
for(int nOrderIndex = 0; nOrderIndex < vecSolverOrder.size(); ++nOrderIndex) {
const nmSolverWellRef& oWellRef = vecSolverOrder[nOrderIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well &&
pManager->isWellAvailableForResult(oWellRef.m_sWellCode) &&
!vecResultWellCodes.contains(oWellRef.m_sWellCode)) {
vecResultWellCodes.append(oWellRef.m_sWellCode);
}
}
// 第二步:尚未生成网格时从当前有效计算井中执行同一结果资格过滤。
if(vecResultWellCodes.isEmpty()) {
QVector<nmCalculationWellRef> vecEffectiveWells =
pManager->getEffectiveCalculationWells();
for(int nWellIndex = 0; nWellIndex < vecEffectiveWells.size(); ++nWellIndex) {
const QString sWellCode = vecEffectiveWells[nWellIndex].m_sWellCode;
if(pManager->isWellAvailableForResult(sWellCode)) {
vecResultWellCodes.append(sWellCode);
}
}
}
for(int nWellIndex = 0; nWellIndex < vecResultWellCodes.size(); ++nWellIndex) {
nmDataWellBase* pWellData =
pManager->findWellByCode(vecResultWellCodes[nWellIndex]);
if(pWellData != nullptr) {
m_pResultWellCombo->addItem(pWellData->getWellName(),
pWellData->getWellCode());
}
}
QString sCurrentWellCode = sSelectedWellCode;
if(sCurrentWellCode.isEmpty()) {
sCurrentWellCode = pManager->getCurrentResultWellCode();
}
if(sCurrentWellCode.isEmpty()) {
sCurrentWellCode = pManager->getPrimaryWellCode();
}
int nIndex = m_pResultWellCombo->findData(sCurrentWellCode);
if(nIndex < 0 && m_pResultWellCombo->count() > 0) {
nIndex = 0;
}
if(nIndex >= 0) {
m_pResultWellCombo->setCurrentIndex(nIndex);
pManager->setCurrentResultWellCode(
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;
}
QString sWellCode = m_pResultWellCombo->itemData(index).toString();
if(sWellCode.isEmpty()) {
return;
}
nmDataAnalyzeManager* pManager = m_pDataManager;
if(pManager != nullptr) {
// 结果井切换只更新查看状态,不改变主分析井和参数编辑井。
pManager->setCurrentResultWellCode(sWellCode);
}
// 复用主窗口已有的井结果刷新逻辑
emit sigResultWellChanged(sWellCode);
}
void nmWxNumericalDesign::onIncludeOtherWellsToggled(bool bChecked)
{
if(m_pDataManager == nullptr) {
return;
}
// 总开关只控制有效计算井集合;已经勾选的井保留,重新打开时无需再次选择。
m_pDataManager->setIncludeOtherWells(bChecked);
fillResultWellCombo(m_pDataManager->getPrimaryWellCode());
}
void nmWxNumericalDesign::onWellCollectionChanged()
{
// 这里只刷新多井入口避免Map编辑时覆盖用户尚未提交的其他数值设置。
updateIncludeOtherWellsAvailability();
}
// 当前模型下拉框切换槽,将用户选择的模型类型同步到数据管理器
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;
// 第一步:这里只收集 Map 中被勾选的有产量干扰井。主井由分析方案
// 单独保存;无产量观察井由数据管理层自动加入,与本对话框无关。
for(int nRowIndex = 0; nRowIndex < vecWellRows.size(); ++nRowIndex) {
const WellDataRow& oWellRow = vecWellRows[nRowIndex];
if(oWellRow.m_bIsPrimary ||
!oWellRow.m_bIsIncluded ||
!oWellRow.canCalculate()) {
continue;
}
nmDataWellBase* pWellData =
m_pDataManager->findWellByCode(oWellRow.m_sWellCode);
if(pWellData == nullptr) {
// Map 中已经移除的井不能被 Include 对话框重新创建。
continue;
}
if(pWellData->getFlowPoints().size() < 2) {
// 对话框打开后若产量数据发生变化,提交时仍要阻止它混入包含列表。
continue;
}
vecIncludedWells.append(
nmCalculationWellRef(oWellRow.m_sWellCode,
NM_CaseWell_RateControlled));
// 第二步:为被包含的有产量井同步历史压力、双对数和半对数数据。
QVector<QVector<double> > vecHistoryPressure;
QVector<QVector<double> > vecHistoryLogLog;
QVector<QVector<double> > vecHistorySemiLog;
m_pDataManager->calculationLogData(pWellData,
vecHistoryPressure,
vecHistoryLogLog,
vecHistorySemiLog);
pWellData->setHistoryPressure(vecHistoryPressure);
pWellData->setHistoryLogLog(vecHistoryLogLog);
pWellData->setHistorySemiLog(vecHistorySemiLog);
}
// 第三步:一次性提交选择,内部会使旧网格和旧结果失效。
m_pDataManager->setIncludedCalculationWells(vecIncludedWells);
m_pDataManager->setIncludeOtherWells(
m_pIncludeOtherWellsCheck->isChecked());
nmWxParameterProperty::notifyUpdateTable();
fillResultWellCombo(m_pDataManager->getPrimaryWellCode());
}
}
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 = pWellData->getFlowPoints().size() >= 2;
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);
}
}