Compare commits

...

5 Commits

Author SHA1 Message Date
lh d0085bd0a8 feat(nmSubWxs): 在储层特性中接入井储计算器
- 在井筒储集分组首行增加井储计算入口并与参数列对齐
- 将计算结果回填当前选中井的井筒储集系数
- 通过编辑会话统一提交,取消对话框时不修改实时井数据
- 补齐中文翻译并更新翻译运行文件
20 hours ago
lh 297775ebd0 fix(nmNum): 修复 PEBI 参数修改后的地图与网格刷新
- 增加属性实际编辑通知,避免相同值重复推进计算输入版本
- 区分网格输入与仅求解输入,按参数类型失效网格或结果
- 监听井数据变化并刷新 Map,补齐井对象替换后的信号重绑定
- GridControl 变化后通知网格窗口,并同步最新控制值
- 修复井图元刷新接口访问权限导致的编译错误
20 hours ago
lh 4542d1132a fix(nmNum): 冻结结果井流量制度并同步刷新历史曲线
- 在求解输入和结果快照中保存公共段时长、三相流量、分相启用状态、制度参考相及流动段索引
- 求解完成后将实际使用的流量制度写入结果快照,避免结果切换依赖实时井数据
- 切换结果井时同步刷新历史压力、计算压力、参考相流量曲线和流动段高亮
- 多相流量完整保留给求解器,历史窗口按求解时的制度参考相显示单条流量曲线
- 将 Snapshot.json 格式升级至 v4,不再兼容旧格式结果快照
21 hours ago
lh 768632b546 fix(nmSubWxs): 修复时变表皮图标和中文显示
- 移除图表模式按钮硬编码的绝对图标路径
- 改为从程序资源目录加载筛选及重置图标
- 补齐时变表皮窗口缺失的中文翻译
- 更新中文翻译运行文件
2 days ago
lh 10c4ce8583 Merge branch 'feature/nmNum-multi-gauge-record' into develop 2 days ago

Binary file not shown.

@ -6494,7 +6494,7 @@ Average pressure in contour: %2 MPa</source>
</message>
<message>
<source>Pressure</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>hr</source>
@ -6518,27 +6518,27 @@ Average pressure in contour: %2 MPa</source>
</message>
<message>
<source>Date Time</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Filter</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Reset Filter</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Reset Lines</source>
<translation type="unfinished"></translation>
<translation>线</translation>
</message>
<message>
<source>Skin0:</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>dSkin/dq:</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>dS/dQ</source>
@ -7284,6 +7284,7 @@ Average pressure in contour: %2 MPa</source>
<name>nmReservoirParameterPage</name>
<message><source>Object display</source><translation></translation></message>
<message><source>Display:</source><translation></translation></message>
<message><source>Storage calculator</source><translation></translation></message>
<message><source>No editable objects in this category.</source><translation></translation></message>
<message><source>Initial oil and water saturations must sum to 1.</source><translation> 1</translation></message>
</context>

@ -34,6 +34,9 @@ struct nmPebiWellInputSnapshot
m_dWellboreStorage(0.0),
m_dSkin(0.0),
m_dDfc(0.0),
m_bUseOilRate(false),
m_bUseGasRate(false),
m_bUseWaterRate(false),
m_bRateControlled(false),
m_bRealWell(false),
m_bHasPerforation(false),
@ -64,6 +67,9 @@ struct nmPebiWellInputSnapshot
double m_dWellboreStorage; ///< 求解时井筒储集系数。
double m_dSkin; ///< 求解时表皮系数。
double m_dDfc; ///< 求解时裂缝导流能力。
bool m_bUseOilRate; ///< 求解时是否启用油相流量。
bool m_bUseGasRate; ///< 求解时是否启用气相流量。
bool m_bUseWaterRate; ///< 求解时是否启用水相流量。
bool m_bRateControlled; ///< 是否提供源汇项。
bool m_bRealWell; ///< 是否为真实井而非手工裂缝槽位。
bool m_bHasPerforation; ///< 求解时是否存在射孔。

@ -55,6 +55,10 @@ signals:
/// @brief Data→UI 方向:属性变更通知,携带 name 和新值
void sigAttrChanged(QString name, QVariant value);
/// @brief UI 已实际修改底层属性,供数据管理器推进网格或结果输入版本
/// @note 与 sigAttrChanged 分开,避免外部数据刷新 UI 时被误判为用户编辑
void sigAttrEdited(QString name, QVariant value);
private slots:
/// @brief 监听 nmDataAttribute::sigValueChanged用 sender() 反查 name

@ -26,6 +26,7 @@
#include <QList>
#include <QMutex>
#include <QSharedPointer>
#include <QVariant>
#include <QWaitCondition>
#include <vector>
@ -832,6 +833,10 @@ public:
/** @brief 并行结果快照或其结果井选择已经改变。 */
void sigPebiResultSnapshotChanged();
private slots:
/** @brief 根据参数面板实际编辑的属性类型,使对应 PEBI 输入版本失效。 */
void onRegisteredAttributeEdited(QString sName, QVariant oValue);
private:
/** @brief 按 UUID 给当前实时井统一绑定或解除最后结果快照弱引用。 */
void rebindPebiResultSnapshotToWells();

