From 2be80141cd0356f8c345f9920b31838257bd6002 Mon Sep 17 00:00:00 2001
From: lh <2334563547@qq.com>
Date: Mon, 7 Sep 2026 14:26:44 +0800
Subject: [PATCH] =?UTF-8?q?feat(nmNum):=20=E6=94=AF=E6=8C=81=E5=8C=85?=
=?UTF-8?q?=E5=90=AB=E4=BA=95=E9=85=8D=E7=BD=AE=E5=8E=8B=E5=8A=9B=E8=AE=B0?=
=?UTF-8?q?=E5=BD=95=E4=B8=8E=E5=88=86=E7=9B=B8=E4=BA=A7=E9=87=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在包含井对话框中增加井别、压力、分相产量和流动段选择
- 使用 Gauge Code 关联并保存各井压力流量记录
- 支持独立启用油、气、水相产量并同步求解输入
- 完善记录切换、结果失效、中文翻译及界面提示
---
Bin/Config/Lang/cn/nmNum_cn.ts | 25 +-
.../nmCalculation/nmCalculationPebiGrid.h | 2 +
Include/nmNum/nmData/nmDataWellBase.h | 23 +-
.../nmNum/nmSubWxs/nmWellPressureFlowPage.h | 10 +-
.../nmNum/nmSubWxs/nmWxIncludeOtherWells.h | 71 +-
.../nmCalculationDllPebiSolverTask.cpp | 12 +-
.../nmCalculation/nmCalculationPebiGrid.cpp | 53 +-
Src/nmNum/nmData/nmDataAnalyzeManager.cpp | 13 +-
Src/nmNum/nmData/nmDataWellBase.cpp | 158 +++-
Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp | 86 +-
Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp | 762 ++++++++++++++----
Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp | 108 ++-
12 files changed, 1010 insertions(+), 313 deletions(-)
diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index 91a9ef4..da3d8c2 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -5189,26 +5189,44 @@ Please check your input coordinates.
Name
井名
+
+ Well category
+ 井别
+
Included
是否包含
Oil production
- 产油量
+ 油产量
Gas production
- 产气量
+ 气产量
Water production
- 产水量
+ 水产量
Pressure
压力
+ Flow segment流动段
+ Oil well油井
+ Gas well气井
+ Water well水井
+ Unknown未知
+ None无
+ (unnamed)(未命名)
+ (Empty)(空)
+ (Corrupt)(损坏)
+ (Invalid)(无效)
+ (No data)(无数据)
+ Segment %1第 %1 段
+ No eligible wells没有可配置的其他井
+ Select at least one production record first.请先选择至少一个产量相。
OK
确定
@@ -7192,6 +7210,7 @@ Average pressure in contour: %2 MPa
Pressure Record:压力记录:
Flow Record:流量记录:
Display Phase:显示相:
+ This page only displays the selected record.此页面仅展示当前选中的记录。
(Invalid) (无效)
(Empty) (空)
(Corrupt) (损坏)
diff --git a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h
index 4f2b7a2..88939f8 100644
--- a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h
+++ b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h
@@ -28,6 +28,7 @@ struct nmPebiWellInputSnapshot
{
nmPebiWellInputSnapshot()
: m_eWellCategory(NM_WellCategory_Unknown),
+ m_eFlowSchedulePhase(PHASE_UNKNOWN),
m_nFlowSectionIndex(1),
m_dRadius(0.0),
m_dWellboreStorage(0.0),
@@ -49,6 +50,7 @@ struct nmPebiWellInputSnapshot
QVector m_vecGasRates; ///< 与公共流动段逐项对应的气相流量。
QVector m_vecWaterRates; ///< 与公共流动段逐项对应的水相流量。
NM_WELL_CATEGORY m_eWellCategory; ///< 求解时冻结的油井、气井或水井井别。
+ NM_PHASE_TYPE m_eFlowSchedulePhase; ///< 旧单相后处理使用的当前制度参考相。
/** @brief 求解时冻结的历史压力曲线。 */
QVector > m_vecHistoryPressure;
/** @brief 求解时冻结的历史双对数曲线。 */
diff --git a/Include/nmNum/nmData/nmDataWellBase.h b/Include/nmNum/nmData/nmDataWellBase.h
index 797b98e..88d3e74 100644
--- a/Include/nmNum/nmData/nmDataWellBase.h
+++ b/Include/nmNum/nmData/nmDataWellBase.h
@@ -153,9 +153,13 @@ public:
bool hasFlowPhaseData(NM_PHASE_TYPE eFlowPhase) const;
/** @brief 油、气、水中是否至少有一相包含真实流动段。 */
bool hasAnyFlowData() const;
- /** @brief 返回井别对应相去除可选 (0,0) 占位点后的真实流动段。 */
+ /** @brief 全部框架记录中是否存在至少一个可用的非零产量相。 */
+ bool hasAvailableFlowRate() const;
+ /** @brief 返回当前启用相的公共流量制度;优先取井别对应相。 */
QVector getFlowSegmentPoints() const;
- /** @brief 返回井别对应相的真实流动段数量。 */
+ /** @brief 返回当前公共流量制度采用的参考相;没有启用相时返回 Unknown。 */
+ NM_PHASE_TYPE getFlowSchedulePhase() const;
+ /** @brief 返回当前启用相共用的真实流动段数量。 */
int getFlowSegmentCount() const;
/** @brief 校验当前流动段索引是否位于一基的真实流动段范围内。 */
bool hasValidFlowSectionIndex() const;
@@ -183,6 +187,18 @@ public:
QVector getPressureRecords() const;
/** @brief 返回全部流量记录(包含损坏/空/身份无效记录)。 */
QVector getFlowRecords() const;
+ /** @brief 返回指定相是否作为当前流量制度的一部分传给求解器。 */
+ bool isFlowPhaseUsed(NM_PHASE_TYPE eFlowPhase) const;
+ /** @brief 设置指定相是否参与当前流量制度;井别不会随此状态改变。 */
+ void setFlowPhaseUsed(NM_PHASE_TYPE eFlowPhase, bool bUsed);
+ // 以下显式接口供现有 UI 和快照组装代码使用,语义与上面的分相接口一致。
+ // 三个开关只控制求解器输入,不修改框架原始记录,也不改变工程井井别。
+ bool getUseOilRate() const;
+ bool getUseGasRate() const;
+ bool getUseWaterRate() const;
+ void setUseOilRate(bool bUsed);
+ void setUseGasRate(bool bUsed);
+ void setUseWaterRate(bool bUsed);
int getIndexF() const;
void setIndexF(const int newIndex);
@@ -319,6 +335,9 @@ protected:
QVector m_vecFlowRecords; // 全部流量测量记录(可能包含损坏/空/身份无效记录)
QString m_sSelectedPressureGaugeCode; // 当前选中的压力记录 Gauge Code,为空表示未选中
QString m_sSelectedFlowGaugeCode; // 当前选中的流量记录 Gauge Code,为空表示未选中
+ bool m_bUseOilRate; // 当前流量制度是否传入油相产量
+ bool m_bUseGasRate; // 当前流量制度是否传入气相产量
+ bool m_bUseWaterRate; // 当前流量制度是否传入水相产量
int m_nIndexF; // 当前流量段的索引
diff --git a/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h b/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
index 6bb9304..65097ad 100644
--- a/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
+++ b/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
@@ -59,12 +59,6 @@ private slots:
/** @brief 将流量曲线点击时间映射为表格行选择。 */
void slotFlowChartPointClicked(double dTimePoint);
- /** @brief 切换压力记录下拉框,按新选中的 Gauge Code 重新读取数据。 */
- void slotPressureGaugeChanged(int nIndex);
-
- /** @brief 切换流量记录下拉框,按新选中的 Gauge Code 恢复流动段索引并重新读取数据。 */
- void slotFlowGaugeChanged(int nIndex);
-
/** @brief 切换显示相下拉框,仅重新按新相取点刷新视图,不改变井数据。 */
void slotDisplayPhaseChanged(int nIndex);
@@ -168,8 +162,8 @@ private:
int m_nSelectedFlowSegment;
QWidget* m_pRecordSelectionBar; ///< 顶部压力/流量记录与显示相下拉框所在行容器。
- QComboBox* m_pPressureGaugeCombo; ///< 压力记录下拉框,itemData 存 Gauge Code。
- QComboBox* m_pFlowGaugeCombo; ///< 流量记录下拉框,itemData 存 Gauge Code。
+ QComboBox* m_pPressureGaugeCombo; ///< 当前压力记录只读展示,itemData 存 Gauge Code。
+ QComboBox* m_pFlowGaugeCombo; ///< 当前流量记录只读展示,itemData 存 Gauge Code。
QComboBox* m_pDisplayPhaseCombo; ///< 显示相下拉框,itemData 存 NM_PHASE_TYPE。
QWidget* m_pDisplayPhaseContainer; ///< 承载显示相下拉框与标签,整体隐藏用。
QPushButton* m_pCheckDataButton; ///< 临时“检查数据”诊断按钮,只读展示用。
diff --git a/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h b/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h
index c064b08..fde7fe2 100644
--- a/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h
+++ b/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h
@@ -4,8 +4,10 @@
#include "nmSubWxs_global.h"
#include "iDlgBase.h"
#include "nmDataNumericalAnalysisCase.h"
+#include "nmDataGaugeRecord.h"
#include
#include
+#include
#include
#include
#include
@@ -25,10 +27,14 @@ struct WellDataRow {
bool m_bCanCalculate; ///< 当前井型和几何参数是否可进入 PEBI 模型。
NM_WELL_CATEGORY m_eWellCategory; ///< 工程井井别,用于单相记录归相及求解模型匹配。
NM_CASE_WELL_MODE m_eMode; ///< 本列表固定保存有产量主动井角色。
- QString m_sOilProd; ///< 油产量列的摘要文本。
- QString m_sGasProd; ///< 气产量列的摘要文本。
- QString m_sWaterProd; ///< 水产量列的摘要文本。
- QString m_sPressure; ///< 压力历史列的摘要文本。
+ QString m_sPressureGaugeCode; ///< 本次选择的压力记录,为空表示“无”。
+ QString m_sFlowGaugeCode; ///< 三个产量列共同使用的流量记录。
+ bool m_bUseOilRate; ///< 是否使用公共流量记录中的油相产量。
+ bool m_bUseGasRate; ///< 是否使用公共流量记录中的气相产量。
+ bool m_bUseWaterRate; ///< 是否使用公共流量记录中的水相产量。
+ int m_nFlowSegmentIndex; ///< 当前流量记录的一基流动段索引。
+ QVector m_vecPressureRecords; ///< 供压力列建立下拉项。
+ QVector m_vecFlowRecords; ///< 供三个产量列建立下拉项。
/** @brief 初始化未勾选、不可计算的主动井候选行。 */
WellDataRow()
@@ -36,13 +42,20 @@ struct WellDataRow {
m_bIsPrimary(false),
m_bCanCalculate(false),
m_eWellCategory(NM_WellCategory_Unknown),
- m_eMode(NM_CaseWell_RateControlled)
+ m_eMode(NM_CaseWell_RateControlled),
+ m_bUseOilRate(false),
+ m_bUseGasRate(false),
+ m_bUseWaterRate(false),
+ m_nFlowSegmentIndex(0)
{
}
- /** @brief WellCode 有效且井型/几何可计算时允许用户勾选。 */
+ /** @brief 至少选择一个产量相时,该井才可作为主动井提交。 */
bool canCalculate() const {
- return m_bCanCalculate && !m_sWellCode.isEmpty();
+ return m_bCanCalculate && !m_sWellCode.isEmpty() &&
+ !m_sFlowGaugeCode.isEmpty() &&
+ (m_bUseOilRate || m_bUseGasRate || m_bUseWaterRate) &&
+ m_nFlowSegmentIndex > 0;
}
};
@@ -72,15 +85,31 @@ private:
/** @brief 将一条候选井数据写入指定表格行。 */
void addWellDataToTable(const WellDataRow& data, int row);
+ /** @brief 根据一行的公共流量记录重新建立三个分相下拉框和流动段。 */
+ void refreshRowEditors(int row);
+ /** @brief 校正一行的分相状态及一基流动段索引。 */
+ void normalizeFlowSelection(WellDataRow& data) const;
+ /** @brief 更新包含复选框;没有有效分相选择时自动取消包含。 */
+ void updateIncludeCheckBox(int row);
+ /** @brief 填充一列分相流量记录选项。 */
+ void populateRateCombo(QComboBox* pComboBox,
+ const WellDataRow& data, NM_PHASE_TYPE ePhase) const;
+ /** @brief 填充压力记录选项。 */
+ void populatePressureCombo(QComboBox* pComboBox,
+ const WellDataRow& data) const;
+ /** @brief 填充当前公共流量记录对应的流动段选项。 */
+ void populateFlowSegmentCombo(QComboBox* pComboBox,
+ const WellDataRow& data) const;
- // 更新复选框状态
- //void updateCheckBoxState(int row);
-
- private slots:
- /** @brief 响应整行点击,并切换该行包含状态。 */
- void onItemClicked(QTableWidgetItem* item);
- /** @brief 把复选框状态同步回对应的 WellDataRow。 */
- void onCheckBoxStateChanged(int state);
+private slots:
+ /** @brief 把复选框状态同步回对应的 WellDataRow。 */
+ void onCheckBoxStateChanged(int state);
+ /** @brief 切换某一相使用的流量记录;其他已选相同步到同一记录。 */
+ void onRateSourceChanged(int nIndex);
+ /** @brief 切换该井使用的压力记录。 */
+ void onPressureSourceChanged(int nIndex);
+ /** @brief 切换该井的一基流动段索引。 */
+ void onFlowSegmentChanged(int nIndex);
private:
/** @brief 当前对话框所属的数据管理器,不拥有对象。 */
@@ -92,6 +121,18 @@ private:
QVBoxLayout* m_pMainLayout; ///< 对话框主布局。
QVector m_vecWellData; ///< 与表格行一一对应的候选井数据。
+
+ enum TableColumn {
+ ColumnName = 0,
+ ColumnWellCategory,
+ ColumnIncluded,
+ ColumnOilRate,
+ ColumnGasRate,
+ ColumnWaterRate,
+ ColumnPressure,
+ ColumnFlowSegment,
+ ColumnCount
+ };
};
#endif // NMWXINCLUDEOTHERWELLS_H
diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
index 4b6b5c8..74b22de 100644
--- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
@@ -176,17 +176,17 @@ bool isFiniteSolverNumber(double value)
#endif
}
-// 旧的双对数曲线 DLL 仍是单相接口,只在结果后处理时按井别取参考相。
-// PEBI 主求解已经在场景中接收油、气、水三相全部流量。
+// 旧的双对数曲线 DLL 仍是单相接口,只在结果后处理时取输入捕获阶段
+// 冻结的制度参考相。PEBI 主求解仍在场景中接收油、气、水三相全部流量。
const QVector* referenceRatesOf(
const nmPebiWellInputSnapshot& oWellInput)
{
- switch(oWellInput.m_eWellCategory) {
- case NM_WellCategory_Oil:
+ switch(oWellInput.m_eFlowSchedulePhase) {
+ case PHASE_Oil:
return &oWellInput.m_vecOilRates;
- case NM_WellCategory_Gas:
+ case PHASE_Gas:
return &oWellInput.m_vecGasRates;
- case NM_WellCategory_Water:
+ case PHASE_Water:
return &oWellInput.m_vecWaterRates;
default:
return nullptr;
diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp
index 7f2d963..c3baa86 100644
--- a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp
@@ -227,33 +227,25 @@ bool captureWellFlowSchedule(
const QVector vecWaterPoints =
pWellData->getFlowSegmentPoints(PHASE_Water);
- const QVector* pTimePoints = nullptr;
- switch(pWellData->getReferenceFlowPhase()) {
- case PHASE_Oil:
- pTimePoints = &vecOilPoints;
- break;
- case PHASE_Gas:
- pTimePoints = &vecGasPoints;
- break;
- case PHASE_Water:
- pTimePoints = &vecWaterPoints;
- break;
- default:
- break;
- }
- if(pTimePoints == nullptr || pTimePoints->isEmpty()) {
- pTimePoints = !vecOilPoints.isEmpty() ? &vecOilPoints
- : (!vecGasPoints.isEmpty() ? &vecGasPoints
- : (!vecWaterPoints.isEmpty() ? &vecWaterPoints : nullptr));
- }
- if(pTimePoints == nullptr || pTimePoints->isEmpty()) {
+ // 公共段时长取自当前至少一个启用相;井别只影响首选顺序,不裁剪相数据。
+ oWellInput.m_eFlowSchedulePhase =
+ pWellData->getFlowSchedulePhase();
+ const QVector vecTimePoints =
+ pWellData->getFlowSegmentPoints();
+ if(oWellInput.m_eFlowSchedulePhase == PHASE_UNKNOWN ||
+ vecTimePoints.isEmpty()) {
return false;
}
- const int nSegmentCount = pTimePoints->size();
- if((!vecOilPoints.isEmpty() && vecOilPoints.size() != nSegmentCount) ||
- (!vecGasPoints.isEmpty() && vecGasPoints.size() != nSegmentCount) ||
- (!vecWaterPoints.isEmpty() && vecWaterPoints.size() != nSegmentCount)) {
+ const int nSegmentCount = vecTimePoints.size();
+ // GaugeDataEx2 本身只有一列公共段时长。这里仅防御启用相数组长度异常,
+ // 未启用相即使保留原始数据,也不会参与本次快照组装。
+ if((pWellData->getUseOilRate() && !vecOilPoints.isEmpty() &&
+ vecOilPoints.size() != nSegmentCount) ||
+ (pWellData->getUseGasRate() && !vecGasPoints.isEmpty() &&
+ vecGasPoints.size() != nSegmentCount) ||
+ (pWellData->getUseWaterRate() && !vecWaterPoints.isEmpty() &&
+ vecWaterPoints.size() != nSegmentCount)) {
return false;
}
@@ -261,14 +253,19 @@ bool captureWellFlowSchedule(
oWellInput.m_vecOilRates.reserve(nSegmentCount);
oWellInput.m_vecGasRates.reserve(nSegmentCount);
oWellInput.m_vecWaterRates.reserve(nSegmentCount);
+ // 求解器要求 qo/qg/qw 与 Rate.t 等长。用户设为“无”的相用同轴零值占位,
+ // 只关闭该相源汇项,不改变其他相的段数和时间位置。
for(int nIndex = 0; nIndex < nSegmentCount; ++nIndex) {
- oWellInput.m_vecFlowDurations.append((*pTimePoints)[nIndex].x());
+ oWellInput.m_vecFlowDurations.append(vecTimePoints[nIndex].x());
oWellInput.m_vecOilRates.append(
- vecOilPoints.isEmpty() ? 0.0 : vecOilPoints[nIndex].y());
+ !pWellData->getUseOilRate() || vecOilPoints.isEmpty()
+ ? 0.0 : vecOilPoints[nIndex].y());
oWellInput.m_vecGasRates.append(
- vecGasPoints.isEmpty() ? 0.0 : vecGasPoints[nIndex].y());
+ !pWellData->getUseGasRate() || vecGasPoints.isEmpty()
+ ? 0.0 : vecGasPoints[nIndex].y());
oWellInput.m_vecWaterRates.append(
- vecWaterPoints.isEmpty() ? 0.0 : vecWaterPoints[nIndex].y());
+ !pWellData->getUseWaterRate() || vecWaterPoints.isEmpty()
+ ? 0.0 : vecWaterPoints[nIndex].y());
}
return true;
}
diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
index 798d8c5..72fa9d0 100644
--- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
+++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
@@ -4150,16 +4150,17 @@ nmDataAnalyzeManager::getEffectiveCalculationWells() const
continue;
}
- const bool bHasValidRate =
- nmIsValidWellCategory(pWellData->getWellCategory()) &&
- pWellData->getFlowSegmentCount() > 0;
- if(!bHasValidRate) {
+ const bool bHasAvailableRate =
+ nmIsValidWellCategory(pWellData->getWellCategory()) &&
+ pWellData->hasAvailableFlowRate();
+ if(!bHasAvailableRate) {
vecEffectiveWells.append(nmCalculationWellRef(
pWellData->getWellCode(),
NM_CaseWell_Observation));
setEffectiveWellCodes.insert(pWellData->getWellCode());
- } else if(setIncludedRateWellCodes.contains(
- pWellData->getWellCode())) {
+ } else if(setIncludedRateWellCodes.contains(
+ pWellData->getWellCode()) &&
+ pWellData->getFlowSegmentCount() > 0) {
vecEffectiveWells.append(nmCalculationWellRef(
pWellData->getWellCode(),
NM_CaseWell_RateControlled));
diff --git a/Src/nmNum/nmData/nmDataWellBase.cpp b/Src/nmNum/nmData/nmDataWellBase.cpp
index c7f1c21..fe5451e 100644
--- a/Src/nmNum/nmData/nmDataWellBase.cpp
+++ b/Src/nmNum/nmData/nmDataWellBase.cpp
@@ -48,8 +48,22 @@ static const TRecord* findGaugeRecordByCode(const QVector& vecRecords,
static const QString kManualPressureGaugeCode = QString::fromLatin1("__ManualPressureRecord__");
static const QString kManualFlowGaugeCode = QString::fromLatin1("__ManualFlowRecord__");
+// 判断一组流动段中是否存在实际产量;公共 (0,0) 占位点和全零相均不算产量。
+static bool hasNonZeroFlowSegment(const QVector& vecPoints)
+{
+ for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
+ if(vecPoints[nIndex].y() != 0.0) {
+ return true;
+ }
+ }
+ return false;
+}
+
nmDataWellBase::nmDataWellBase()
: m_eWellCategory(NM_WellCategory_Unknown)
+ , m_bUseOilRate(true)
+ , m_bUseGasRate(true)
+ , m_bUseWaterRate(true)
, m_nIndexF(0)
, m_bPlotVisible(true)
, m_bTimeDependentSkin(false)
@@ -165,6 +179,9 @@ nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other)
m_vecFlowRecords = other.m_vecFlowRecords;
m_sSelectedPressureGaugeCode = other.m_sSelectedPressureGaugeCode;
m_sSelectedFlowGaugeCode = other.m_sSelectedFlowGaugeCode;
+ m_bUseOilRate = other.m_bUseOilRate;
+ m_bUseGasRate = other.m_bUseGasRate;
+ m_bUseWaterRate = other.m_bUseWaterRate;
m_nIndexF = other.m_nIndexF;
m_finalWellboreStorage = other.m_finalWellboreStorage;
m_inputWellHead = other.m_inputWellHead;
@@ -285,6 +302,10 @@ rapidjson::Value nmDataWellBase::ToJsonValue(rapidjson::Document::AllocatorType&
rapidjson::Value(baFlowGaugeCode.constData(),
static_cast(baFlowGaugeCode.size()),
allocator).Move(), allocator);
+ // 井别只描述工程井属性,三相是否参与求解必须独立保存。
+ wellObject.AddMember("UseOilRate", m_bUseOilRate, allocator);
+ wellObject.AddMember("UseGasRate", m_bUseGasRate, allocator);
+ wellObject.AddMember("UseWaterRate", m_bUseWaterRate, allocator);
// 序列化射孔段集合 (现在处理指针)
rapidjson::Value perforationsArray(rapidjson::kArrayType);
@@ -455,6 +476,18 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue)
m_sSelectedFlowGaugeCode = QString::fromUtf8(
jsonValue["SelectedFlowGaugeCode"].GetString());
}
+ if(jsonValue.HasMember("UseOilRate") &&
+ jsonValue["UseOilRate"].IsBool()) {
+ m_bUseOilRate = jsonValue["UseOilRate"].GetBool();
+ }
+ if(jsonValue.HasMember("UseGasRate") &&
+ jsonValue["UseGasRate"].IsBool()) {
+ m_bUseGasRate = jsonValue["UseGasRate"].GetBool();
+ }
+ if(jsonValue.HasMember("UseWaterRate") &&
+ jsonValue["UseWaterRate"].IsBool()) {
+ m_bUseWaterRate = jsonValue["UseWaterRate"].GetBool();
+ }
// 反序列化射孔段集合 (现在处理指针)
if(jsonValue.HasMember("Perforations") && jsonValue["Perforations"].IsArray()) {
@@ -909,6 +942,72 @@ QVector nmDataWellBase::getFlowRecords() const
return m_vecFlowRecords;
}
+bool nmDataWellBase::isFlowPhaseUsed(NM_PHASE_TYPE eFlowPhase) const
+{
+ // 分相开关是数值模块自己的求解配置,不反向写入框架 GaugeDataEx2。
+ switch(eFlowPhase) {
+ case PHASE_Oil:
+ return m_bUseOilRate;
+ case PHASE_Gas:
+ return m_bUseGasRate;
+ case PHASE_Water:
+ return m_bUseWaterRate;
+ default:
+ return false;
+ }
+}
+
+void nmDataWellBase::setFlowPhaseUsed(
+ NM_PHASE_TYPE eFlowPhase, bool bUsed)
+{
+ // 未知相和组合相不是独立的求解数组,不能映射到任一分相开关。
+ switch(eFlowPhase) {
+ case PHASE_Oil:
+ m_bUseOilRate = bUsed;
+ break;
+ case PHASE_Gas:
+ m_bUseGasRate = bUsed;
+ break;
+ case PHASE_Water:
+ m_bUseWaterRate = bUsed;
+ break;
+ default:
+ break;
+ }
+}
+
+bool nmDataWellBase::getUseOilRate() const
+{
+ // 保留显式 getter,便于 Qt 4.8 时代的调用代码直接读取三个固定相。
+ return m_bUseOilRate;
+}
+
+bool nmDataWellBase::getUseGasRate() const
+{
+ return m_bUseGasRate;
+}
+
+bool nmDataWellBase::getUseWaterRate() const
+{
+ return m_bUseWaterRate;
+}
+
+void nmDataWellBase::setUseOilRate(bool bUsed)
+{
+ // 这里只保存选择状态;记录存在性和流动段合法性由提交及快照阶段统一处理。
+ m_bUseOilRate = bUsed;
+}
+
+void nmDataWellBase::setUseGasRate(bool bUsed)
+{
+ m_bUseGasRate = bUsed;
+}
+
+void nmDataWellBase::setUseWaterRate(bool bUsed)
+{
+ m_bUseWaterRate = bUsed;
+}
+
QVector nmDataWellBase::getFlowSegmentPoints(
NM_PHASE_TYPE eFlowPhase) const
{
@@ -958,14 +1057,61 @@ bool nmDataWellBase::hasAnyFlowData() const
hasFlowPhaseData(PHASE_Water);
}
+bool nmDataWellBase::hasAvailableFlowRate() const
+{
+ // 这里判断框架事实,不受当前 Gauge Code 和三个分相开关影响。
+ // 数据管理器用它区分“未包含的产量井”和“自动观察井”。
+ for(int nRecordIndex = 0;
+ nRecordIndex < m_vecFlowRecords.size();
+ ++nRecordIndex) {
+ const nmFlowGaugeRecord& oRecord = m_vecFlowRecords[nRecordIndex];
+ if(oRecord.eStatus != NM_GaugeRecord_Usable) {
+ continue;
+ }
+ if(hasNonZeroFlowSegment(oRecord.vecOilPoints) ||
+ hasNonZeroFlowSegment(oRecord.vecGasPoints) ||
+ hasNonZeroFlowSegment(oRecord.vecWaterPoints)) {
+ return true;
+ }
+ }
+ return false;
+}
+
QVector nmDataWellBase::getFlowSegmentPoints() const
{
- return getFlowSegmentPoints(getReferenceFlowPhase());
+ const NM_PHASE_TYPE eSchedulePhase = getFlowSchedulePhase();
+ return eSchedulePhase == PHASE_UNKNOWN
+ ? QVector() : getFlowSegmentPoints(eSchedulePhase);
+}
+
+NM_PHASE_TYPE nmDataWellBase::getFlowSchedulePhase() const
+{
+ // 公共流量制度优先使用井别对应且已启用的相;该相被设为“无”时,
+ // 再按油、气、水顺序选择其他已启用且有数据的相作为参考流量。
+ const NM_PHASE_TYPE eReferencePhase = getReferenceFlowPhase();
+ if(isFlowPhaseUsed(eReferencePhase) && hasNonZeroFlowSegment(
+ getFlowSegmentPoints(eReferencePhase))) {
+ return eReferencePhase;
+ }
+ const NM_PHASE_TYPE arrPhases[] = {
+ PHASE_Oil, PHASE_Gas, PHASE_Water
+ };
+ for(int nIndex = 0; nIndex < 3; ++nIndex) {
+ if(!isFlowPhaseUsed(arrPhases[nIndex])) {
+ continue;
+ }
+ const QVector vecPoints =
+ getFlowSegmentPoints(arrPhases[nIndex]);
+ if(hasNonZeroFlowSegment(vecPoints)) {
+ return arrPhases[nIndex];
+ }
+ }
+ return PHASE_UNKNOWN;
}
int nmDataWellBase::getFlowSegmentCount() const
{
- return getFlowSegmentCount(getReferenceFlowPhase());
+ return getFlowSegmentPoints().size();
}
bool nmDataWellBase::hasValidFlowSectionIndex() const
@@ -1762,10 +1908,10 @@ bool nmDataWellBase::exportHistoryRateToCsv(const QString& outDir)
QTextStream out(&file);
- // 4) 表头与井别保持一致,便于离线排查实际进入求解器的相别。
- const char* pRateColumn = m_eWellCategory == NM_WellCategory_Gas
- ? "qg" : (m_eWellCategory == NM_WellCategory_Water
- ? "qw" : "qo");
+ // 4) 表头跟随当前制度参考相,避免井别相被设为“无”后误标流量列。
+ const NM_PHASE_TYPE eSchedulePhase = getFlowSchedulePhase();
+ const char* pRateColumn = eSchedulePhase == PHASE_Gas
+ ? "qg" : (eSchedulePhase == PHASE_Water ? "qw" : "qo");
out << "t," << pRateColumn << ",sectionIndex\n";
// 5) 写入每个点
diff --git a/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp b/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
index b6c15d5..fa07e3c 100644
--- a/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
+++ b/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
@@ -117,17 +117,14 @@ bool hasRealPhaseData(const QVector& vecPoints)
return false;
}
-// 压力/流量记录下拉项文本:优先显示计量名称,否则回退到 Gauge Code;再拼接测量时间。
+// 压力/流量记录下拉框只显示稳定的 Gauge Code,避免顶部工具栏过宽。
+// 异常旧数据没有 Code 时,才回退到框架名称。
QString formatGaugeRecordLabel(const QString& sGaugeName,
const QString& sGaugeCode,
const QString& sGaugeTime)
{
- QString sBaseLabel = sGaugeName.isEmpty() ? sGaugeCode : sGaugeName;
- if (sGaugeTime.isEmpty())
- {
- return sBaseLabel;
- }
- return sBaseLabel + QLatin1String(" | ") + sGaugeTime;
+ Q_UNUSED(sGaugeTime);
+ return sGaugeCode.isEmpty() ? sGaugeName : sGaugeCode;
}
// 统一向压力/流量记录下拉框追加一项:身份无效的记录禁用不可选,空/损坏记录仍可选但追加提示后缀。
@@ -844,45 +841,6 @@ void nmWellPressureFlowPage::slotFlowChartPointClicked(double dTimePoint)
findFlowSegmentByTableTime(dTableTimePoint));
}
-void nmWellPressureFlowPage::slotPressureGaugeChanged(int nIndex)
-{
- if (m_pWell == nullptr || nIndex < 0)
- {
- return;
- }
-
- const QString sCode = m_pPressureGaugeCombo->itemData(nIndex).toString();
- if (sCode == m_pWell->getSelectedPressureGaugeCode())
- {
- return;
- }
-
- m_pWell->selectPressureGaugeCode(sCode);
- refreshData();
-}
-
-void nmWellPressureFlowPage::slotFlowGaugeChanged(int nIndex)
-{
- if (m_pWell == nullptr || nIndex < 0)
- {
- return;
- }
-
- const QString sCode = m_pFlowGaugeCombo->itemData(nIndex).toString();
- if (sCode == m_pWell->getSelectedFlowGaugeCode())
- {
- return;
- }
-
- // 必须调用 switchSelectedFlowGaugeCode 而不是 selectFlowGaugeCode,才能根据新记录
- // 自己的 nIndexF 恢复/校验流动段选择,否则换记录后选择器就回到默认 0。
- m_pWell->switchSelectedFlowGaugeCode(sCode);
- // 换记录后显示相重置为新记录的默认相,m_vecFlowGaugeRecords 仍为本井完整记录列表,
- // 因此可在 refreshData() 重新填充下拉框前先用旧缓存安全查找新选中记录。
- m_eDisplayPhase = defaultDisplayPhase(currentFlowRecord());
- refreshData();
-}
-
void nmWellPressureFlowPage::slotDisplayPhaseChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
@@ -1067,12 +1025,16 @@ void nmWellPressureFlowPage::createRecordSelectionBar()
m_pPressureGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->setMinimumWidth(160);
+ m_pPressureGaugeCombo->setToolTip(
+ tr("This page only displays the selected record."));
QLabel* pFlowLabel = new QLabel(
tr("Flow Record:"), m_pRecordSelectionBar);
m_pFlowGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->setMinimumWidth(160);
+ m_pFlowGaugeCombo->setToolTip(
+ tr("This page only displays the selected record."));
// 显示相是纯展示状态,整体容器随记录是否为多相含数据而隐藏/显示。
m_pDisplayPhaseContainer = new QWidget(m_pRecordSelectionBar);
@@ -1101,14 +1063,6 @@ void nmWellPressureFlowPage::createRecordSelectionBar()
pBarLayout->addStretch(1);
pBarLayout->addWidget(m_pCheckDataButton);
- connect(m_pPressureGaugeCombo,
- SIGNAL(currentIndexChanged(int)),
- this,
- SLOT(slotPressureGaugeChanged(int)));
- connect(m_pFlowGaugeCombo,
- SIGNAL(currentIndexChanged(int)),
- this,
- SLOT(slotFlowGaugeChanged(int)));
connect(m_pDisplayPhaseCombo,
SIGNAL(currentIndexChanged(int)),
this,
@@ -1138,10 +1092,11 @@ void nmWellPressureFlowPage::populatePressureGaugeCombo()
QVector vecRecords =
m_pWell->getPressureRecords();
- m_pPressureGaugeCombo->setEnabled(!vecRecords.isEmpty());
+ m_pPressureGaugeCombo->setEnabled(false);
+ m_pPressureGaugeCombo->addItem(tr("None"), QString());
const QString sSelectedCode = m_pWell->getSelectedPressureGaugeCode();
- int nCurrentIndex = -1;
+ int nCurrentIndex = 0;
for (int nIndex = 0; nIndex < vecRecords.size(); ++nIndex)
{
const nmPressureGaugeRecord& oRecord = vecRecords[nIndex];
@@ -1150,13 +1105,10 @@ void nmWellPressureFlowPage::populatePressureGaugeCombo()
oRecord.sGaugeTime, oRecord.eStatus);
if (oRecord.sGaugeCode == sSelectedCode)
{
- nCurrentIndex = nIndex;
+ nCurrentIndex = nIndex + 1;
}
}
- if (nCurrentIndex >= 0)
- {
- m_pPressureGaugeCombo->setCurrentIndex(nCurrentIndex);
- }
+ m_pPressureGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
}
@@ -1180,10 +1132,11 @@ void nmWellPressureFlowPage::populateFlowGaugeCombo()
// 缓存整份记录列表供 currentFlowRecord() 安全返回内部指针,记录内容切换记录时不变。
m_vecFlowGaugeRecords = m_pWell->getFlowRecords();
- m_pFlowGaugeCombo->setEnabled(!m_vecFlowGaugeRecords.isEmpty());
+ m_pFlowGaugeCombo->setEnabled(false);
+ m_pFlowGaugeCombo->addItem(tr("None"), QString());
const QString sSelectedCode = m_pWell->getSelectedFlowGaugeCode();
- int nCurrentIndex = -1;
+ int nCurrentIndex = 0;
for (int nIndex = 0; nIndex < m_vecFlowGaugeRecords.size(); ++nIndex)
{
const nmFlowGaugeRecord& oRecord = m_vecFlowGaugeRecords[nIndex];
@@ -1192,13 +1145,10 @@ void nmWellPressureFlowPage::populateFlowGaugeCombo()
oRecord.sGaugeTime, oRecord.eStatus);
if (oRecord.sGaugeCode == sSelectedCode)
{
- nCurrentIndex = nIndex;
+ nCurrentIndex = nIndex + 1;
}
}
- if (nCurrentIndex >= 0)
- {
- m_pFlowGaugeCombo->setCurrentIndex(nCurrentIndex);
- }
+ m_pFlowGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
}
diff --git a/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp b/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp
index f61de15..e270fcd 100644
--- a/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp
@@ -1,9 +1,13 @@
#include "nmWxIncludeOtherWells.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
+
+#include
#include
-#include
#include
+#include
+#include
+#include
namespace
{
@@ -20,16 +24,26 @@ bool isSupportedNumericalWell(nmDataWellBase* pWellData)
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
-// 表格中的“产量”表示该相至少出现过一个非零流量;全零相仍会保留在
-// 求解输入中,但不能在摘要里误导用户为该井实际存在该相产量。
-bool hasNonZeroRate(nmDataWellBase* pWellData, NM_PHASE_TYPE ePhase)
+// 从同一条流量记录中取得指定相的只读点集,避免三处分支判断各自漂移。
+const QVector& phasePoints(
+ const nmFlowGaugeRecord& oRecord, NM_PHASE_TYPE ePhase)
{
- if(pWellData == nullptr) {
- return false;
+ switch(ePhase) {
+ case PHASE_Gas:
+ return oRecord.vecGasPoints;
+ case PHASE_Water:
+ return oRecord.vecWaterPoints;
+ case PHASE_Oil:
+ default:
+ return oRecord.vecOilPoints;
}
+}
- const QVector vecPoints =
- pWellData->getFlowSegmentPoints(ePhase);
+// “有该相产量”按至少一个非零值判断;只有公共零占位行不算真实产量。
+bool hasNonZeroPhaseRate(
+ const nmFlowGaugeRecord& oRecord, NM_PHASE_TYPE ePhase)
+{
+ const QVector& vecPoints = phasePoints(oRecord, ePhase);
for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
if(vecPoints[nIndex].y() != 0.0) {
return true;
@@ -37,6 +51,182 @@ bool hasNonZeroRate(nmDataWellBase* pWellData, NM_PHASE_TYPE ePhase)
}
return false;
}
+
+// 下拉项只有在记录可用且该相确有非零产量时才允许选择。
+bool isSelectableFlowPhase(
+ const nmFlowGaugeRecord& oRecord, NM_PHASE_TYPE ePhase)
+{
+ return oRecord.eStatus == NM_GaugeRecord_Usable &&
+ hasNonZeroPhaseRate(oRecord, ePhase);
+}
+
+// 候选井至少要有一条可选分相记录;压力记录不是主动井候选的必要条件。
+bool hasSelectableRateRecord(const QVector& vecRecords)
+{
+ for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) {
+ const nmFlowGaugeRecord& oRecord = vecRecords[nIndex];
+ if(isSelectableFlowPhase(oRecord, PHASE_Oil) ||
+ isSelectableFlowPhase(oRecord, PHASE_Gas) ||
+ isSelectableFlowPhase(oRecord, PHASE_Water)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Gauge Code 是记录的稳定身份,显示名称和测量时间都不能替代它参与关联。
+const nmFlowGaugeRecord* findFlowRecord(
+ const QVector& vecRecords,
+ const QString& sGaugeCode)
+{
+ if(sGaugeCode.isEmpty()) {
+ return nullptr;
+ }
+ for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) {
+ if(vecRecords[nIndex].sGaugeCode == sGaugeCode) {
+ return &vecRecords[nIndex];
+ }
+ }
+ return nullptr;
+}
+
+// 压力曲线至少需要两个点才能用于历史曲线计算。
+bool isSelectablePressureRecord(const nmPressureGaugeRecord& oRecord)
+{
+ return oRecord.eStatus == NM_GaugeRecord_Usable &&
+ oRecord.vecPressurePoints.size() >= 2;
+}
+
+// 打开对话框时只恢复仍存在且仍可用的压力记录 Code。
+bool containsSelectablePressureCode(
+ const QVector& vecRecords,
+ const QString& sGaugeCode)
+{
+ for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) {
+ if(vecRecords[nIndex].sGaugeCode == sGaugeCode &&
+ isSelectablePressureRecord(vecRecords[nIndex])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// 下拉框只显示稳定的 Gauge Code,避免名称和测量时间挤占表格列宽。
+// 异常旧数据没有 Code 时,才回退到框架名称或“未命名”。
+QString gaugeRecordLabel(const QString& sGaugeName,
+ const QString& sGaugeCode, const QString& sGaugeTime)
+{
+ Q_UNUSED(sGaugeTime);
+ QString sLabel = sGaugeCode.isEmpty() ? sGaugeName : sGaugeCode;
+ if(sLabel.isEmpty()) {
+ sLabel = nmWxIncludeOtherWells::tr("(unnamed)");
+ }
+ return sLabel;
+}
+
+// 异常记录仍显示在下拉列表中,后缀用于说明其不可用于当前选择的原因。
+QString recordStatusSuffix(NM_GAUGE_RECORD_STATUS eStatus)
+{
+ switch(eStatus) {
+ case NM_GaugeRecord_Empty:
+ return nmWxIncludeOtherWells::tr(" (Empty)");
+ case NM_GaugeRecord_Corrupt:
+ return nmWxIncludeOtherWells::tr(" (Corrupt)");
+ case NM_GaugeRecord_InvalidIdentity:
+ return nmWxIncludeOtherWells::tr(" (Invalid)");
+ default:
+ return QString();
+ }
+}
+
+// Qt 4.8 的 QComboBox 没有直接禁用单项的接口,通过其标准项模型设置标志。
+void setComboItemEnabled(QComboBox* pComboBox, int nIndex, bool bEnabled)
+{
+ QStandardItemModel* pModel = qobject_cast(
+ pComboBox == nullptr ? nullptr : pComboBox->model());
+ QStandardItem* pItem = pModel == nullptr ? nullptr : pModel->item(nIndex);
+ if(pItem == nullptr) {
+ return;
+ }
+ if(bEnabled) {
+ pItem->setFlags(pItem->flags() | Qt::ItemIsEnabled);
+ } else {
+ pItem->setFlags(pItem->flags() & ~Qt::ItemIsEnabled);
+ }
+}
+
+// 流动段索引是一基索引;统计段数时必须先排除框架可能写入的公共占位行。
+int flowRecordSegmentCount(const nmFlowGaugeRecord& oRecord)
+{
+ const QVector* pPoints = !oRecord.vecOilPoints.isEmpty()
+ ? &oRecord.vecOilPoints : (!oRecord.vecGasPoints.isEmpty()
+ ? &oRecord.vecGasPoints : (!oRecord.vecWaterPoints.isEmpty()
+ ? &oRecord.vecWaterPoints : nullptr));
+ if(pPoints == nullptr) {
+ return 0;
+ }
+
+ bool bHasPlaceholder = false;
+ if(oRecord.bMultiPhase) {
+ bHasPlaceholder = !oRecord.vecOilPoints.isEmpty() &&
+ !oRecord.vecGasPoints.isEmpty() &&
+ !oRecord.vecWaterPoints.isEmpty() &&
+ oRecord.vecOilPoints.first() == QPointF(0.0, 0.0) &&
+ oRecord.vecGasPoints.first() == QPointF(0.0, 0.0) &&
+ oRecord.vecWaterPoints.first() == QPointF(0.0, 0.0);
+ } else {
+ bHasPlaceholder = pPoints->first() == QPointF(0.0, 0.0);
+ }
+ return pPoints->size() - (bHasPlaceholder ? 1 : 0);
+}
+
+// 井别只用于只读展示和默认参考相判断,不由油、气、水下拉选择反推。
+QString wellCategoryText(NM_WELL_CATEGORY eWellCategory)
+{
+ switch(eWellCategory) {
+ case NM_WellCategory_Oil:
+ return nmWxIncludeOtherWells::tr("Oil well");
+ case NM_WellCategory_Gas:
+ return nmWxIncludeOtherWells::tr("Gas well");
+ case NM_WellCategory_Water:
+ return nmWxIncludeOtherWells::tr("Water well");
+ default:
+ return nmWxIncludeOtherWells::tr("Unknown");
+ }
+}
+
+// WellDataRow 将三个固定相保存为独立开关,这里统一完成枚举到成员的映射。
+bool rowPhaseUsed(const WellDataRow& oData, NM_PHASE_TYPE ePhase)
+{
+ switch(ePhase) {
+ case PHASE_Oil:
+ return oData.m_bUseOilRate;
+ case PHASE_Gas:
+ return oData.m_bUseGasRate;
+ case PHASE_Water:
+ return oData.m_bUseWaterRate;
+ default:
+ return false;
+ }
+}
+
+void setRowPhaseUsed(
+ WellDataRow& oData, NM_PHASE_TYPE ePhase, bool bUsed)
+{
+ switch(ePhase) {
+ case PHASE_Oil:
+ oData.m_bUseOilRate = bUsed;
+ break;
+ case PHASE_Gas:
+ oData.m_bUseGasRate = bUsed;
+ break;
+ case PHASE_Water:
+ oData.m_bUseWaterRate = bUsed;
+ break;
+ default:
+ break;
+ }
+}
}
nmWxIncludeOtherWells::nmWxIncludeOtherWells(
@@ -51,7 +241,7 @@ nmWxIncludeOtherWells::nmWxIncludeOtherWells(
nmWxIncludeOtherWells::~nmWxIncludeOtherWells()
{
- // Qt的对象父子关系会自动处理内存释放
+ // Qt 的对象父子关系会自动处理控件释放。
}
void nmWxIncludeOtherWells::initUI()
@@ -62,157 +252,389 @@ void nmWxIncludeOtherWells::initUI()
this->setupConnections();
this->setWindowTitle(tr("Include Other Wells"));
- this->resize(772, 339);
+ // 缩小时依靠横向滚动保留稳定列宽,不让中英文标题挤压下拉内容。
+ this->setMinimumSize(760, 280);
+ this->resize(1240, 420);
}
void nmWxIncludeOtherWells::setupTable()
{
m_pTableWidget = new QTableWidget(this);
-
- QVector vecMapWells;
- if(m_pDataManager != nullptr) {
- vecMapWells = m_pDataManager->getWellDataList();
- }
-
- // 设置行列数
- m_pTableWidget->setColumnCount(6);
- m_pTableWidget->setRowCount(vecMapWells.size());
-
+ m_pTableWidget->setColumnCount(ColumnCount);
+ m_pTableWidget->setRowCount(0);
m_pTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pTableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
-
- m_pTableWidget->setStyleSheet(
- "QTableWidget::item:focus {"
- " background: transparent;"
- " color: black;"
- "}"
- );
-
- // 设置水平表头(列标题)
- QStringList headers;
- headers << tr("Name") << tr("Included") << tr("Oil production")
- << tr("Gas production") << tr("Water production") << tr("Pressure");
- m_pTableWidget->setHorizontalHeaderLabels(headers);
- // 确保水平表头可见
+ m_pTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_pTableWidget->setAlternatingRowColors(true);
+ m_pTableWidget->setWordWrap(false);
+ m_pTableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
+ m_pTableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+
+ QStringList listHeaders;
+ listHeaders << tr("Name") << tr("Well category") << tr("Included")
+ << tr("Oil production") << tr("Gas production")
+ << tr("Water production") << tr("Pressure")
+ << tr("Flow segment");
+ m_pTableWidget->setHorizontalHeaderLabels(listHeaders);
m_pTableWidget->horizontalHeader()->setVisible(true);
+ m_pTableWidget->horizontalHeader()->setMinimumSectionSize(64);
+ m_pTableWidget->horizontalHeader()->setStretchLastSection(false);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnName, QHeaderView::Stretch);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnWellCategory, QHeaderView::ResizeToContents);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnIncluded, QHeaderView::Fixed);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnOilRate, QHeaderView::Interactive);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnGasRate, QHeaderView::Interactive);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnWaterRate, QHeaderView::Interactive);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnPressure, QHeaderView::Interactive);
+ m_pTableWidget->horizontalHeader()->setResizeMode(
+ ColumnFlowSegment, QHeaderView::Fixed);
+ m_pTableWidget->setColumnWidth(ColumnIncluded, 76);
+ m_pTableWidget->setColumnWidth(ColumnOilRate, 180);
+ m_pTableWidget->setColumnWidth(ColumnGasRate, 180);
+ m_pTableWidget->setColumnWidth(ColumnWaterRate, 180);
+ m_pTableWidget->setColumnWidth(ColumnPressure, 190);
+ m_pTableWidget->setColumnWidth(ColumnFlowSegment, 96);
+
+ m_pTableWidget->verticalHeader()->setVisible(false);
+ m_pTableWidget->verticalHeader()->setDefaultSectionSize(32);
+ m_pTableWidget->verticalHeader()->setMinimumSectionSize(28);
- // 设置垂直表头(行标题)属性
- m_pTableWidget->verticalHeader()->setVisible(true); // 保持垂直表头可见(用于行选择视觉反馈)
- m_pTableWidget->verticalHeader()->setMinimumWidth(16); // 最小宽度
-
- // 隐藏垂直表头的数字(设置为空文本)
- QStringList emptyRowHeaders;
- for(int i = 0; i < m_pTableWidget->rowCount(); ++i) {
- emptyRowHeaders << "";
+ QVector vecMapWells;
+ if(m_pDataManager != nullptr) {
+ vecMapWells = m_pDataManager->getWellDataList();
}
- m_pTableWidget->setVerticalHeaderLabels(emptyRowHeaders);
-
- // 加载数据
- QString sPrimaryWellCode = m_pDataManager != nullptr
+ const QString sPrimaryWellCode = m_pDataManager != nullptr
? m_pDataManager->getPrimaryWellCode() : QString();
- int nDisplayRow = 0;
- // 第一步:候选来源是 Map,而不是整个项目井库。该对话框只管理
- // 其他有产量的生产/注入井;主井和无产量观察井都不在这里显示。
- for(int i = 0; i < vecMapWells.size(); i++) {
- nmDataWellBase* pWell = vecMapWells[i];
- if(pWell == nullptr || !isSupportedNumericalWell(pWell)) {
+ // 候选来源仍严格限定为 Map 中的其他主动井;只把选择界面迁移到这里。
+ for(int nIndex = 0; nIndex < vecMapWells.size(); ++nIndex) {
+ nmDataWellBase* pWellData = vecMapWells[nIndex];
+ if(!isSupportedNumericalWell(pWellData) ||
+ pWellData->getWellCode() == sPrimaryWellCode ||
+ !nmIsValidWellCategory(pWellData->getWellCategory())) {
continue;
}
- const bool bIsPrimary = pWell->getWellCode() == sPrimaryWellCode;
- const bool bHasValidRate =
- nmIsValidWellCategory(pWell->getWellCategory()) &&
- pWell->getFlowSegmentCount() > 0;
- const bool bHasValidPressure = pWell->getPressurePoints().size() >= 2;
- if(bIsPrimary || !bHasValidRate) {
+ WellDataRow oWellRow;
+ oWellRow.m_sWellCode = pWellData->getWellCode();
+ oWellRow.m_sName = pWellData->getWellName();
+ oWellRow.m_bIsPrimary = false;
+ oWellRow.m_eWellCategory = pWellData->getWellCategory();
+ oWellRow.m_eMode = NM_CaseWell_RateControlled;
+ oWellRow.m_vecPressureRecords = pWellData->getPressureRecords();
+ oWellRow.m_vecFlowRecords = pWellData->getFlowRecords();
+ oWellRow.m_bCanCalculate =
+ hasSelectableRateRecord(oWellRow.m_vecFlowRecords);
+ if(!oWellRow.m_bCanCalculate) {
continue;
}
- WellDataRow oWellRow;
- oWellRow.m_sWellCode = pWell->getWellCode();
- oWellRow.m_sName = pWell->getWellName();
- oWellRow.m_bIsPrimary = bIsPrimary;
- oWellRow.m_eWellCategory = pWell->getWellCategory();
-
- // 第二步:读取这口有产量干扰井上次确认的包含状态。
- oWellRow.m_bCanCalculate = true;
- oWellRow.m_bIsIncluded =
- m_pDataManager != nullptr &&
- m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
- oWellRow.m_sWellCode);
-
- // 第三步:多相井可同时具有多列产量,逐相读取实际非零流量。
- oWellRow.m_sPressure = bHasValidPressure
- ? tr("History") : tr("None");
- oWellRow.m_sOilProd = tr("None");
- oWellRow.m_sGasProd = tr("None");
- oWellRow.m_sWaterProd = tr("None");
- if(hasNonZeroRate(pWell, PHASE_Oil)) {
- oWellRow.m_sOilProd = tr("Rate");
- }
- if(hasNonZeroRate(pWell, PHASE_Gas)) {
- oWellRow.m_sGasProd = tr("Rate");
+ oWellRow.m_sPressureGaugeCode =
+ pWellData->getSelectedPressureGaugeCode();
+ if(!containsSelectablePressureCode(
+ oWellRow.m_vecPressureRecords,
+ oWellRow.m_sPressureGaugeCode)) {
+ oWellRow.m_sPressureGaugeCode.clear();
}
- if(hasNonZeroRate(pWell, PHASE_Water)) {
- oWellRow.m_sWaterProd = tr("Rate");
+ oWellRow.m_sFlowGaugeCode =
+ pWellData->getSelectedFlowGaugeCode();
+ oWellRow.m_bUseOilRate = pWellData->getUseOilRate();
+ oWellRow.m_bUseGasRate = pWellData->getUseGasRate();
+ oWellRow.m_bUseWaterRate = pWellData->getUseWaterRate();
+ oWellRow.m_nFlowSegmentIndex = pWellData->getIndexF();
+
+ const bool bHadRequestedPhase = oWellRow.m_bUseOilRate ||
+ oWellRow.m_bUseGasRate || oWellRow.m_bUseWaterRate;
+ normalizeFlowSelection(oWellRow);
+ // 新井默认启用三相;若框架当前选择失效,回退到第一条有产量记录。
+ // 用户已明确把三相都设为“无”时不自动恢复。
+ if(!oWellRow.canCalculate() && bHadRequestedPhase) {
+ for(int nRecordIndex = 0;
+ nRecordIndex < oWellRow.m_vecFlowRecords.size();
+ ++nRecordIndex) {
+ const nmFlowGaugeRecord& oRecord =
+ oWellRow.m_vecFlowRecords[nRecordIndex];
+ if(oRecord.eStatus != NM_GaugeRecord_Usable) {
+ continue;
+ }
+ oWellRow.m_sFlowGaugeCode = oRecord.sGaugeCode;
+ oWellRow.m_bUseOilRate =
+ isSelectableFlowPhase(oRecord, PHASE_Oil);
+ oWellRow.m_bUseGasRate =
+ isSelectableFlowPhase(oRecord, PHASE_Gas);
+ oWellRow.m_bUseWaterRate =
+ isSelectableFlowPhase(oRecord, PHASE_Water);
+ normalizeFlowSelection(oWellRow);
+ if(oWellRow.canCalculate()) {
+ break;
+ }
+ }
}
- oWellRow.m_eMode = NM_CaseWell_RateControlled;
- // 添加到数据列表和表格
+ oWellRow.m_bIsIncluded = oWellRow.canCalculate() &&
+ m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
+ oWellRow.m_sWellCode);
+
+ const int nRow = m_vecWellData.size();
m_vecWellData.append(oWellRow);
- addWellDataToTable(oWellRow, nDisplayRow);
- ++nDisplayRow;
+ m_pTableWidget->setRowCount(nRow + 1);
+ addWellDataToTable(oWellRow, nRow);
}
- m_pTableWidget->setRowCount(nDisplayRow); // 调整实际显示的行数
- m_pTableWidget->horizontalHeader()->setStretchLastSection(true);
- m_pTableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
+ if(m_vecWellData.isEmpty()) {
+ m_pTableWidget->setRowCount(1);
+ m_pTableWidget->setSpan(0, 0, 1, ColumnCount);
+ QTableWidgetItem* pEmptyItem = new QTableWidgetItem(
+ tr("No eligible wells"));
+ pEmptyItem->setFlags(Qt::NoItemFlags);
+ pEmptyItem->setTextAlignment(Qt::AlignCenter);
+ m_pTableWidget->setItem(0, 0, pEmptyItem);
+ }
}
-void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
+void nmWxIncludeOtherWells::addWellDataToTable(
+ const WellDataRow& data, int row)
{
- // Name列 - 不可编辑
- QTableWidgetItem* nameItem = new QTableWidgetItem(data.m_sName);
- nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
- nameItem->setData(Qt::UserRole, data.m_sWellCode); // 表格内部始终携带 WellCode
- nameItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
- m_pTableWidget->setItem(row, 0, nameItem);
+ QTableWidgetItem* pNameItem = new QTableWidgetItem(data.m_sName);
+ pNameItem->setData(Qt::UserRole, data.m_sWellCode);
+ pNameItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+ m_pTableWidget->setItem(row, ColumnName, pNameItem);
+
+ QTableWidgetItem* pCategoryItem = new QTableWidgetItem(
+ wellCategoryText(data.m_eWellCategory));
+ pCategoryItem->setTextAlignment(Qt::AlignCenter);
+ m_pTableWidget->setItem(row, ColumnWellCategory, pCategoryItem);
+
+ QWidget* pCheckContainer = new QWidget(m_pTableWidget);
+ QCheckBox* pCheckBox = new QCheckBox(pCheckContainer);
+ QHBoxLayout* pCheckLayout = new QHBoxLayout(pCheckContainer);
+ pCheckLayout->setContentsMargins(0, 0, 0, 0);
+ pCheckLayout->setAlignment(Qt::AlignCenter);
+ pCheckLayout->addWidget(pCheckBox);
+ pCheckBox->setProperty("row", row);
+ connect(pCheckBox, SIGNAL(stateChanged(int)),
+ this, SLOT(onCheckBoxStateChanged(int)));
+ m_pTableWidget->setCellWidget(row, ColumnIncluded, pCheckContainer);
+
+ const int arrRateColumns[] = {
+ ColumnOilRate, ColumnGasRate, ColumnWaterRate
+ };
+ const NM_PHASE_TYPE arrPhases[] = {
+ PHASE_Oil, PHASE_Gas, PHASE_Water
+ };
+ for(int nIndex = 0; nIndex < 3; ++nIndex) {
+ QComboBox* pRateCombo = new QComboBox(m_pTableWidget);
+ pRateCombo->setProperty("row", row);
+ pRateCombo->setProperty("phase", static_cast(arrPhases[nIndex]));
+ pRateCombo->setMaxVisibleItems(16);
+ connect(pRateCombo, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(onRateSourceChanged(int)));
+ m_pTableWidget->setCellWidget(row, arrRateColumns[nIndex], pRateCombo);
+ }
- // Included列 - 使用自定义复选框组件
- QWidget* pWidget = new QWidget();
- QCheckBox* pCheckBox = new QCheckBox();
- QHBoxLayout* pLayout = new QHBoxLayout(pWidget);
- pLayout->addWidget(pCheckBox);
- pLayout->setAlignment(Qt::AlignCenter);
- pLayout->setContentsMargins(0, 0, 0, 0);
- pWidget->setLayout(pLayout);
+ QComboBox* pPressureCombo = new QComboBox(m_pTableWidget);
+ pPressureCombo->setProperty("row", row);
+ pPressureCombo->setMaxVisibleItems(16);
+ connect(pPressureCombo, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(onPressureSourceChanged(int)));
+ m_pTableWidget->setCellWidget(row, ColumnPressure, pPressureCombo);
- pCheckBox->setChecked(data.m_bIsIncluded);
- // 表内只有其他有产量井,用户可以决定它是否作为干扰井参与计算。
- pCheckBox->setEnabled(data.canCalculate());
- pCheckBox->setProperty("row", row); // 存储行索引
+ QComboBox* pSegmentCombo = new QComboBox(m_pTableWidget);
+ pSegmentCombo->setProperty("row", row);
+ connect(pSegmentCombo, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(onFlowSegmentChanged(int)));
+ m_pTableWidget->setCellWidget(row, ColumnFlowSegment, pSegmentCombo);
- // 连接复选框信号
- connect(pCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onCheckBoxStateChanged(int)));
+ refreshRowEditors(row);
+}
- // 设置单元格控件
- m_pTableWidget->setCellWidget(row, 1, pWidget);
+void nmWxIncludeOtherWells::normalizeFlowSelection(WellDataRow& data) const
+{
+ const nmFlowGaugeRecord* pRecord = findFlowRecord(
+ data.m_vecFlowRecords, data.m_sFlowGaugeCode);
+ if(pRecord == nullptr ||
+ pRecord->eStatus != NM_GaugeRecord_Usable) {
+ data.m_sFlowGaugeCode.clear();
+ data.m_bUseOilRate = false;
+ data.m_bUseGasRate = false;
+ data.m_bUseWaterRate = false;
+ data.m_nFlowSegmentIndex = 0;
+ return;
+ }
+
+ data.m_bUseOilRate = data.m_bUseOilRate &&
+ isSelectableFlowPhase(*pRecord, PHASE_Oil);
+ data.m_bUseGasRate = data.m_bUseGasRate &&
+ isSelectableFlowPhase(*pRecord, PHASE_Gas);
+ data.m_bUseWaterRate = data.m_bUseWaterRate &&
+ isSelectableFlowPhase(*pRecord, PHASE_Water);
+ if(!data.m_bUseOilRate && !data.m_bUseGasRate &&
+ !data.m_bUseWaterRate) {
+ data.m_sFlowGaugeCode.clear();
+ data.m_nFlowSegmentIndex = 0;
+ return;
+ }
+
+ const int nSegmentCount = flowRecordSegmentCount(*pRecord);
+ if(nSegmentCount <= 0) {
+ data.m_sFlowGaugeCode.clear();
+ data.m_bUseOilRate = false;
+ data.m_bUseGasRate = false;
+ data.m_bUseWaterRate = false;
+ data.m_nFlowSegmentIndex = 0;
+ return;
+ }
+ if(data.m_nFlowSegmentIndex < 1 ||
+ data.m_nFlowSegmentIndex > nSegmentCount) {
+ data.m_nFlowSegmentIndex = pRecord->nIndexF;
+ if(data.m_nFlowSegmentIndex < 1 ||
+ data.m_nFlowSegmentIndex > nSegmentCount) {
+ data.m_nFlowSegmentIndex = nSegmentCount;
+ }
+ }
+}
+
+void nmWxIncludeOtherWells::populateRateCombo(
+ QComboBox* pComboBox, const WellDataRow& data,
+ NM_PHASE_TYPE ePhase) const
+{
+ if(pComboBox == nullptr) {
+ return;
+ }
+ const bool bSignalsWereBlocked = pComboBox->blockSignals(true);
+ pComboBox->clear();
+ pComboBox->addItem(tr("None"), QString());
+
+ // 三个分相下拉框都列出完整记录集合,但当前相无数据的记录保持可见且禁用。
+ for(int nIndex = 0; nIndex < data.m_vecFlowRecords.size(); ++nIndex) {
+ const nmFlowGaugeRecord& oRecord = data.m_vecFlowRecords[nIndex];
+ const bool bSelectable = isSelectableFlowPhase(oRecord, ePhase);
+ QString sLabel = gaugeRecordLabel(
+ oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime);
+ sLabel += recordStatusSuffix(oRecord.eStatus);
+ if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) {
+ sLabel += tr(" (No data)");
+ }
+ pComboBox->addItem(sLabel, oRecord.sGaugeCode);
+ setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable);
+ }
+
+ int nCurrentIndex = 0;
+ if(rowPhaseUsed(data, ePhase)) {
+ const int nFoundIndex = pComboBox->findData(data.m_sFlowGaugeCode);
+ if(nFoundIndex > 0) {
+ nCurrentIndex = nFoundIndex;
+ }
+ }
+ pComboBox->setCurrentIndex(nCurrentIndex);
+ pComboBox->setToolTip(pComboBox->currentText());
+ pComboBox->blockSignals(bSignalsWereBlocked);
+}
+
+void nmWxIncludeOtherWells::populatePressureCombo(
+ QComboBox* pComboBox, const WellDataRow& data) const
+{
+ if(pComboBox == nullptr) {
+ return;
+ }
+ const bool bSignalsWereBlocked = pComboBox->blockSignals(true);
+ pComboBox->clear();
+ pComboBox->addItem(tr("None"), QString());
+ for(int nIndex = 0; nIndex < data.m_vecPressureRecords.size(); ++nIndex) {
+ const nmPressureGaugeRecord& oRecord = data.m_vecPressureRecords[nIndex];
+ const bool bSelectable = isSelectablePressureRecord(oRecord);
+ QString sLabel = gaugeRecordLabel(
+ oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime);
+ sLabel += recordStatusSuffix(oRecord.eStatus);
+ if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) {
+ sLabel += tr(" (No data)");
+ }
+ pComboBox->addItem(sLabel, oRecord.sGaugeCode);
+ setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable);
+ }
+
+ const int nCurrentIndex = pComboBox->findData(
+ data.m_sPressureGaugeCode);
+ pComboBox->setCurrentIndex(nCurrentIndex < 0 ? 0 : nCurrentIndex);
+ pComboBox->setToolTip(pComboBox->currentText());
+ pComboBox->blockSignals(bSignalsWereBlocked);
+}
+
+void nmWxIncludeOtherWells::populateFlowSegmentCombo(
+ QComboBox* pComboBox, const WellDataRow& data) const
+{
+ if(pComboBox == nullptr) {
+ return;
+ }
+ const bool bSignalsWereBlocked = pComboBox->blockSignals(true);
+ pComboBox->clear();
+ const nmFlowGaugeRecord* pRecord = findFlowRecord(
+ data.m_vecFlowRecords, data.m_sFlowGaugeCode);
+ const int nSegmentCount = pRecord == nullptr
+ ? 0 : flowRecordSegmentCount(*pRecord);
+ for(int nIndex = 1; nIndex <= nSegmentCount; ++nIndex) {
+ pComboBox->addItem(tr("Segment %1").arg(nIndex), nIndex);
+ }
+ const int nCurrentIndex = pComboBox->findData(
+ data.m_nFlowSegmentIndex);
+ if(nCurrentIndex >= 0) {
+ pComboBox->setCurrentIndex(nCurrentIndex);
+ }
+ pComboBox->setEnabled(data.canCalculate());
+ pComboBox->setToolTip(pComboBox->currentText());
+ pComboBox->blockSignals(bSignalsWereBlocked);
+}
- // 其他列只显示各类 simulation target,与 Saphir 对话框保持一致。
- QStringList listOtherData;
- listOtherData.append(data.m_sOilProd);
- listOtherData.append(data.m_sGasProd);
- listOtherData.append(data.m_sWaterProd);
- listOtherData.append(data.m_sPressure);
+void nmWxIncludeOtherWells::refreshRowEditors(int row)
+{
+ if(row < 0 || row >= m_vecWellData.size()) {
+ return;
+ }
+ const WellDataRow& oData = m_vecWellData[row];
+ populateRateCombo(qobject_cast(
+ m_pTableWidget->cellWidget(row, ColumnOilRate)),
+ oData, PHASE_Oil);
+ populateRateCombo(qobject_cast(
+ m_pTableWidget->cellWidget(row, ColumnGasRate)),
+ oData, PHASE_Gas);
+ populateRateCombo(qobject_cast(
+ m_pTableWidget->cellWidget(row, ColumnWaterRate)),
+ oData, PHASE_Water);
+ populatePressureCombo(qobject_cast(
+ m_pTableWidget->cellWidget(row, ColumnPressure)), oData);
+ populateFlowSegmentCombo(qobject_cast(
+ m_pTableWidget->cellWidget(row, ColumnFlowSegment)), oData);
+ updateIncludeCheckBox(row);
+}
- for(int col = 2; col < 6; col++) {
- QTableWidgetItem* item = new QTableWidgetItem(listOtherData[col-2]);
- item->setFlags(item->flags() & ~Qt::ItemIsEditable);
- item->setData(Qt::UserRole, row);
- item->setTextAlignment(Qt::AlignCenter);
- m_pTableWidget->setItem(row, col, item);
+void nmWxIncludeOtherWells::updateIncludeCheckBox(int row)
+{
+ if(row < 0 || row >= m_vecWellData.size()) {
+ return;
+ }
+ WellDataRow& oData = m_vecWellData[row];
+ if(!oData.canCalculate()) {
+ oData.m_bIsIncluded = false;
+ }
+ QWidget* pContainer = m_pTableWidget->cellWidget(row, ColumnIncluded);
+ QCheckBox* pCheckBox = pContainer == nullptr
+ ? nullptr : pContainer->findChild();
+ if(pCheckBox == nullptr) {
+ return;
}
+ const bool bSignalsWereBlocked = pCheckBox->blockSignals(true);
+ pCheckBox->setEnabled(oData.m_bCanCalculate);
+ pCheckBox->setChecked(oData.m_bIsIncluded);
+ pCheckBox->setToolTip(oData.canCalculate()
+ ? QString() : tr("Select at least one production record first."));
+ pCheckBox->blockSignals(bSignalsWereBlocked);
}
void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state)
@@ -221,25 +643,91 @@ void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state)
if(pCheckBox == nullptr) {
return;
}
-
const int nRow = pCheckBox->property("row").toInt();
if(nRow < 0 || nRow >= m_vecWellData.size()) {
return;
}
- // 主井正常不会进入本表;保留该判断作为防御,避免未来改动误取消主井。
- if(m_vecWellData[nRow].m_bIsPrimary) {
- m_vecWellData[nRow].m_bIsIncluded = true;
+ WellDataRow& oData = m_vecWellData[nRow];
+ if(oData.m_bIsPrimary ||
+ (state == Qt::Checked && !oData.canCalculate())) {
+ oData.m_bIsIncluded = false;
+ updateIncludeCheckBox(nRow);
+ return;
+ }
+ oData.m_bIsIncluded = state == Qt::Checked;
+ m_pTableWidget->selectRow(nRow);
+}
+
+void nmWxIncludeOtherWells::onRateSourceChanged(int nIndex)
+{
+ QComboBox* pComboBox = qobject_cast(sender());
+ if(pComboBox == nullptr || nIndex < 0) {
+ return;
+ }
+ const int nRow = pComboBox->property("row").toInt();
+ const NM_PHASE_TYPE ePhase = static_cast(
+ pComboBox->property("phase").toInt());
+ if(nRow < 0 || nRow >= m_vecWellData.size()) {
return;
}
- // 其他候选井只记录选择状态,绝不在该对话框内创建或删除 Map 井。
- m_vecWellData[nRow].m_bIsIncluded = (state == Qt::Checked);
+ WellDataRow& oData = m_vecWellData[nRow];
+ // 可见文本只用于展示,真正写回始终读取 itemData 中的 Gauge Code。
+ const QString sGaugeCode = pComboBox->itemData(nIndex).toString();
+ if(sGaugeCode.isEmpty()) {
+ setRowPhaseUsed(oData, ePhase, false);
+ } else {
+ const nmFlowGaugeRecord* pRecord = findFlowRecord(
+ oData.m_vecFlowRecords, sGaugeCode);
+ if(pRecord == nullptr || !isSelectableFlowPhase(*pRecord, ePhase)) {
+ refreshRowEditors(nRow);
+ return;
+ }
+ if(oData.m_sFlowGaugeCode != sGaugeCode) {
+ oData.m_sFlowGaugeCode = sGaugeCode;
+ oData.m_nFlowSegmentIndex = pRecord->nIndexF;
+ }
+ setRowPhaseUsed(oData, ePhase, true);
+ }
+
+ // PEBI 每口井只有一条 Rate.t。切换任一相的记录后,其他已启用相
+ // 自动使用同一记录;新记录没有的相自动回到“无”。
+ normalizeFlowSelection(oData);
+ refreshRowEditors(nRow);
+ m_pTableWidget->selectRow(nRow);
+}
+
+void nmWxIncludeOtherWells::onPressureSourceChanged(int nIndex)
+{
+ QComboBox* pComboBox = qobject_cast(sender());
+ if(pComboBox == nullptr || nIndex < 0) {
+ return;
+ }
+ const int nRow = pComboBox->property("row").toInt();
+ if(nRow < 0 || nRow >= m_vecWellData.size()) {
+ return;
+ }
+ m_vecWellData[nRow].m_sPressureGaugeCode =
+ pComboBox->itemData(nIndex).toString();
+ pComboBox->setToolTip(pComboBox->currentText());
m_pTableWidget->selectRow(nRow);
}
-void nmWxIncludeOtherWells::onItemClicked(QTableWidgetItem* item)
+void nmWxIncludeOtherWells::onFlowSegmentChanged(int nIndex)
{
+ QComboBox* pComboBox = qobject_cast(sender());
+ if(pComboBox == nullptr || nIndex < 0) {
+ return;
+ }
+ const int nRow = pComboBox->property("row").toInt();
+ if(nRow < 0 || nRow >= m_vecWellData.size()) {
+ return;
+ }
+ m_vecWellData[nRow].m_nFlowSegmentIndex =
+ pComboBox->itemData(nIndex).toInt();
+ pComboBox->setToolTip(pComboBox->currentText());
+ m_pTableWidget->selectRow(nRow);
}
QVector nmWxIncludeOtherWells::getWellData() const
@@ -249,24 +737,20 @@ QVector nmWxIncludeOtherWells::getWellData() const
void nmWxIncludeOtherWells::setupButtons()
{
- // 创建确定和取消按钮
m_pOkButton = new QPushButton(tr("OK"), this);
m_pCancelButton = new QPushButton(tr("Cancel"), this);
}
void nmWxIncludeOtherWells::setupLayouts()
{
- // 创建按钮布局并添加按钮
m_pButtonLayout = new QHBoxLayout();
- m_pButtonLayout->addStretch(); // 添加伸缩项使按钮靠右
+ m_pButtonLayout->addStretch();
m_pButtonLayout->addWidget(m_pOkButton);
m_pButtonLayout->addWidget(m_pCancelButton);
- // 创建主布局并添加控件
m_pMainLayout = new QVBoxLayout(this);
m_pMainLayout->addWidget(m_pTableWidget);
m_pMainLayout->addLayout(m_pButtonLayout);
-
setLayout(m_pMainLayout);
}
@@ -274,6 +758,4 @@ void nmWxIncludeOtherWells::setupConnections()
{
connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(reject()));
- connect(m_pTableWidget, SIGNAL(itemClicked(QTableWidgetItem*)),
- this, SLOT(onItemClicked(QTableWidgetItem*)));
}
diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp
index 3e781e4..108f826 100644
--- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp
@@ -64,6 +64,17 @@ bool isSupportedAdditionalWell(nmDataWellBase* pWellData)
eWellType == Horizontal_Fractured_Well;
}
+// 可配置性看框架记录本身,不能看当前是否已把三个相都设为“无”,
+// 否则用户清空分相后将无法再次打开对话框恢复选择。
+bool hasAvailableRateTarget(nmDataWellBase* pWellData)
+{
+ if(pWellData == nullptr ||
+ !nmIsValidWellCategory(pWellData->getWellCategory())) {
+ return false;
+ }
+ return pWellData->hasAvailableFlowRate();
+}
+
}
nmWxNumericalDesign* nmWxNumericalDesign::s_pCurrentInstance = nullptr;
@@ -1080,51 +1091,88 @@ void nmWxNumericalDesign::onOptionsIconClicked()
if(dlg.exec() == QDialog::Accepted) {
QVector vecWellRows = dlg.getWellData();
QVector vecIncludedWells;
+ bool bIncludedMembershipChanged = false;
+ bool bIncludedCurveSelectionChanged = false;
- // 第一步:这里只收集 Map 中被勾选的有产量干扰井。主井由分析方案
- // 单独保存;无产量观察井由数据管理层自动加入,与本对话框无关。
+ // 第一步:先把每一行的压力、公共流量记录、分相开关和流动段写回。
+ // 这些状态属于井本身;未勾选井也保留配置,方便下次直接启用。
for(int nRowIndex = 0; nRowIndex < vecWellRows.size(); ++nRowIndex) {
const WellDataRow& oWellRow = vecWellRows[nRowIndex];
- if(oWellRow.m_bIsPrimary ||
- !oWellRow.m_bIsIncluded ||
- !oWellRow.canCalculate()) {
+ if(oWellRow.m_bIsPrimary) {
continue;
}
nmDataWellBase* pWellData =
m_pDataManager->findWellByCode(oWellRow.m_sWellCode);
if(pWellData == nullptr) {
- // Map 中已经移除的井不能被 Include 对话框重新创建。
continue;
}
- if(!nmIsValidWellCategory(pWellData->getWellCategory()) ||
- pWellData->getFlowSegmentCount() <= 0) {
- // 对话框打开后若产量数据发生变化,提交时仍要阻止它混入包含列表。
- continue;
- }
-
- vecIncludedWells.append(
- nmCalculationWellRef(oWellRow.m_sWellCode,
- NM_CaseWell_RateControlled));
-
- // 第二步:为被包含的有产量井同步历史压力、双对数和半对数数据。
- QVector > vecHistoryPressure;
- QVector > vecHistoryLogLog;
- QVector > vecHistorySemiLog;
- m_pDataManager->calculationLogData(pWellData,
- vecHistoryPressure,
- vecHistoryLogLog,
- vecHistorySemiLog);
- pWellData->setHistoryPressure(vecHistoryPressure);
- pWellData->setHistoryLogLog(vecHistoryLogLog);
- pWellData->setHistorySemiLog(vecHistorySemiLog);
+ 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;
+
+ // 先切换公共流量记录,再写入分相开关和该记录的一基流动段索引。
+ // 这样 switchSelectedFlowGaugeCode() 可以先保存旧记录自己的段索引。
+ pWellData->selectPressureGaugeCode(
+ oWellRow.m_sPressureGaugeCode);
+ if(pWellData->getSelectedFlowGaugeCode() !=
+ oWellRow.m_sFlowGaugeCode) {
+ pWellData->switchSelectedFlowGaugeCode(
+ oWellRow.m_sFlowGaugeCode);
+ }
+ pWellData->setUseOilRate(oWellRow.m_bUseOilRate);
+ pWellData->setUseGasRate(oWellRow.m_bUseGasRate);
+ pWellData->setUseWaterRate(oWellRow.m_bUseWaterRate);
+ pWellData->setIndexF(oWellRow.m_nFlowSegmentIndex);
+
+ if(bCurveSelectionChanged) {
+ QVector > vecHistoryPressure;
+ QVector > vecHistoryLogLog;
+ QVector > vecHistorySemiLog;
+ m_pDataManager->calculationLogData(pWellData,
+ vecHistoryPressure,
+ vecHistoryLogLog,
+ vecHistorySemiLog);
+ pWellData->setHistoryPressure(vecHistoryPressure);
+ pWellData->setHistoryLogLog(vecHistoryLogLog);
+ pWellData->setHistorySemiLog(vecHistorySemiLog);
+ 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();
@@ -1215,9 +1263,7 @@ void nmWxNumericalDesign::updateIncludeOtherWellsAvailability()
continue;
}
- const bool bHasRateTarget =
- nmIsValidWellCategory(pWellData->getWellCategory()) &&
- pWellData->getFlowSegmentCount() > 0;
+ const bool bHasRateTarget = hasAvailableRateTarget(pWellData);
if(bHasRateTarget) {
bHasEligibleOtherWell = true;
break;