@ -78,6 +78,24 @@ struct NM_DATA_EXPORT nmPebiResultWellSnapshot
NM_CASE_WELL_MODE m_eWellMode;
/** @brief 求解时冻结的规范化 Gauge 输入签名。 */
QString m_sGaugeInputSha1;
/** @brief 求解时冻结的公共流动段时长,不包含框架占位行。 */
QVector<double> m_vecFlowDurations;
/** @brief 与公共流动段逐项对应的油相流量。 */
QVector<double> m_vecOilRates;
/** @brief 与公共流动段逐项对应的气相流量。 */
QVector<double> m_vecGasRates;
/** @brief 与公共流动段逐项对应的水相流量。 */
QVector<double> m_vecWaterRates;
/** @brief 历史流量图及旧单相后处理使用的制度参考相。 */
NM_PHASE_TYPE m_eFlowSchedulePhase;
/** @brief 求解时冻结的一基流动段索引。 */
int m_nFlowSectionIndex;
/** @brief 求解时是否启用油相流量。 */
bool m_bUseOilRate;
/** @brief 求解时是否启用气相流量。 */
bool m_bUseGasRate;
/** @brief 求解时是否启用水相流量。 */
bool m_bUseWaterRate;
/** @brief 求解时冻结的井平面位置。 */
QPointF m_oLocation;
/** @brief 求解时是否存在射孔数据。 */

@ -187,7 +187,7 @@ public:
// 同步井图元可见性到井数据
// 更新井图元的数据映射(编辑井后更换数据对象时使用)
void updateWellDataMapping(nmObjPointWell* pGraphic, nmDataWellBase* pNewData) { m_mapWellData[pGraphic] = pNewData; }
void updateWellDataMapping(nmObjPointWell* pGraphic, nmDataWellBase* pNewData);
// 井相关数据中心委托方法
bool hasLayers() const;
@ -242,6 +242,7 @@ private slots:
// 井图元信号 → 数据同步
void onGraphicWellVisibleChanged(bool bVisible);
void onWellDataChanged();
private:
nmDataAnalyzeManager* m_pDataMgr; // 数据中心,不持有,外部注入

@ -240,7 +240,7 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
// 填充已经存在的数值三维和结果参数页面。
bool populateResultWidgets(iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager);
/** @brief 用快照中冻结的历史和计算曲线刷新三个结果窗口。 */
/** @brief 用快照中冻结的压力、流量和计算曲线刷新三个结果窗口。 */
bool refreshPebiResultCurves(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager,

@ -75,6 +75,9 @@ private slots:
/** @brief 枚举code变化后保存并执行集中联动。 */
void slotEnumIndexChanged(int nIndex);
/** @brief 计算并回填当前井的井筒储集系数。 */
void slotWellboreStorageCalculator();
private:
/** @brief 创建对象显示控制区和单层参数分组区域。 */
void createUi();

@ -2015,6 +2015,24 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate()
? NM_CaseWell_RateControlled
: NM_CaseWell_Observation;
oWell.m_sGaugeInputSha1 = oWellInput.m_sGaugeInputSha1;
if(oWellInput.m_bRateControlled) {
// 结果切井必须显示本轮求解实际使用的制度,不能重新读取实时井。
oWell.m_eFlowSchedulePhase =
oWellInput.m_eFlowSchedulePhase;
oWell.m_nFlowSectionIndex =
oWellInput.m_nFlowSectionIndex;
oWell.m_bUseOilRate = oWellInput.m_bUseOilRate;
oWell.m_bUseGasRate = oWellInput.m_bUseGasRate;
oWell.m_bUseWaterRate = oWellInput.m_bUseWaterRate;
qSwap(oWell.m_vecFlowDurations,
oWellInput.m_vecFlowDurations);
qSwap(oWell.m_vecOilRates,
oWellInput.m_vecOilRates);
qSwap(oWell.m_vecGasRates,
oWellInput.m_vecGasRates);
qSwap(oWell.m_vecWaterRates,
oWellInput.m_vecWaterRates);
}
oWell.m_oLocation = oWellInput.m_oLocation;
oWell.m_bHasPerforation = oWellInput.m_bHasPerforation;
oWell.m_bHasSkin = oWellInput.m_bHasPerforation;

@ -216,6 +216,10 @@ bool captureWellFlowSchedule(
oWellInput.m_vecOilRates.clear();
oWellInput.m_vecGasRates.clear();
oWellInput.m_vecWaterRates.clear();
oWellInput.m_eFlowSchedulePhase = PHASE_UNKNOWN;
oWellInput.m_bUseOilRate = false;
oWellInput.m_bUseGasRate = false;
oWellInput.m_bUseWaterRate = false;
if(!oGaugeInput.hasAlignedFlowSchedule()) {
return false;
}
@ -224,6 +228,9 @@ bool captureWellFlowSchedule(
oWellInput.m_vecOilRates = oGaugeInput.vecOilRates;
oWellInput.m_vecGasRates = oGaugeInput.vecGasRates;
oWellInput.m_vecWaterRates = oGaugeInput.vecWaterRates;
oWellInput.m_bUseOilRate = oGaugeInput.bUseOilRate;
oWellInput.m_bUseGasRate = oGaugeInput.bUseGasRate;
oWellInput.m_bUseWaterRate = oGaugeInput.bUseWaterRate;
return true;
}

@ -126,10 +126,22 @@ void nmAttrRegistry::setValue(QString name, QVariant value)
return;
}
// 控件重建或失去焦点时可能重复提交相同值;此类提交不能推进计算输入版本。
const QVariant oOldValue = pAttr->getValue();
if (oOldValue == value) {
return;
}
m_bSyncing = true;
pAttr->setValue(value); // → nmDataAttribute 发 sigValueChanged
// → onAttrValueChanged 检测 m_bSyncing=true → 跳过 sigAttrChanged
m_bSyncing = false;
// 属性已完成写入后再通知数据管理器,保证监听者读取到的是最终值。
const QVariant oNewValue = pAttr->getValue();
if (oOldValue != oNewValue) {
emit sigAttrEdited(name, oNewValue);
}
}
void nmAttrRegistry::onAttrValueChanged()

@ -607,6 +607,39 @@ static bool isGeometryParaAttrName(const QString& name)
return false;
}
// 参数注册名可能带对象编码前缀,判断时只匹配完整的基础参数后缀。
static bool hasRegisteredAttributeBaseName(
const QString& sName,
const char* pBaseName)
{
const QString sBaseName = QString::fromLatin1(pBaseName);
return sName == sBaseName || sName.endsWith(QString("_") + sBaseName);
}
// 这里只区分网格输入和仅求解输入,不在界面层重新解释 PEBI 业务规则。
static bool isPebiGridInputAttributeName(const QString& sName)
{
static const char* GRID_INPUT_NAMES[] = {
"W_X", "W_Y", "W_Rw",
"W_FractureHalfLength", "W_FractureAngle", "W_Dfc",
"W_WellLength", "W_DrainAngle", "W_NumberOfFractures",
"F_X0", "F_Y0", "F_X1", "F_Y1",
"FT_X0", "FT_Y0", "FT_X1", "FT_Y1",
"BR_XMin", "BR_YMin", "BR_XMax", "BR_YMax",
"BC_CenterX", "BC_CenterY", "BC_Radius",
"BP_X", "BP_Y"
};
const int nNameCount = sizeof(GRID_INPUT_NAMES) /
sizeof(GRID_INPUT_NAMES[0]);
for(int nIndex = 0; nIndex < nNameCount; ++nIndex) {
if(hasRegisteredAttributeBaseName(sName,
GRID_INPUT_NAMES[nIndex])) {
return true;
}
}
return false;
}
namespace {
// 清空指定目录下的所有旧文件和子目录,但保留目录本身
bool clearDirectoryContents(const QString& dirPath)
@ -1329,6 +1362,8 @@ ZX_DEFINE_DYNAMIC(DataAnalyzeManager, nmDataAnalyzeManager)
// 属性注册表
m_pAttrRegistry = new nmAttrRegistry();
connect(m_pAttrRegistry, SIGNAL(sigAttrEdited(QString,QVariant)),
this, SLOT(onRegisteredAttributeEdited(QString,QVariant)));
m_backgroundImageInfo.bIsVisible = false;
// 初始化混合参数数据(Temp)
@ -4308,7 +4343,14 @@ bool nmDataAnalyzeManager::getIncludeOtherWells() const
void nmDataAnalyzeManager::setPebiGridControl(double dGridControl)
{
const quint64 nOldGridInputRevision =
m_oNumericalAnalysisCase.getGridInputRevision();
m_oNumericalAnalysisCase.setPebiGridControl(dGridControl);
if(nOldGridInputRevision !=
m_oNumericalAnalysisCase.getGridInputRevision()) {
// 分析方案已完成网格和结果失效,这里只通知已打开的网格窗口刷新。
emit dataChanged();
}
}
double nmDataAnalyzeManager::getPebiGridControl() const
@ -4811,13 +4853,29 @@ nmDataWellBase* nmDataAnalyzeManager::getCurWellData()
void nmDataAnalyzeManager::notifyDataChanged()
{
// 现有属性编辑链路尚未细分“几何变化”和“仅求解输入变化”,
// 为保证正确性先采用保守策略:任一计算数据变化都使网格失效
// 本入口用于几何、井集合等网格输入;仅影响求解的参数应调用
// invalidatePebiResults(),避免无意义地重建当前有效网格
m_oNumericalAnalysisCase.invalidateGrid();
emit dataChanged();
}
void nmDataAnalyzeManager::onRegisteredAttributeEdited(
QString sName,
QVariant oValue)
{
Q_UNUSED(oValue);
if(isPebiGridInputAttributeName(sName)) {
// 几何及离散参数变化后Map 使用数据对象重绘,网格窗口按新版本自动重建。
notifyDataChanged();
return;
}
// 其余实时注册参数均属于求解输入,只使结果过期,保留当前有效网格。
invalidatePebiResults();
}
void nmDataAnalyzeManager::notifyGeometryListChanged()
{
m_oNumericalAnalysisCase.invalidateGrid();

@ -19,6 +19,11 @@ nmPebiResultWellSnapshot::nmPebiResultWellSnapshot()
: m_eWellType(Unknow_Well),
m_eWellCategory(NM_WellCategory_Unknown),
m_eWellMode(NM_CaseWell_Observation),
m_eFlowSchedulePhase(PHASE_UNKNOWN),
m_nFlowSectionIndex(0),
m_bUseOilRate(false),
m_bUseGasRate(false),
m_bUseWaterRate(false),
m_bHasPerforation(false),
m_bHasSkin(false),
m_bHasDfc(false),

@ -20,7 +20,7 @@
namespace {
// Builder 只校验内存模型的完整性;持久化文件的数量上限和字节上限
// 由 v3 加载器在分配 VTK/Qt 容器之前单独检查。
// 由当前成果加载器在分配 VTK/Qt 容器之前单独检查。
bool isFiniteValue(double dValue)
{
#if defined(_MSC_VER)
@ -128,6 +128,45 @@ bool areWellCurvesValid(const nmPebiResultWellCurves& oCurves)
isCurveValid(oCurves.m_vecResultSemiLog, 2, false);
}
bool isWellFlowScheduleValid(const nmPebiResultWellSnapshot& oWell)
{
// 观察井不向求解器提供源汇项,结果快照中也不伪造流量制度。
if(oWell.m_eWellMode == NM_CaseWell_Observation)
{
return oWell.m_vecFlowDurations.isEmpty() &&
oWell.m_vecOilRates.isEmpty() &&
oWell.m_vecGasRates.isEmpty() &&
oWell.m_vecWaterRates.isEmpty() &&
oWell.m_eFlowSchedulePhase == PHASE_UNKNOWN &&
oWell.m_nFlowSectionIndex == 0 &&
!oWell.m_bUseOilRate && !oWell.m_bUseGasRate &&
!oWell.m_bUseWaterRate;
}
const int nSegmentCount = oWell.m_vecFlowDurations.size();
if(nSegmentCount <= 0 ||
oWell.m_vecOilRates.size() != nSegmentCount ||
oWell.m_vecGasRates.size() != nSegmentCount ||
oWell.m_vecWaterRates.size() != nSegmentCount ||
!isFiniteVector(oWell.m_vecFlowDurations) ||
!isFiniteVector(oWell.m_vecOilRates) ||
!isFiniteVector(oWell.m_vecGasRates) ||
!isFiniteVector(oWell.m_vecWaterRates) ||
oWell.m_nFlowSectionIndex < 1 ||
oWell.m_nFlowSectionIndex > nSegmentCount)
{
return false;
}
// 参考相必须是本次求解实际启用的相,历史流量图才能与对数曲线一致。
return (oWell.m_eFlowSchedulePhase == PHASE_Oil &&
oWell.m_bUseOilRate) ||
(oWell.m_eFlowSchedulePhase == PHASE_Gas &&
oWell.m_bUseGasRate) ||
(oWell.m_eFlowSchedulePhase == PHASE_Water &&
oWell.m_bUseWaterRate);
}
bool areReservoirParametersFinite(
const nmPebiResultReservoirParameters& oParameters)
{
@ -541,9 +580,18 @@ bool nmPebiResultSnapshotBuilder::takeWell(
qSwap(oTarget.m_sWellCode, oWell.m_sWellCode);
qSwap(oTarget.m_sWellName, oWell.m_sWellName);
qSwap(oTarget.m_sGaugeInputSha1, oWell.m_sGaugeInputSha1);
qSwap(oTarget.m_vecFlowDurations, oWell.m_vecFlowDurations);
qSwap(oTarget.m_vecOilRates, oWell.m_vecOilRates);
qSwap(oTarget.m_vecGasRates, oWell.m_vecGasRates);
qSwap(oTarget.m_vecWaterRates, oWell.m_vecWaterRates);
oTarget.m_eWellType = oWell.m_eWellType;
oTarget.m_eWellCategory = oWell.m_eWellCategory;
oTarget.m_eWellMode = oWell.m_eWellMode;
oTarget.m_eFlowSchedulePhase = oWell.m_eFlowSchedulePhase;
oTarget.m_nFlowSectionIndex = oWell.m_nFlowSectionIndex;
oTarget.m_bUseOilRate = oWell.m_bUseOilRate;
oTarget.m_bUseGasRate = oWell.m_bUseGasRate;
oTarget.m_bUseWaterRate = oWell.m_bUseWaterRate;
oTarget.m_oLocation = oWell.m_oLocation;
oTarget.m_bHasPerforation = oWell.m_bHasPerforation;
oTarget.m_bHasSkin = oWell.m_bHasSkin;
@ -569,6 +617,11 @@ bool nmPebiResultSnapshotBuilder::takeWell(
oWell.m_eWellType = Unknow_Well;
oWell.m_eWellCategory = NM_WellCategory_Unknown;
oWell.m_eWellMode = NM_CaseWell_Observation;
oWell.m_eFlowSchedulePhase = PHASE_UNKNOWN;
oWell.m_nFlowSectionIndex = 0;
oWell.m_bUseOilRate = false;
oWell.m_bUseGasRate = false;
oWell.m_bUseWaterRate = false;
oWell.m_oLocation = QPointF();
oWell.m_bHasPerforation = false;
oWell.m_bHasSkin = false;
@ -762,6 +815,11 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
return fail(QString("PEBI result well curves are invalid: %1")
.arg(oWell.m_sWellCode), pError);
}
if(!isWellFlowScheduleValid(oWell))
{
return fail(QString("PEBI result well flow schedule is invalid: %1")
.arg(oWell.m_sWellCode), pError);
}
setWellIds.insert(oWell.m_sWellInstanceId);
setWellCodes.insert(oWell.m_sWellCode);
}
@ -815,7 +873,11 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
{
const QString& sWellInstanceId =
m_pCandidate->m_listDisplayWellInstanceIds[nIndex];
const nmPebiResultWellSnapshot* pDisplayWell =
m_pCandidate->findWell(sWellInstanceId);
if(!setWellIds.contains(sWellInstanceId) ||
pDisplayWell == NULL ||
pDisplayWell->m_eWellMode != NM_CaseWell_RateControlled ||
setDisplayWellIds.contains(sWellInstanceId))
{
return fail("PEBI display well list is invalid.", pError);

@ -27,6 +27,7 @@ namespace {
const char g_aPressureMagic[8] = { 'N', 'M', 'P', 'R', 'E', 'S', '3', 0 };
const char g_aCurvesMagic[8] = { 'N', 'M', 'C', 'U', 'R', 'V', '3', 0 };
const quint32 g_nBinaryFormatVersion = 1;
const int g_nSnapshotFormatVersion = 4;
const quint32 g_nEndianMarker = 0x01020304u;
const quint64 g_nMaximumCellCount = 50000000ull;
const quint32 g_nMaximumFrameCount = 10000u;
@ -533,7 +534,8 @@ bool parseSnapshotJson(
if(!oDocument.IsObject() ||
!oDocument.HasMember("SnapshotFormatVersion") ||
!oDocument["SnapshotFormatVersion"].IsInt() ||
oDocument["SnapshotFormatVersion"].GetInt() != 3 ||
oDocument["SnapshotFormatVersion"].GetInt() !=
g_nSnapshotFormatVersion ||
!oDocument.HasMember("ResultCellCount") ||
!oDocument["ResultCellCount"].IsUint64() ||
!oDocument.HasMember("PressureFrameCount") ||
@ -700,8 +702,9 @@ bool nmPebiResultSnapshotSerializer::save(
oDocument.SetObject();
rapidjson::Document::AllocatorType& oAllocator =
oDocument.GetAllocator();
// v3 强制保存每口结果井的 Gauge 输入签名。
oDocument.AddMember("SnapshotFormatVersion", 3, oAllocator);
// v4 在井目录中增加求解时冻结的完整流量制度,不兼容旧快照。
oDocument.AddMember("SnapshotFormatVersion",
g_nSnapshotFormatVersion, oAllocator);
oDocument.AddMember("SnapshotId",
toJsonString(pSnapshot->m_sSnapshotId, oAllocator), oAllocator);
oDocument.AddMember("GridInputRevision",
@ -758,6 +761,21 @@ bool nmPebiResultSnapshotSerializer::save(
oJson.AddMember("WellMode", static_cast<int>(oWell.m_eWellMode), oAllocator);
oJson.AddMember("GaugeInputSha1", toJsonString(
oWell.m_sGaugeInputSha1, oAllocator), oAllocator);
oJson.AddMember("FlowSchedulePhase", static_cast<int>(
oWell.m_eFlowSchedulePhase), oAllocator);
oJson.AddMember("FlowSectionIndex",
oWell.m_nFlowSectionIndex, oAllocator);
oJson.AddMember("UseOilRate", oWell.m_bUseOilRate, oAllocator);
oJson.AddMember("UseGasRate", oWell.m_bUseGasRate, oAllocator);
oJson.AddMember("UseWaterRate", oWell.m_bUseWaterRate, oAllocator);
addDoubleVector(oJson, "FlowDurations",
oWell.m_vecFlowDurations, oAllocator);
addDoubleVector(oJson, "OilRates",
oWell.m_vecOilRates, oAllocator);
addDoubleVector(oJson, "GasRates",
oWell.m_vecGasRates, oAllocator);
addDoubleVector(oJson, "WaterRates",
oWell.m_vecWaterRates, oAllocator);
oJson.AddMember("X", oWell.m_oLocation.x(), oAllocator);
oJson.AddMember("Y", oWell.m_oLocation.y(), oAllocator);
oJson.AddMember("HasPerforation", oWell.m_bHasPerforation, oAllocator);
@ -1064,6 +1082,7 @@ bool nmPebiResultSnapshotSerializer::load(
int nWellType = 0;
int nWellCategory = 0;
int nWellMode = 0;
int nFlowSchedulePhase = 0;
if(!oJson.IsObject() ||
!oJson.HasMember("WellInstanceId") ||
!oJson["WellInstanceId"].IsString() ||
@ -1075,6 +1094,24 @@ bool nmPebiResultSnapshotSerializer::load(
!readRequiredInt(oJson, "WellType", nWellType) ||
!readRequiredInt(oJson, "WellCategory", nWellCategory) ||
!readRequiredInt(oJson, "WellMode", nWellMode) ||
!readRequiredInt(oJson, "FlowSchedulePhase",
nFlowSchedulePhase) ||
!readRequiredInt(oJson, "FlowSectionIndex",
oWell.m_nFlowSectionIndex) ||
!readRequiredBool(oJson, "UseOilRate",
oWell.m_bUseOilRate) ||
!readRequiredBool(oJson, "UseGasRate",
oWell.m_bUseGasRate) ||
!readRequiredBool(oJson, "UseWaterRate",
oWell.m_bUseWaterRate) ||
!readDoubleVector(oJson, "FlowDurations",
oWell.m_vecFlowDurations) ||
!readDoubleVector(oJson, "OilRates",
oWell.m_vecOilRates) ||
!readDoubleVector(oJson, "GasRates",
oWell.m_vecGasRates) ||
!readDoubleVector(oJson, "WaterRates",
oWell.m_vecWaterRates) ||
!readRequiredDouble(oJson, "X", oWell.m_oLocation.rx()) ||
!readRequiredDouble(oJson, "Y", oWell.m_oLocation.ry()) ||
!readRequiredBool(oJson, "HasPerforation", oWell.m_bHasPerforation) ||
@ -1099,6 +1136,8 @@ bool nmPebiResultSnapshotSerializer::load(
oWell.m_eWellCategory = static_cast<NM_WELL_CATEGORY>(
nWellCategory);
oWell.m_eWellMode = static_cast<NM_CASE_WELL_MODE>(nWellMode);
oWell.m_eFlowSchedulePhase = static_cast<NM_PHASE_TYPE>(
nFlowSchedulePhase);
if(setJsonWellIds.contains(oWell.m_sWellInstanceId) ||
!mapCurves.contains(oWell.m_sWellInstanceId))
{

@ -1605,6 +1605,20 @@ void nmPlotGraphicBinder::bindWellToGraphic(nmObjPointWell* pGraphic, nmDataWell
_connectWell(pGraphic, pData);
}
void nmPlotGraphicBinder::updateWellDataMapping(
nmObjPointWell* pGraphic,
nmDataWellBase* pNewData)
{
if (!pGraphic || !pNewData) return;
// replaceWellData 已销毁旧井对象,其 QObject 连接会自动解除;这里补绑新对象。
m_mapWellData[pGraphic] = pNewData;
connect(pNewData, SIGNAL(sigWellDataChanged()),
this, SLOT(onWellDataChanged()), Qt::UniqueConnection);
// nmObjPointWell 将 update() 收窄为 protected通过公开基类接口触发虚刷新。
static_cast<nmObjBase*>(pGraphic)->update();
}
// 删除井图元前,先由数据层确认删除成功,再断开信号并清理映射。
bool nmPlotGraphicBinder::removeWellBinding(nmObjPointWell* pGraphic)
{
@ -1929,6 +1943,19 @@ void nmPlotGraphicBinder::onGraphicWellVisibleChanged(bool bVisible)
pData->setPlotVisible(bVisible);
}
void nmPlotGraphicBinder::onWellDataChanged()
{
nmDataWellBase* pData = qobject_cast<nmDataWellBase*>(sender());
if (!pData) return;
// 井图元在 paintBack 中读取实时坐标和裂缝几何,只需触发一次重绘。
nmObjPointWell* pGraphic = m_mapWellData.key(pData, nullptr);
if (pGraphic) {
// 保持与 nmGuiPlot 的现有调用方式一致,避免访问井类的 protected override。
static_cast<nmObjBase*>(pGraphic)->update();
}
}
bool nmPlotGraphicBinder::hasLayers() const
{
if (!m_pDataMgr) return false;
@ -1973,6 +2000,8 @@ void nmPlotGraphicBinder::_connectWell(nmObjPointWell* pGraphic, nmDataWellBase*
connect(pGraphic, SIGNAL(sigObjVisibleChanged(bool)),
this, SLOT(onGraphicWellVisibleChanged(bool)));
connect(pData, SIGNAL(sigWellDataChanged()),
this, SLOT(onWellDataChanged()), Qt::UniqueConnection);
m_setBoundWellGraphics.insert(pGraphic);
}
@ -1985,6 +2014,11 @@ void nmPlotGraphicBinder::_disconnectWell(nmObjPointWell* pGraphic)
disconnect(pGraphic, SIGNAL(sigObjVisibleChanged(bool)),
this, SLOT(onGraphicWellVisibleChanged(bool)));
nmDataWellBase* pData = m_mapWellData.value(pGraphic, nullptr);
if (pData) {
disconnect(pData, SIGNAL(sigWellDataChanged()),
this, SLOT(onWellDataChanged()));
}
m_setBoundWellGraphics.remove(pGraphic);
}

@ -282,6 +282,12 @@ void nmSubWndGrid::onDataManagerChanged()
if(sender() != m_pDataManager.data()) {
return;
}
// GridControl 由分析方案统一持有,刷新前同步,避免窗口把旧局部值写回。
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager != nullptr) {
m_dPebiGridControl = pDataManager->getPebiGridControl();
}
updateGrid();
}

@ -64,6 +64,8 @@
#include "nmWxGeoRefDlg.h"
#include "iSubWndFitting.h"
#include "iGuiPlotPF.h"
#include "ZxObjCurveFlow.h"
#include "iAnalRun.h"
#include <QVector>
@ -99,6 +101,25 @@ namespace
// QPointer会在QObject销毁后自动变空避免保留已经释放的线程指针。
QPointer<nmCalculationDllPebiSolverTask> s_pRunningSolverTask;
QPointer<nmSubWndMain> s_pPendingSolverWindow;
// 历史窗口仍是单流量曲线结构,因此显示与双对数、半对数后处理一致的制度参考相。
const QVector<double>* resultReferenceRatesOf(
const nmPebiResultWellSnapshot* pWell)
{
if(pWell == NULL) {
return NULL;
}
switch(pWell->m_eFlowSchedulePhase) {
case PHASE_Oil:
return pWell->m_bUseOilRate ? &pWell->m_vecOilRates : NULL;
case PHASE_Gas:
return pWell->m_bUseGasRate ? &pWell->m_vecGasRates : NULL;
case PHASE_Water:
return pWell->m_bUseWaterRate ? &pWell->m_vecWaterRates : NULL;
default:
return NULL;
}
}
}
nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) :
@ -2772,6 +2793,51 @@ bool nmSubWndMain::refreshPebiResultCurves(
vecResultPressure, false);
pSubWndFit->adjustFitSubPlotBy(
FSRT_Hist, vecResultPressure, true, &sErrorMessage);
// 压力和流量属于同一口结果井;切井时必须一起替换,避免下图残留上一口井制度。
const QVector<double>* pReferenceRates =
resultReferenceRatesOf(pWell);
iCurveDesc oFlowCurve;
oFlowCurve.m_oCurveType = POT_CurveFlow;
oFlowCurve.m_sCurveName = s_HistorySouce_CurveF;
oFlowCurve.m_bLineVisible = true;
oFlowCurve.m_bPointVisible = false;
oFlowCurve.m_oPen.setColor(Qt::red);
if(pWell != NULL && pReferenceRates != NULL &&
pReferenceRates->size() == pWell->m_vecFlowDurations.size()) {
// 快照只保存真实流量段;绘图对象仍按框架原始格式使用首个
// (0,0) 占位点,一基流动段索引因此可以直接恢复而无需换算。
oFlowCurve.m_vecX = pWell->m_vecFlowDurations;
oFlowCurve.m_vecY = *pReferenceRates;
oFlowCurve.m_vecX.prepend(0.0);
oFlowCurve.m_vecY.prepend(0.0);
}
QVector<iCurveDesc> vecHistoryFlow;
vecHistoryFlow.append(oFlowCurve);
pSubWndFit->adjustFitSubPlotBy(
FSRT_Hist, vecHistoryFlow, true, &sErrorMessage);
// 框架流动段索引与 ZxSegmentInfo 相同,均为一基。这里只更新只读结果图元,
// 阻断选择信号,不能让结果井切换反向修改当前分析井的流动段配置。
QWidget* pHistoryWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Hist, false, &sErrorMessage);
iGuiPlotPF* pHistoryPlot = qobject_cast<iGuiPlotPF*>(pHistoryWidget);
if(pHistoryPlot != NULL) {
ZxObjCurveBase* pFlowObject = NULL;
if(pHistoryPlot->getChartDataObjOfPF(pFlowObject, false) &&
pFlowObject != NULL) {
ZxObjCurveFlow* pFlowCurve =
dynamic_cast<ZxObjCurveFlow*>(pFlowObject);
if(pFlowCurve != NULL) {
const bool bSignalsBlocked = pFlowCurve->blockSignals(true);
pFlowCurve->setCurSegPtIndex(
pWell == NULL ? 0 :
pWell->m_nFlowSectionIndex);
pFlowCurve->blockSignals(bSignalsBlocked);
pHistoryPlot->runUpdate();
}
}
}
return true;
}

@ -2,13 +2,18 @@
#include "nmParameterField.h"
#include "nmReservoirPropertiesSession.h"
#include "nmWxWellboreStorageCalculator.h"
#include <QComboBox>
#include <QCoreApplication>
#include <QDialog>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QSizePolicy>
#include <QToolButton>
#include <QVBoxLayout>
#include <QtGlobal>
@ -261,6 +266,57 @@ bool nmReservoirParameterPage::buildGroup(
nFieldColumnOffset + 2,
nmParameterField::unitMinimumWidth());
bool bHasWellboreStorage = false;
for (int nFieldIndex = 0;
nFieldIndex < oGroupInfo.m_listFields.size();
++nFieldIndex)
{
if (oGroupInfo.m_listFields[nFieldIndex].m_sBaseParameterId ==
"W_C")
{
bHasWellboreStorage = true;
break;
}
}
int nFirstFieldRow = 0;
if (m_eCategory == NM_RESERVOIR_PAGE_WELL &&
bHasWellboreStorage)
{
// 计算器单独占用分组首行,标签和按钮分别对齐
// 下方参数的名称列右边界和数值列左边界。
QLabel* pCalculatorLabel = new QLabel(
tr("Storage calculator"), pGroupBox);
pCalculatorLabel->setAlignment(
Qt::AlignRight | Qt::AlignVCenter);
pCalculatorLabel->setWordWrap(false);
QToolButton* pCalculatorButton =
new QToolButton(pGroupBox);
QString sIconDir = QCoreApplication::applicationDirPath()
.section('/', 0, -2) + "/Res/Icon/";
pCalculatorButton->setIcon(QIcon(
sIconDir + "NmWellboreStorageCalculator.png"));
pCalculatorButton->setIconSize(QSize(20, 20));
pCalculatorButton->setFixedSize(26, 26);
pCalculatorButton->setAutoRaise(true);
pCalculatorButton->setToolTip(
tr("Storage calculator"));
connect(pCalculatorButton, SIGNAL(clicked()),
this, SLOT(slotWellboreStorageCalculator()));
pFieldLayout->addWidget(
pCalculatorLabel,
0,
nFieldColumnOffset,
Qt::AlignRight | Qt::AlignVCenter);
pFieldLayout->addWidget(
pCalculatorButton,
0,
nFieldColumnOffset + 1,
Qt::AlignLeft | Qt::AlignVCenter);
nFirstFieldRow = 1;
}
// 每个字段只把三个直接控件放入此网格,不产生字段外框或子面板。
for (int nFieldIndex = 0;
nFieldIndex < oGroupInfo.m_listFields.size();
@ -272,7 +328,7 @@ bool nmReservoirParameterPage::buildGroup(
m_pUnitService,
pGroupBox);
if (!pField->initialize(pFieldLayout,
nFieldIndex,
nFirstFieldRow + nFieldIndex,
nFieldColumnOffset,
sError))
{
@ -481,6 +537,43 @@ void nmReservoirParameterPage::slotEnumIndexChanged(int nIndex)
updateValidity();
}
void nmReservoirParameterPage::slotWellboreStorageCalculator()
{
if (m_nCurrentObjectIndex < 0 ||
m_nCurrentObjectIndex >= m_listObjectInfos.size())
{
return;
}
// 只定位当前井的W_C字段不访问实时井或全局当前井。
const nmReservoirPropertyObjectInfo& oObjectInfo =
m_listObjectInfos[m_nCurrentObjectIndex];
nmParameterField* pWellboreStorageField =
m_mapFields.value(
oObjectInfo.m_sObjectCode + "_W_C", nullptr);
if (pWellboreStorageField == nullptr)
{
return;
}
nmWxWellboreStorageCalculator oCalculator(this);
if (oCalculator.exec() != QDialog::Accepted)
{
return;
}
double dWellboreStorage =
oCalculator.getWellboreStorageValue();
if (dWellboreStorage < 0.0)
{
return;
}
// 仅回填页面字段;切换井或外层确定时才写入会话工作副本。
pWellboreStorageField->setBaseValue(dWellboreStorage);
updateValidity();
}
void nmReservoirParameterPage::applyVisibilityRules()
{
if (m_pSession == nullptr || m_nCurrentObjectIndex < 0 ||

@ -1275,6 +1275,9 @@ void nmWxTimeDependentSkin::restoreToolbarState()
void nmWxTimeDependentSkin::createChartModeBtn()
{
const QString sIconDir = QCoreApplication::applicationDirPath()
.section('/', 0, -2) + "/Res/Icon/";
// 创建Filter按钮
m_pFilterContainer = new QWidget(this);
QVBoxLayout* filterLayout = new QVBoxLayout(m_pFilterContainer);
@ -1282,7 +1285,7 @@ void nmWxTimeDependentSkin::createChartModeBtn()
filterLayout->setContentsMargins(2, 2, 2, 2);
m_pFilterBtn = new QPushButton(m_pFilterContainer);
m_pFilterBtn->setIcon(QIcon("D:/01-Projects/01-CNPC/01-nmWTAI-gitea/Bin/Res/Icon/NmFilter.png"));
m_pFilterBtn->setIcon(QIcon(sIconDir + "NmFilter.png"));
m_pFilterBtn->setIconSize(QSize(36, 36));
m_pFilterBtn->setFixedSize(40, 40);
m_pFilterBtn->setEnabled(true);
@ -1303,7 +1306,7 @@ void nmWxTimeDependentSkin::createChartModeBtn()
resetFilterLayout->setContentsMargins(2, 2, 2, 2);
m_pResetFilterBtn = new QPushButton(m_pResetFilterContainer);
m_pResetFilterBtn->setIcon(QIcon("D:/01-Projects/01-CNPC/01-nmWTAI-gitea/Bin/Res/Icon/NmResetFilter.png"));
m_pResetFilterBtn->setIcon(QIcon(sIconDir + "NmResetFilter.png"));
m_pResetFilterBtn->setIconSize(QSize(36, 36));
m_pResetFilterBtn->setFixedSize(40, 40);
m_pResetFilterBtn->setEnabled(true);
@ -1324,7 +1327,7 @@ void nmWxTimeDependentSkin::createChartModeBtn()
resetLinesLayout->setContentsMargins(2, 2, 2, 2);
m_pResetLinesBtn = new QPushButton(m_pResetLinesContainer);
m_pResetLinesBtn->setIcon(QIcon("D:/01-Projects/01-CNPC/01-nmWTAI-gitea/Bin/Res/Icon/NmResetLines.png"));
m_pResetLinesBtn->setIcon(QIcon(sIconDir + "NmResetLines.png"));
m_pResetLinesBtn->setIconSize(QSize(36, 36));
m_pResetLinesBtn->setFixedSize(40, 40);

Loading…
Cancel
Save