feat(nmNum): 重构储层参数设置对话框

- 新增模块化参数页面、字段目录和编辑会话
- 通过数据源注入实现工作副本及统一提交
- 支持单位换算、参数校验和分类联动
- 无对应对象时禁用分类按钮
- 统一渗透率基准单位为 D
feature/ReservoirPropertiesDlg-Refactoring-20260813
lh 5 days ago
parent a4366fb711
commit efc590edc6

Binary file not shown.

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="zh_CN">
<context>
@ -6781,4 +6781,78 @@ Average pressure in contour: %2 MPa</source>
<translation>Kriging </translation>
</message>
</context>
<context>
<name>nmReservoirParameterField</name>
<message><source>The parameter layout is unavailable.</source><translation></translation></message>
<message><source>Parameter definition not found: %1</source><translation>%1</translation></message>
<message><source>Failed to bind the unit for parameter: %1</source><translation>%1</translation></message>
<message><source>Dimensionless</source><translation></translation></message>
</context>
<context>
<name>nmReservoirParameterPage</name>
<message><source>Reservoir parameters</source><translation></translation></message>
<message><source>Well parameters</source><translation></translation></message>
<message><source>Fault parameters</source><translation></translation></message>
<message><source>Fracture parameters</source><translation></translation></message>
<message><source>Composite region parameters</source><translation></translation></message>
<message><source>Region mark parameters</source><translation></translation></message>
<message><source>Select well:</source><translation></translation></message>
<message><source>Select fault:</source><translation></translation></message>
<message><source>Select fracture:</source><translation></translation></message>
<message><source>Select composite region:</source><translation></translation></message>
<message><source>Select region mark:</source><translation></translation></message>
<message><source>Select object:</source><translation></translation></message>
<message><source>No editable objects in this category.</source><translation></translation></message>
</context>
<context>
<name>nmWxReservoirPropertiesEditor</name>
<message><source>Reservoir properties settings</source><translation></translation></message>
<message><source>Reservoir properties</source><translation></translation></message>
<message><source>Help</source><translation></translation></message>
<message><source>OK</source><translation></translation></message>
<message><source>Cancel</source><translation></translation></message>
<message><source>Basic</source><translation></translation></message>
<message><source>Wells</source><translation></translation></message>
<message><source>Geometry objects</source><translation></translation></message>
<message><source>Reservoir parameters</source><translation></translation></message>
<message><source>Well parameters</source><translation></translation></message>
<message><source>Faults</source><translation></translation></message>
<message><source>Fractures</source><translation></translation></message>
<message><source>Composite regions</source><translation></translation></message>
<message><source>Region marks</source><translation></translation></message>
<message><source>Please correct the invalid parameter values.</source><translation></translation></message>
<message><source>Reservoir type</source><translation></translation></message>
<message><source>Homogeneous</source><translation></translation></message>
<message><source>Dual porosity pseudo steady state</source><translation></translation></message>
<message><source>Fault flow model</source><translation></translation></message>
<message><source>Region flow model</source><translation></translation></message>
<message><source>Leaky</source><translation></translation></message>
<message><source>Composite limit</source><translation></translation></message>
<message><source>Fracture flow model</source><translation></translation></message>
<message><source>Finite conductivity</source><translation></translation></message>
<message><source>Infinite conductivity</source><translation></translation></message>
<message><source>Vertical well</source><translation></translation></message>
<message><source>Vertical fractured well</source><translation></translation></message>
<message><source>Horizontal fractured well</source><translation></translation></message>
<message><source>Position and wellbore</source><translation></translation></message>
<message><source>Storage and fracture</source><translation></translation></message>
<message><source>Basic properties</source><translation></translation></message>
<message><source>Fluid parameters</source><translation></translation></message>
<message><source>Compression and saturation</source><translation></translation></message>
<message><source>Dual-medium parameters</source><translation></translation></message>
<message><source>Geometry</source><translation></translation></message>
<message><source>Flow properties</source><translation></translation></message>
<message><source>Conductivity properties</source><translation></translation></message>
<message><source>Region properties</source><translation></translation></message>
</context>
<context>
<name>nmSubWndMain</name>
<message><source>Reservoir properties</source><translation></translation></message>
<message><source>The current numerical well-test data is unavailable.</source><translation></translation></message>
</context>
<context>
<name>QObject</name>
<message><source>The current numerical well-test data is unavailable.</source><translation></translation></message>
<message><source>The reservoir properties editing session is unavailable.</source><translation></translation></message>
</context>
</TS>

@ -17,8 +17,11 @@
<ParaGroup Name="" Alias="储层参数">
<ParaItem Name="h" Alias="储层厚度" Unit="m" Default="10" Max="10000" Min="1e-05" Digit="6" Desc="reservoir thickness" />
<ParaItem Name="Pi" Alias="初始压力" Unit="MPa" Default="40" Max="500" Min="0.1" Digit="6" Desc="initial formation pressure" />
<ParaItem Name="K" Alias="渗透率" Unit="mD" Default="1" Max="10000" Min="0" Digit="6" Desc="permeability" />
<ParaItem Name="K" Alias="渗透率" Unit="D" Default="0.025" Max="10" Min="0" Digit="6" Desc="permeability" />
<ParaItem Name="phi" Alias="孔隙度" Unit="" Default="0.1" Max="1" Min="1e-05" Digit="6" Desc="porosity" />
<ParaItem Name="Bo" Alias="油体积系数" Unit="" Default="1.2" Max="10" Min="1e-06" Digit="6" Desc="oil formation volume factor" />
<ParaItem Name="Miuo" Alias="油相黏度" Unit="mPa.s" Default="0.5" Max="10000" Min="1e-06" Digit="6" Desc="oil viscosity" />
<ParaItem Name="KxKy" Alias="水平渗透率比" Unit="" Default="1" Max="1000" Min="1e-06" Digit="6" Desc="horizontal permeability ratio" />
<ParaItem Name="Cti" Alias="综合压缩系数" Unit="1/MPa" Default="0.001" Max="10" Min="1e-30" Digit="10" Scientific="1" Desc="total compressibility" />
<ParaItem Name="Cf" Alias="岩石压缩系数" Unit="1/MPa" Default="0.0001" Max="10" Min="1e-30" Digit="10" Scientific="1" Desc="rock compressibility" />
<ParaItem Name="Soi" Alias="初始含油饱和度" Unit="" Default="0.8" Max="1" Min="0" Digit="6" Desc="initial oil saturation" />
@ -32,11 +35,11 @@
<ParaItem Name="W_Skin" Alias="表皮系数" Unit="" Default="0" Max="1000" Min="-100" Digit="6" Desc="skin factor" />
<ParaItem Name="W_C" Alias="井筒储集系数" Unit="m^3/MPa" Default="0" Max="1000" Min="0" Digit="6" Desc="wellbore storage coefficient" />
<ParaItem Name="W_WellLength" Alias="水平段长度" Unit="m" Default="200" Max="10000" Min="1e-05" Digit="6" Desc="horizontal well length" />
<ParaItem Name="W_DrainAngle" Alias="排水角度" Unit="o" Default="0" Max="360" Min="-360" Digit="6" Desc="horizontal well drain angle" />
<ParaItem Name="W_DrainAngle" Alias="排水角度" Unit="°" Default="0" Max="360" Min="-360" Digit="6" Desc="horizontal well drain angle" />
<ParaItem Name="W_NumberOfFractures" Alias="裂缝数量" Unit="" Default="15" Max="1000" Min="2" Digit="0" Desc="number of fractures" />
<ParaItem Name="W_FractureHalfLength" Alias="裂缝半长" Unit="m" Default="50" Max="10000" Min="1e-05" Digit="6" Desc="fracture half-length" />
<ParaItem Name="W_FractureAngle" Alias="裂缝角度" Unit="o" Default="90" Max="360" Min="-360" Digit="6" Desc="fracture angle" />
<ParaItem Name="W_Dfc" Alias="裂缝导流能力" Unit="md.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
<ParaItem Name="W_FractureAngle" Alias="裂缝角度" Unit="°" Default="90" Max="360" Min="-360" Digit="6" Desc="fracture angle" />
<ParaItem Name="W_Dfc" Alias="裂缝导流能力" Unit="mD.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
</ParaGroup>
<ParaGroup Name="" Alias="独立裂缝参数">
@ -44,7 +47,8 @@
<ParaItem Name="F_Y0" Alias="起点Y0" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fracture start Y coordinate" />
<ParaItem Name="F_X1" Alias="终点X1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fracture end X coordinate" />
<ParaItem Name="F_Y1" Alias="终点Y1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fracture end Y coordinate" />
<ParaItem Name="F_FC" Alias="导流能力" Unit="md.m" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
<ParaItem Name="F_FC" Alias="导流能力" Unit="mD.m" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
<ParaItem Name="F_DW" Alias="裂缝宽度参数" Unit="" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fracture width parameter" />
</ParaGroup>
<ParaGroup Name="" Alias="断层参数">
@ -52,6 +56,7 @@
<ParaItem Name="FT_Y0" Alias="起点Y0" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fault start Y coordinate" />
<ParaItem Name="FT_X1" Alias="终点X1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fault end X coordinate" />
<ParaItem Name="FT_Y1" Alias="终点Y1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fault end Y coordinate" />
<ParaItem Name="FT_Leakage" Alias="泄漏系数" Unit="" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fault leakage coefficient" />
</ParaGroup>
<ParaGroup Name="" Alias="矩形边界参数">
@ -85,12 +90,12 @@
<!-- 结果面板参数只由 rebuildResultParas() 选择,不会出现在左侧面板中。 -->
<ParaGroup Name="" Alias="结果参数">
<ParaItem Name="Result_W_Rw" Alias="井径" Unit="m" Default="0.1" Max="10" Min="0.001" Digit="6" Desc="result wellbore radius" />
<ParaItem Name="Result_W_Dfc" Alias="裂缝导流能力" Unit="md.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="result fracture conductivity" />
<ParaItem Name="Result_W_Dfc" Alias="裂缝导流能力" Unit="mD.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="result fracture conductivity" />
<ParaItem Name="Result_W_Skin" Alias="表皮系数" Unit="" Default="0" Max="1000" Min="-100" Digit="6" Desc="result skin factor" />
<ParaItem Name="Result_W_C" Alias="井筒储集系数" Unit="m^3/MPa" Default="0" Max="1000" Min="0" Digit="6" Desc="result wellbore storage coefficient" />
<ParaItem Name="Result_Pi" Alias="初始地层压力" Unit="MPa" Default="40" Max="500" Min="0.1" Digit="6" Desc="result initial formation pressure" />
<ParaItem Name="Result_K" Alias="渗透率" Unit="mD" Default="1" Max="10000" Min="0" Digit="6" Desc="result permeability" />
<ParaItem Name="Result_K" Alias="渗透率" Unit="D" Default="0.025" Max="10" Min="0" Digit="6" Desc="result permeability" />
<ParaItem Name="Result_h" Alias="储层厚度" Unit="m" Default="10" Max="10000" Min="1e-05" Digit="6" Desc="result reservoir thickness" />
<ParaItem Name="Result_phi" Alias="孔隙度" Unit="" Default="0.1" Max="1" Min="1e-05" Digit="6" Desc="result porosity" />
<ParaItem Name="Result_Cti" Alias="综合压缩系数" Unit="1/MPa" Default="0.001" Max="10" Min="1e-30" Digit="10" Scientific="1" Desc="result total compressibility" />
@ -99,7 +104,7 @@
<ParaItem Name="Result_Sgi" Alias="初始含气饱和度" Unit="" Default="0" Max="1" Min="0" Digit="6" Desc="result initial gas saturation" />
<ParaItem Name="Result_Swi" Alias="初始含水饱和度" Unit="" Default="0.2" Max="1" Min="0" Digit="6" Desc="result initial water saturation" />
<ParaItem Name="Result_Bo" Alias="油体积系数" Unit="m3/m3" Default="1" Max="1e+30" Min="0" Digit="6" Desc="result oil formation volume factor" />
<ParaItem Name="Result_Bo" Alias="油体积系数" Unit="" Default="1" Max="1e+30" Min="0" Digit="6" Desc="result oil formation volume factor" />
<ParaItem Name="Result_Miuo" Alias="油相黏度" Unit="mPa.s" Default="1" Max="1e+30" Min="1e-30" Digit="6" Desc="result oil viscosity" />
<ParaItem Name="Result_Co" Alias="油压缩系数" Unit="1/MPa" Default="0" Max="1e+30" Min="0" Digit="10" Scientific="1" Desc="result oil compressibility" />
<ParaItem Name="Result_Bw" Alias="水体积系数" Unit="m3/m3" Default="1" Max="1e+30" Min="0" Digit="6" Desc="result water formation volume factor" />

@ -18,8 +18,11 @@
<ParaGroup Name="" Alias="Reservoir Parameters">
<ParaItem Name="h" Alias="Reservoir Thickness" Unit="m" Default="10" Max="10000" Min="1e-05" Digit="3" Desc="reservoir thickness" />
<ParaItem Name="Pi" Alias="Initial Pressure" Unit="MPa" Default="40" Max="500" Min="0.1" Digit="5" Desc="initial formation pressure" />
<ParaItem Name="K" Alias="Permeability" Unit="mD" Default="1" Max="10000" Min="0" Digit="6" Desc="permeability" />
<ParaItem Name="K" Alias="Permeability" Unit="D" Default="0.025" Max="10" Min="0" Digit="6" Desc="permeability" />
<ParaItem Name="phi" Alias="Porosity" Unit="" Default="0.1" Max="1" Min="1e-05" Digit="4" Desc="porosity" />
<ParaItem Name="Bo" Alias="Oil Formation Volume Factor" Unit="" Default="1.2" Max="10" Min="1e-06" Digit="6" Desc="oil formation volume factor" />
<ParaItem Name="Miuo" Alias="Oil Viscosity" Unit="mPa.s" Default="0.5" Max="10000" Min="1e-06" Digit="6" Desc="oil viscosity" />
<ParaItem Name="KxKy" Alias="Horizontal Permeability Ratio" Unit="" Default="1" Max="1000" Min="1e-06" Digit="6" Desc="horizontal permeability ratio" />
<ParaItem Name="Cti" Alias="Total Compressibility" Unit="1/MPa" Default="0.001" Max="10" Min="1e-30" Digit="10" Scientific="1" Desc="total compressibility" />
<ParaItem Name="Cf" Alias="Rock Compressibility" Unit="1/MPa" Default="0.0001" Max="10" Min="1e-30" Digit="10" Scientific="1" Desc="rock compressibility" />
<ParaItem Name="Soi" Alias="Initial Oil Saturation" Unit="" Default="0.8" Max="1" Min="0" Digit="6" Desc="initial oil saturation" />
@ -33,11 +36,11 @@
<ParaItem Name="W_Skin" Alias="Skin Factor" Unit="" Default="0" Max="1000" Min="-100" Digit="6" Desc="skin factor" />
<ParaItem Name="W_C" Alias="Wellbore Storage Coeff" Unit="m^3/MPa" Default="0" Max="1000" Min="0" Digit="6" Desc="wellbore storage coefficient" />
<ParaItem Name="W_WellLength" Alias="Horizontal Well Length" Unit="m" Default="200" Max="10000" Min="1e-05" Digit="6" Desc="horizontal well length" />
<ParaItem Name="W_DrainAngle" Alias="Drain Angle" Unit="o" Default="0" Max="360" Min="-360" Digit="6" Desc="horizontal well drain angle" />
<ParaItem Name="W_DrainAngle" Alias="Drain Angle" Unit="°" Default="0" Max="360" Min="-360" Digit="6" Desc="horizontal well drain angle" />
<ParaItem Name="W_NumberOfFractures" Alias="Number of Fractures" Unit="" Default="15" Max="1000" Min="2" Digit="0" Desc="number of fractures" />
<ParaItem Name="W_FractureHalfLength" Alias="Fracture Half-Length" Unit="m" Default="50" Max="10000" Min="1e-05" Digit="6" Desc="fracture half-length" />
<ParaItem Name="W_FractureAngle" Alias="Fracture Angle" Unit="o" Default="90" Max="360" Min="-360" Digit="6" Desc="fracture angle" />
<ParaItem Name="W_Dfc" Alias="Fracture Conductivity" Unit="md.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
<ParaItem Name="W_FractureAngle" Alias="Fracture Angle" Unit="°" Default="90" Max="360" Min="-360" Digit="6" Desc="fracture angle" />
<ParaItem Name="W_Dfc" Alias="Fracture Conductivity" Unit="mD.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
</ParaGroup>
<ParaGroup Name="" Alias="Independent Fracture Parameters">
@ -45,7 +48,8 @@
<ParaItem Name="F_Y0" Alias="Start Y0" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fracture start Y coordinate" />
<ParaItem Name="F_X1" Alias="End X1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fracture end X coordinate" />
<ParaItem Name="F_Y1" Alias="End Y1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fracture end Y coordinate" />
<ParaItem Name="F_FC" Alias="Fracture Conductivity" Unit="md.m" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
<ParaItem Name="F_FC" Alias="Fracture Conductivity" Unit="mD.m" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fracture conductivity" />
<ParaItem Name="F_DW" Alias="Fracture Width Parameter" Unit="" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fracture width parameter" />
</ParaGroup>
<ParaGroup Name="" Alias="Fault Parameters">
@ -53,6 +57,7 @@
<ParaItem Name="FT_Y0" Alias="Start Y0" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fault start Y coordinate" />
<ParaItem Name="FT_X1" Alias="End X1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fault end X coordinate" />
<ParaItem Name="FT_Y1" Alias="End Y1" Unit="m" Default="0" Max="10000" Min="-10000" Digit="6" Desc="fault end Y coordinate" />
<ParaItem Name="FT_Leakage" Alias="Leakage Coefficient" Unit="" Default="1" Max="1e+30" Min="0" Digit="6" Desc="fault leakage coefficient" />
</ParaGroup>
<ParaGroup Name="" Alias="Rectangle Boundary Parameters">
@ -86,12 +91,12 @@
<!-- Result parameters are selected only by rebuildResultParas(). -->
<ParaGroup Name="" Alias="Result Parameters">
<ParaItem Name="Result_W_Rw" Alias="Well Radius" Unit="m" Default="0.1" Max="10" Min="0.001" Digit="6" Desc="result wellbore radius" />
<ParaItem Name="Result_W_Dfc" Alias="Fracture Conductivity" Unit="md.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="result fracture conductivity" />
<ParaItem Name="Result_W_Dfc" Alias="Fracture Conductivity" Unit="mD.m" Default="1000" Max="1e+30" Min="0" Digit="6" Desc="result fracture conductivity" />
<ParaItem Name="Result_W_Skin" Alias="Skin Factor" Unit="" Default="0" Max="1000" Min="-100" Digit="6" Desc="result skin factor" />
<ParaItem Name="Result_W_C" Alias="Wellbore Storage Coeff" Unit="m^3/MPa" Default="0" Max="1000" Min="0" Digit="6" Desc="result wellbore storage coefficient" />
<ParaItem Name="Result_Pi" Alias="Initial Formation Pressure" Unit="MPa" Default="40" Max="500" Min="0.1" Digit="6" Desc="result initial formation pressure" />
<ParaItem Name="Result_K" Alias="Permeability" Unit="mD" Default="1" Max="10000" Min="0" Digit="6" Desc="result permeability" />
<ParaItem Name="Result_K" Alias="Permeability" Unit="D" Default="0.025" Max="10" Min="0" Digit="6" Desc="result permeability" />
<ParaItem Name="Result_h" Alias="Reservoir Thickness" Unit="m" Default="10" Max="10000" Min="1e-05" Digit="6" Desc="result reservoir thickness" />
<ParaItem Name="Result_phi" Alias="Porosity" Unit="" Default="0.1" Max="1" Min="1e-05" Digit="6" Desc="result porosity" />
<ParaItem Name="Result_Cti" Alias="Total Compressibility" Unit="1/MPa" Default="0.001" Max="10" Min="1e-30" Digit="10" Scientific="1" Desc="result total compressibility" />
@ -100,7 +105,7 @@
<ParaItem Name="Result_Sgi" Alias="Initial Gas Saturation" Unit="" Default="0" Max="1" Min="0" Digit="6" Desc="result initial gas saturation" />
<ParaItem Name="Result_Swi" Alias="Initial Water Saturation" Unit="" Default="0.2" Max="1" Min="0" Digit="6" Desc="result initial water saturation" />
<ParaItem Name="Result_Bo" Alias="Oil Formation Volume Factor" Unit="m3/m3" Default="1" Max="1e+30" Min="0" Digit="6" Desc="result oil formation volume factor" />
<ParaItem Name="Result_Bo" Alias="Oil Formation Volume Factor" Unit="" Default="1" Max="1e+30" Min="0" Digit="6" Desc="result oil formation volume factor" />
<ParaItem Name="Result_Miuo" Alias="Oil Viscosity" Unit="mPa.s" Default="1" Max="1e+30" Min="1e-30" Digit="6" Desc="result oil viscosity" />
<ParaItem Name="Result_Co" Alias="Oil Compressibility" Unit="1/MPa" Default="0" Max="1e+30" Min="0" Digit="10" Scientific="1" Desc="result oil compressibility" />
<ParaItem Name="Result_Bw" Alias="Water Formation Volume Factor" Unit="m3/m3" Default="1" Max="1e+30" Min="0" Digit="6" Desc="result water formation volume factor" />

@ -0,0 +1,143 @@
#pragma once
#include "nmSubWxs_global.h"
#include "nmDefines.h"
#include <QList>
#include <QString>
class nmAttrRegistry;
struct nmReservoirPropertiesSnapshot;
/** @brief 储层参数编辑页面分类。 */
enum nmReservoirPropertiesCategory
{
NM_RESERVOIR_PAGE_RESERVOIR = 0, ///< 储层。
NM_RESERVOIR_PAGE_WELL, ///< 井。
NM_RESERVOIR_PAGE_FAULT, ///< 断层。
NM_RESERVOIR_PAGE_FRACTURE, ///< 裂缝。
NM_RESERVOIR_PAGE_COMPOSITE_REGION, ///< 复合区。
NM_RESERVOIR_PAGE_REGION_MARK, ///< 区域标记。
NM_RESERVOIR_PAGE_COUNT ///< 分类数量。
};
/** @brief 参数对象的具体类型。 */
enum nmReservoirPropertiesObjectType
{
NM_RESERVOIR_OBJECT_RESERVOIR = 0, ///< 储层对象。
NM_RESERVOIR_OBJECT_VERTICAL_WELL, ///< 直井对象。
NM_RESERVOIR_OBJECT_VERTICAL_FRAC_WELL, ///< 垂直压裂井对象。
NM_RESERVOIR_OBJECT_HORIZONTAL_FRAC_WELL,///< 水平压裂井对象。
NM_RESERVOIR_OBJECT_REGION_MARK, ///< 区域标记对象。
NM_RESERVOIR_OBJECT_FAULT, ///< 断层对象。
NM_RESERVOIR_OBJECT_FRACTURE, ///< 裂缝对象。
NM_RESERVOIR_OBJECT_REGION ///< 复合区对象。
};
/** @brief 参数字段的编辑器类型。 */
enum nmReservoirParameterEditorType
{
NM_RESERVOIR_EDITOR_NUMBER = 0, ///< 浮点数输入框。
NM_RESERVOIR_EDITOR_INTEGER, ///< 整数输入框。
NM_RESERVOIR_EDITOR_ENUM ///< 稳定code枚举下拉框。
};
/** @brief 枚举下拉框选项。 */
struct NM_SUB_WXS_EXPORT nmReservoirEnumOption
{
QString m_sCode; ///< 不随语言变化的业务代码。
QString m_sText; ///< 当前语言下的显示文本。
};
/**
* @brief
* @note IDIDXML
*/
struct NM_SUB_WXS_EXPORT nmReservoirParameterFieldInfo
{
QString m_sParameterId; ///< 编辑会话中的稳定参数ID。
QString m_sBaseParameterId; ///< 中英文参数XML中的基础参数ID。
QString m_sLabel; ///< 枚举字段的显示名称数值字段留空时读取XML别名。
nmReservoirParameterEditorType m_eEditorType; ///< 字段编辑器类型。
bool m_bUnitEnabled; ///< 是否为该字段创建单位下拉框。
QList<nmReservoirEnumOption> m_listOptions; ///< 枚举字段的稳定选项。
};
/** @brief 参数分组的界面描述。 */
struct NM_SUB_WXS_EXPORT nmReservoirParameterGroupInfo
{
QString m_sTitle; ///< 分组标题。
int m_nRow; ///< 分组在页面网格中的行。
int m_nColumn; ///< 分组在页面网格中的列。
int m_nRowSpan; ///< 分组纵向跨越的行数。
int m_nColumnSpan; ///< 分组横跨的列数。
bool m_bValueRightAligned; ///< 数值输入是否右对齐。
QList<nmReservoirParameterFieldInfo> m_listFields; ///< 分组内按显示顺序排列的字段。
};
/** @brief 单个可编辑对象的完整界面描述。 */
struct NM_SUB_WXS_EXPORT nmReservoirPropertyObjectInfo
{
nmReservoirPropertiesCategory m_eCategory; ///< 对象所属页面分类。
nmReservoirPropertiesObjectType m_eObjectType; ///< 对象具体类型。
int m_nSourceIndex; ///< 对象在工作副本容器中的索引。
QString m_sObjectCode; ///< 编辑会话内稳定的对象编码。
QString m_sDisplayName; ///< 对象显示名称。
QList<nmReservoirParameterGroupInfo> m_listGroups; ///< 原型式分组和字段描述。
};
/**
* @brief
* @note IDID
*/
class NM_SUB_WXS_EXPORT nmReservoirParameterCatalog
{
public:
/**
* @brief
* @param pRegistry
* @param oSnapshot
*/
static void registerSnapshot(nmAttrRegistry* pRegistry,
nmReservoirPropertiesSnapshot& oSnapshot);
/**
* @brief
* @param eCategory
* @param oSnapshot
* @return
*/
static QList<nmReservoirPropertyObjectInfo> createObjectInfos(
nmReservoirPropertiesCategory eCategory,
nmReservoirPropertiesSnapshot& oSnapshot);
/**
* @brief code
* @param sParameterId ID
* @param sStoredValue
* @return code
*/
static QString enumCodeFromStoredValue(const QString& sParameterId,
const QString& sStoredValue);
/**
* @brief code
* @param sParameterId ID
* @param sCode code
* @return 使
*/
static QString storedValueFromEnumCode(const QString& sParameterId,
const QString& sCode);
/** @brief 读取工作副本中的枚举code。 */
static QString enumCode(const nmReservoirPropertiesSnapshot& oSnapshot,
const QString& sParameterId);
/** @brief 修改工作副本中的枚举code。 */
static bool setEnumCode(nmReservoirPropertiesSnapshot& oSnapshot,
const QString& sParameterId,
const QString& sCode);
private:
nmReservoirParameterCatalog();
};

@ -0,0 +1,155 @@
#pragma once
#include "nmSubWxs_global.h"
#include "nmReservoirParameterCatalog.h"
#include <QObject>
#include <QVariant>
class iParameter;
class iUnitGroup;
class QComboBox;
class QGridLayout;
class QLabel;
class QLineEdit;
/**
* @brief
* @note iDlgBase
*/
class NM_SUB_WXS_EXPORT nmIReservoirParameterUnitService
{
public:
/** @brief 析构单位服务接口。 */
virtual ~nmIReservoirParameterUnitService() {}
/** @brief 按NM参数定义绑定一个数值编辑器和单位下拉框。 */
virtual bool bindUnitField(QLineEdit* pLineEdit,
QComboBox* pUnitComboBox,
const QString& sBaseParameterId) = 0;
/** @brief 将基准单位数值写入指定编辑器。 */
virtual void setUnitBaseValue(QLineEdit* pLineEdit,
double dBaseValue) = 0;
/** @brief 读取指定编辑器的基准单位数值。 */
virtual double unitBaseValue(QLineEdit* pLineEdit) const = 0;
/** @brief 校验指定单位编辑器。 */
virtual bool isUnitInputValid(QLineEdit* pLineEdit) = 0;
/** @brief 返回指定编辑器当前显示单位。 */
virtual QString currentUnit(QLineEdit* pLineEdit) const = 0;
/** @brief 返回指定编辑器的单位组,不转移所有权。 */
virtual iUnitGroup* unitGroup(QLineEdit* pLineEdit) const = 0;
};
/**
* @brief
* @note
*/
class NM_SUB_WXS_EXPORT nmReservoirParameterField : public QObject
{
public:
/**
* @brief
* @param oFieldInfo
* @param pUnitService
* @param pParent
*/
nmReservoirParameterField(
const nmReservoirParameterFieldInfo& oFieldInfo,
nmIReservoirParameterUnitService* pUnitService,
QObject* pParent = nullptr);
/** @brief 析构字段,不单独删除由分组控件拥有的界面控件。 */
virtual ~nmReservoirParameterField();
/**
* @brief
* @param pLayout
* @param nRow
* @param sError
* @return true
*/
bool initialize(QGridLayout* pLayout, int nRow, QString& sError);
/** @brief 返回编辑会话使用的稳定参数ID。 */
QString parameterId() const;
/** @brief 返回XML参数定义使用的基础参数ID。 */
QString baseParameterId() const;
/** @brief 返回字段是否为稳定code枚举。 */
bool isEnum() const;
/** @brief 设置数值输入文字是否右对齐。 */
void setValueRightAligned(bool bRightAligned);
/** @brief 返回数值编辑列的统一最小宽度。 */
static int editorMinimumWidth();
/** @brief 返回单位列的统一最小宽度。 */
static int unitMinimumWidth();
/** @brief 返回输入框和下拉框的统一紧凑高度。 */
static int editorHeight();
/** @brief 设置基准单位下的数值并刷新显示。 */
void setBaseValue(const QVariant& oValue);
/**
* @brief
* @param bValid
* @return QVariant
*/
QVariant baseValue(bool& bValid);
/** @brief 设置枚举稳定code不会触发业务联动。 */
void setEnumCode(const QString& sCode);
/** @brief 返回当前枚举稳定code。 */
QString enumCode() const;
/** @brief 校验当前输入,并更新参数范围提示。 */
bool validate();
/** @brief 同时显示或隐藏该参数行的三个直接控件。 */
void setFieldVisible(bool bVisible);
/** @brief 返回参数行当前是否参与编辑和校验。 */
bool isFieldVisible() const;
/** @brief 返回数值输入框,供页面集中连接变化信号。 */
QLineEdit* valueEditor() const;
/** @brief 返回枚举下拉框,供页面集中连接变化信号。 */
QComboBox* enumEditor() const;
private:
/** @brief 创建数值输入框及单位控件。 */
bool createNumericEditor(QGridLayout* pLayout,
int nRow,
QString& sError);
/** @brief 创建稳定code枚举下拉框。 */
void createEnumEditor(QGridLayout* pLayout, int nRow);
/** @brief 按XML精度格式化无单位或整数参数。 */
QString formatBaseValue(double dValue) const;
/** @brief 更新输入框的有效状态和帮助文字。 */
void updateValidationAppearance(bool bValid);
nmReservoirParameterFieldInfo m_oFieldInfo; ///< 稳定字段描述。
nmIReservoirParameterUnitService* m_pUnitService; ///< 注入的单位服务,不拥有所有权。
iParameter* m_pParameter; ///< 共享XML参数定义不拥有所有权。
QLabel* m_pLabel; ///< 参数名称标签。
QLineEdit* m_pLineEdit; ///< 数值输入框。
QComboBox* m_pValueComboBox; ///< 枚举值下拉框。
QComboBox* m_pUnitComboBox; ///< 可换算单位下拉框。
QLabel* m_pUnitLabel; ///< 无单位字段的空白对齐占位。
bool m_bVisible; ///< 参数行是否参与编辑。
bool m_bUpdating; ///< 程序写值时阻止业务信号。
};

@ -0,0 +1,128 @@
#pragma once
#include "nmSubWxs_global.h"
#include "nmReservoirParameterCatalog.h"
#include <QList>
#include <QMap>
#include <QWidget>
class nmIReservoirParameterUnitService;
class nmReservoirParameterField;
class nmReservoirPropertiesSession;
class QComboBox;
class QGridLayout;
class QLabel;
class QVBoxLayout;
/**
* @brief
* @note QWidget
*/
class NM_SUB_WXS_EXPORT nmReservoirParameterPage : public QWidget
{
Q_OBJECT
public:
/**
* @brief
* @param eCategory
* @param pSession
* @param pUnitService
* @param pParent
*/
nmReservoirParameterPage(nmReservoirPropertiesCategory eCategory,
nmReservoirPropertiesSession* pSession,
nmIReservoirParameterUnitService* pUnitService,
QWidget* pParent = nullptr);
/** @brief 析构参数页面。 */
virtual ~nmReservoirParameterPage();
/**
* @brief
* @param sError
* @return true
*/
bool reloadObjects(QString& sError);
/**
* @brief
* @return true
*/
bool commitPendingEdits();
/** @brief 返回当前页面的可见输入是否全部有效。 */
bool isInputValid();
/** @brief 返回当前分类是否存在可编辑对象。 */
bool hasEditableObjects() const;
/** @brief 返回页面分类。 */
nmReservoirPropertiesCategory category() const;
signals:
/** @brief 当前页有效状态发生变化。 */
void sigValidityChanged(bool bValid);
private slots:
/** @brief 切换当前编辑对象。 */
void slotObjectChanged(int nIndex);
/** @brief 数值输入变化后刷新整体有效状态。 */
void slotFieldValueEdited();
/** @brief 枚举code变化后保存并执行集中联动。 */
void slotEnumIndexChanged(int nIndex);
private:
/** @brief 创建页标题、对象选择器和单层分组区域。 */
void createUi();
/** @brief 根据当前分类返回标题。 */
QString categoryTitle() const;
/** @brief 根据当前分类返回对象选择标签。 */
QString objectSelectorLabel() const;
/** @brief 构建指定对象的全部分组和字段。 */
bool buildObject(int nObjectIndex, QString& sError);
/** @brief 删除上一个对象的分组控件和字段对象。 */
void clearObjectEditors();
/** @brief 创建一个原型式参数分组。 */
bool buildGroup(const nmReservoirParameterGroupInfo& oGroupInfo,
QString& sError);
/** @brief 从工作副本加载字段初始值。 */
void loadFieldValue(nmReservoirParameterField* pField);
/** @brief 根据当前稳定code统一刷新字段可见性。 */
void applyVisibilityRules();
/** @brief 设置指定稳定参数ID的字段可见性。 */
void setFieldVisible(const QString& sParameterId, bool bVisible);
/** @brief 刷新有效状态并通知主对话框。 */
void updateValidity();
nmReservoirPropertiesCategory m_eCategory; ///< 页面分类。
nmReservoirPropertiesSession* m_pSession; ///< 编辑会话,不拥有所有权。
nmIReservoirParameterUnitService* m_pUnitService; ///< 主对话框单位服务,不拥有所有权。
QList<nmReservoirPropertyObjectInfo> m_listObjectInfos; ///< 当前分类对象描述。
int m_nCurrentObjectIndex; ///< 当前对象索引。
bool m_bUpdatingObject; ///< 阻止对象下拉框回滚重入。
bool m_bBuildingFields; ///< 阻止加载数据时触发业务信号。
QLabel* m_pTitleLabel; ///< 页面标题。
QLabel* m_pObjectLabel; ///< 对象选择标签。
QComboBox* m_pObjectComboBox; ///< 多对象选择下拉框。
QWidget* m_pGroupsWidget; ///< 无边框的分组布局载体。
QGridLayout* m_pGroupsLayout; ///< 两列分组布局。
QVBoxLayout* m_pMainLayout; ///< 页面主布局。
QLabel* m_pEmptyLabel; ///< 空对象提示。
QList<nmReservoirParameterField*> m_listFields; ///< 当前对象全部字段。
QMap<QString, nmReservoirParameterField*> m_mapFields; ///< 稳定ID到字段映射。
QMap<QObject*, nmReservoirParameterField*> m_mapEditorFields; ///< 编辑器到字段映射。
};

@ -0,0 +1,92 @@
#pragma once
#include "nmSubWxs_global.h"
#include "nmDataReservoir.h"
#include "nmDataVerticalWell.h"
#include "nmDataVerticalFracturedWell.h"
#include "nmDataHorizontalFracturedWell.h"
#include "nmDataRegionMark.h"
#include "nmDataFault.h"
#include "nmDataFracture.h"
#include "nmDataRegion.h"
class nmDataAnalyzeManager;
/**
* @brief
* @note
*/
struct NM_SUB_WXS_EXPORT nmReservoirPropertiesSnapshot
{
/** @brief 构造空快照。 */
nmReservoirPropertiesSnapshot();
nmDataReservoir m_oReservoir; ///< 储层参数副本。
QVector<nmDataVerticalWell> m_vecVerticalWells; ///< 直井参数副本。
QVector<nmDataVerticalFracturedWell> m_vecVerticalFracturedWells; ///< 垂直压裂井参数副本。
QVector<nmDataHorizontalFracturedWell> m_vecHorizontalFracturedWells; ///< 水平压裂井参数副本。
QVector<nmDataRegionMark> m_vecRegionMarks; ///< 区域标记参数副本。
QVector<nmDataFault> m_vecFaults; ///< 断层参数副本。
QVector<nmDataFracture> m_vecFractures; ///< 裂缝参数副本包含DFN以保持提交索引稳定。
QVector<nmDataRegion> m_vecRegions; ///< 复合区参数副本。
};
/**
* @brief
* @note 访
*/
class NM_SUB_WXS_EXPORT nmIReservoirPropertiesDataSource
{
public:
/** @brief 析构数据源接口。 */
virtual ~nmIReservoirPropertiesDataSource();
/**
* @brief
* @param oSnapshot
* @param sError
* @return true
*/
virtual bool loadSnapshot(nmReservoirPropertiesSnapshot& oSnapshot,
QString& sError) const = 0;
/**
* @brief
* @param oSnapshot
* @param bGeometryChanged
* @param sError
* @return true
*/
virtual bool commitSnapshot(const nmReservoirPropertiesSnapshot& oSnapshot,
bool bGeometryChanged,
QString& sError) = 0;
};
/**
* @brief nmDataAnalyzeManager
*/
class NM_SUB_WXS_EXPORT nmAnalyzeManagerReservoirDataSource :
public nmIReservoirPropertiesDataSource
{
public:
/**
* @brief
* @param pDataManager
*/
explicit nmAnalyzeManagerReservoirDataSource(nmDataAnalyzeManager* pDataManager);
/** @brief 析构数据源适配器。 */
virtual ~nmAnalyzeManagerReservoirDataSource();
virtual bool loadSnapshot(nmReservoirPropertiesSnapshot& oSnapshot,
QString& sError) const;
virtual bool commitSnapshot(const nmReservoirPropertiesSnapshot& oSnapshot,
bool bGeometryChanged,
QString& sError);
private:
nmDataAnalyzeManager* m_pDataManager; ///< 当前分析窗口的数据管理器,不拥有所有权。
};

@ -0,0 +1,98 @@
#pragma once
#include "nmSubWxs_global.h"
#include "nmReservoirPropertiesDataSource.h"
#include "nmReservoirParameterCatalog.h"
#include <QMap>
#include <QVariant>
class nmAttrRegistry;
/**
* @brief
* @note
*/
class NM_SUB_WXS_EXPORT nmReservoirPropertiesSession
{
public:
/**
* @brief
* @param pDataSource
*/
explicit nmReservoirPropertiesSession(nmIReservoirPropertiesDataSource* pDataSource);
/** @brief 析构编辑会话及其独立注册表。 */
~nmReservoirPropertiesSession();
/**
* @brief
* @param sError
* @return true
*/
bool initialize(QString& sError);
/**
* @brief
* @param eCategory
* @return
*/
QList<nmReservoirPropertyObjectInfo> objectInfos(
nmReservoirPropertiesCategory eCategory);
/**
* @brief
* @param sParameterId ID
* @return QVariant
*/
QVariant value(const QString& sParameterId) const;
/**
* @brief
* @param sParameterId ID
* @param oValue
* @return true
*/
bool setValue(const QString& sParameterId, const QVariant& oValue);
/**
* @brief
* @param mapValues ID
*/
void setValues(const QMap<QString, QVariant>& mapValues);
/**
* @brief code
* @param sParameterId ID
* @return code
*/
QString enumCode(const QString& sParameterId) const;
/**
* @brief code
* @param sParameterId ID
* @param sCode code
* @return true
*/
bool setEnumCode(const QString& sParameterId, const QString& sCode);
/**
* @brief
* @param sError
* @return true
*/
bool commit(QString& sError);
/** @brief 获取只读工作副本,供后续导入预览或测试使用。 */
const nmReservoirPropertiesSnapshot& workingSnapshot() const;
private:
/** @brief 判断井坐标或几何参数是否发生变化。 */
bool isGeometryChanged() const;
nmIReservoirPropertiesDataSource* m_pDataSource; ///< 参数数据源,不拥有所有权。
nmAttrRegistry* m_pAttrRegistry; ///< 编辑会话独占的属性注册表。
nmReservoirPropertiesSnapshot m_oOriginalSnapshot; ///< 打开对话框时的原始快照。
nmReservoirPropertiesSnapshot m_oWorkingSnapshot; ///< 所有界面编辑写入的工作副本。
bool m_bInitialized; ///< 会话是否初始化成功。
};

@ -0,0 +1,148 @@
#pragma once
#include "iDlgBase.h"
#include "nmSubWxs_global.h"
#include "nmReservoirParameterCatalog.h"
#include "nmReservoirParameterField.h"
class nmIReservoirPropertiesDataSource;
class nmReservoirPropertiesSession;
class nmReservoirParameterPage;
class QButtonGroup;
class QDialogButtonBox;
class QFrame;
class QHBoxLayout;
class QLabel;
class QStackedWidget;
class QToolButton;
/**
* @brief
* @note
*/
class NM_SUB_WXS_EXPORT nmWxReservoirPropertiesEditor :
public iDlgBase,
public nmIReservoirParameterUnitService
{
Q_OBJECT
public:
/**
* @brief
* @param pDataSource
* @param pParent
*/
nmWxReservoirPropertiesEditor(
nmIReservoirPropertiesDataSource* pDataSource,
QWidget* pParent = nullptr);
/** @brief 析构编辑器。 */
virtual ~nmWxReservoirPropertiesEditor();
/**
* @brief
* @param sError
* @return true
*/
bool initialize(QString& sError);
/** @brief 通过iDlgBase按NM参数系列绑定单位控件。 */
virtual bool bindUnitField(QLineEdit* pLineEdit,
QComboBox* pUnitComboBox,
const QString& sBaseParameterId);
/** @brief 将基准单位数值写入字段。 */
virtual void setUnitBaseValue(QLineEdit* pLineEdit,
double dBaseValue);
/** @brief 读取字段的基准单位数值。 */
virtual double unitBaseValue(QLineEdit* pLineEdit) const;
/** @brief 校验单位字段输入。 */
virtual bool isUnitInputValid(QLineEdit* pLineEdit);
/** @brief 返回字段当前显示单位。 */
virtual QString currentUnit(QLineEdit* pLineEdit) const;
/** @brief 返回字段单位组,不转移所有权。 */
virtual iUnitGroup* unitGroup(QLineEdit* pLineEdit) const;
public slots:
/** @brief 校验并一次性提交工作副本。 */
virtual void accept();
/** @brief 丢弃工作副本并关闭窗口。 */
virtual void reject();
private slots:
/** @brief 切换顶部参数分类。 */
void slotCategoryButtonClicked(int nCategory);
/** @brief 根据各页面状态更新确定按钮。 */
void slotPageValidityChanged(bool bValid);
/** @brief 进入当前界面的上下文帮助模式。 */
void slotHelp();
private:
/** @brief 创建编辑器的原型式固定布局。 */
void createUi();
/** @brief 创建顶部分组导航。 */
QWidget* createCategoryBar();
/**
* @brief ribbon
* @param sCaption
* @param pParent
* @param pCommandLayout
* @return
*/
QWidget* createRibbonGroup(const QString& sCaption,
QWidget* pParent,
QHBoxLayout*& pCommandLayout);
/**
* @brief 线
* @param pParent 线
* @return Qt线
*/
QFrame* createRibbonSeparator(QWidget* pParent);
/**
* @brief
* @param eCategory
* @param sText
* @param sIconName
* @param pParent ribbon
* @return
*/
QToolButton* createCategoryButton(
nmReservoirPropertiesCategory eCategory,
const QString& sText,
const QString& sIconName,
QWidget* pParent);
/** @brief 创建帮助命令按钮。 */
QToolButton* createHelpButton(QWidget* pParent);
/** @brief 创建六个共享参数页面。 */
void createPages();
/** @brief 根据各分类的实际对象数量更新顶部按钮启用状态。 */
void updateCategoryButtonStates();
/** @brief 校验所有页面并更新确定按钮。 */
bool validateAllPages();
nmIReservoirPropertiesDataSource* m_pDataSource; ///< 参数数据源,不拥有所有权。
nmReservoirPropertiesSession* m_pSession; ///< 对话框独占编辑会话。
QButtonGroup* m_pCategoryButtonGroup; ///< 顶部分类按钮及稳定页面ID映射。
QStackedWidget* m_pStackedWidget; ///< 无边框分类页面堆栈。
QDialogButtonBox* m_pButtonBox; ///< 确定和取消按钮区。
QToolButton* m_pFooterHelpButton; ///< 底部帮助命令。
QLabel* m_pStatusLabel; ///< 校验或提交错误提示。
nmReservoirParameterPage* m_pPages[NM_RESERVOIR_PAGE_COUNT]; ///< 六个参数页。
int m_nCurrentCategory; ///< 当前显示分类。
bool m_bInitialized; ///< 编辑器是否初始化完成。
};

@ -13,7 +13,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_swiSelected = false; // 默认不选中
// 初始化参数最大值
m_permeabilityMax = nmDataAttribute("Permeability Max", 10.0, "Darcy"); // 1000 mD
m_permeabilityMax = nmDataAttribute("Permeability Max", 10.0, "D");
m_skinMax = nmDataAttribute("Skin Max", 10.0, ""); // 100
m_wellboreStorageMax = nmDataAttribute("Wellbore Storage Max", 2.0, "m^3/MPa");
m_porosityMax = nmDataAttribute("Porosity Max", 0.5, ""); // 50%
@ -23,7 +23,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_swiMax = nmDataAttribute("Swi Max", 1.0, "");
// 初始化参数最小值
m_permeabilityMin = nmDataAttribute("Permeability Min", 0.001, "Darcy"); // 0.001 mD
m_permeabilityMin = nmDataAttribute("Permeability Min", 0.001, "D");
m_skinMin = nmDataAttribute("Skin Min", -10.0, ""); // 允许负表皮
m_wellboreStorageMin = nmDataAttribute("Wellbore Storage Min", 1e-4, "m^3/MPa");
m_porosityMin = nmDataAttribute("Porosity Min", 0.01, ""); // 1%
@ -164,6 +164,8 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
// 反序列化参数最大值
if (jsonValue.HasMember("PermeabilityMax") && jsonValue["PermeabilityMax"].IsObject()) {
m_permeabilityMax.FromJsonValue(jsonValue["PermeabilityMax"]);
// 自动拟合上下限与求解器使用同一基准单位不按旧JSON单位文本换算。
m_permeabilityMax.setUnit("D");
}
if (jsonValue.HasMember("SkinMax") && jsonValue["SkinMax"].IsObject()) {
m_skinMax.FromJsonValue(jsonValue["SkinMax"]);
@ -190,6 +192,7 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
// 反序列化参数最小值
if (jsonValue.HasMember("PermeabilityMin") && jsonValue["PermeabilityMin"].IsObject()) {
m_permeabilityMin.FromJsonValue(jsonValue["PermeabilityMin"]);
m_permeabilityMin.setUnit("D");
}
if (jsonValue.HasMember("SkinMin") && jsonValue["SkinMin"].IsObject()) {
m_skinMin.FromJsonValue(jsonValue["SkinMin"]);

@ -1,17 +1,18 @@
#include "nmDataReservoir.h"
#include "nmDataReservoir.h"
#include "ZxSerializer.h"
nmDataReservoir::nmDataReservoir() {
// 基础属性
m_initialPressure = nmDataAttribute("Initial Pressure",40.0, "MPa", UNIT_TYPE_PRESSURE, QStringList(), QStringList() << "psia" << "Pa" << "kPa" << "atm" << "bara" << "kg/cm^2" << "m" << "psig" << "bar" << "MPa" << "kPag");
m_permeability = nmDataAttribute("Permeability", 0.025, "Darcy", UNIT_TYPE_PERMEABILITY, QStringList(), QStringList() << "md" << "Darcy" << "m^2" << "cm^2" << "um^2");
// 求解器直接读取该数值内部基准单位固定为D显示换算由框架单位绑定器负责。
m_permeability = nmDataAttribute("Permeability", 0.025, "D");
m_thickness = nmDataAttribute("Thickness", 10.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "ft" << "m" << "cm" << "mm" << "in" << "0.1 in" << "mile" << "km");
m_porosity = nmDataAttribute("Porosity", 0.1, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList());
m_kxKy = nmDataAttribute("Kx/Ky", 1.0, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList());
// 油相PVT单值
m_Bo = nmDataAttribute("Bo", 1.2, "");
m_Miuo = nmDataAttribute("Miuo", 0.5, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList());
m_Miuo = nmDataAttribute("Miuo", 0.5, "mPa.s");
// 气相PVT单值
m_Bg = nmDataAttribute("Bg", 1.0, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList());
@ -22,8 +23,8 @@ nmDataReservoir::nmDataReservoir() {
m_Miuw = nmDataAttribute("Miuw", 1.0, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList());
// 压缩系数
m_Ct = nmDataAttribute("Ct", 0.001, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList());
m_Cf = nmDataAttribute("Cf", 0.0001, "", UNIT_TYPE_DIMENSIONLESS, QStringList(), QStringList());
m_Ct = nmDataAttribute("Ct", 0.001, "1/MPa");
m_Cf = nmDataAttribute("Cf", 0.0001, "1/MPa");
// 储层类型与传导率
m_reservoirType = nmDataAttribute("Reservoir type", "Homogeneous", "", UNIT_TYPE_DIMENSIONLESS, QStringList() << "Homogeneous" << "Dual porosity pseudo steady state", QStringList());
@ -147,6 +148,8 @@ void nmDataReservoir::FromJsonValue(const rapidjson::Value& jsonValue)
}
if (jsonValue.HasMember("Permeability") && jsonValue["Permeability"].IsObject()) {
m_permeability.FromJsonValue(jsonValue["Permeability"]);
// JSON中的单位文本不参与兼容换算读取后的内部单位始终按求解器标准固定为D。
m_permeability.setUnit("D");
}
if (jsonValue.HasMember("Thickness") && jsonValue["Thickness"].IsObject()) {
m_thickness.FromJsonValue(jsonValue["Thickness"]);
@ -240,11 +243,11 @@ bool nmDataReservoir::_parseData(VecVariant vec, int& n) {
m_initialPressure = nmDataAttribute("Initial Pressure", vec[n++].toString(), "MPa");
m_reservoirType = nmDataAttribute("Reservoir Type", vec[n++].toString(), "");
m_Bo = nmDataAttribute("Bo", vec[n++].toString(), "");
m_Miuo = nmDataAttribute("Miuo", vec[n++].toString(), "mPa.s(cp)");
m_permeability = nmDataAttribute("Permeability", vec[n++].toString(), "mD");
m_Miuo = nmDataAttribute("Miuo", vec[n++].toString(), "mPa.s");
m_permeability = nmDataAttribute("Permeability", vec[n++].toString(), "D");
m_thickness = nmDataAttribute("Thickness", vec[n++].toString(), "m");
m_porosity = nmDataAttribute("Porosity", vec[n++].toString(), "");
m_Ct = nmDataAttribute("Ct", vec[n++].toString(), "");
m_Ct = nmDataAttribute("Ct", vec[n++].toString(), "1/MPa");
m_kxKy = nmDataAttribute("Kx/Ky", vec[n++].toString(), "");
return true;
}
@ -280,11 +283,12 @@ void nmDataReservoir::onDeserialize(ZxSerializer* ser) {
ser->read("InitialPressure", tempValue); m_initialPressure = nmDataAttribute("Initial Pressure", tempValue, "MPa");
ser->read("ReservoirType", tempValue); m_reservoirType = nmDataAttribute("Reservoir Type", tempValue, "");
ser->read("Bo", tempValue); m_Bo = nmDataAttribute("Bo", tempValue, "");
ser->read("Miuo", tempValue); m_Miuo = nmDataAttribute("Miuo", tempValue, "mPa.s(cp)");
ser->read("Permeability", tempValue); m_permeability = nmDataAttribute("Permeability", tempValue, "mD");
ser->read("Miuo", tempValue); m_Miuo = nmDataAttribute("Miuo", tempValue, "mPa.s");
// 序列化格式只保存数值读取时明确按求解器基准单位D解释。
ser->read("Permeability", tempValue); m_permeability = nmDataAttribute("Permeability", tempValue, "D");
ser->read("Thickness", tempValue); m_thickness = nmDataAttribute("Thickness", tempValue, "m");
ser->read("Porosity", tempValue); m_porosity = nmDataAttribute("Porosity", tempValue, "");
ser->read("NetToGross", tempValue); m_Ct = nmDataAttribute("Ct", tempValue, "");
ser->read("Ct", tempValue); m_Ct = nmDataAttribute("Ct", tempValue, "1/MPa");
ser->read("KxKy", tempValue); m_kxKy = nmDataAttribute("Kx/Ky", tempValue, "");
}

@ -56,7 +56,8 @@
#include "nmWxRCDialog.h"
#include "nmWxWellLayerDlg.h"
#include "nmWxGeometryLayerDlg.h"
#include "nmWxReservoirPropertiesDlg.h"
#include "nmReservoirPropertiesDataSource.h"
#include "nmWxReservoirPropertiesEditor.h"
#include "nmWxPropertyInterpolationDlg.h"
#include "nmWxPostprocessingAnimationWidget.h"
#include "nmWxGeoRefDlg.h"
@ -1961,10 +1962,29 @@ bool nmSubWndMain::onConfirmClosing()
void nmSubWndMain::reservoirCharacteristics()
{
nmWxReservoirPropertiesDlg dlg;
dlg.exec();
// 第一步:取得当前分析窗口对应的数据管理器,避免新对话框访问全局单例。
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
if (pDataManager == nullptr)
{
QMessageBox::warning(this, tr("Reservoir properties"),
tr("The current numerical well-test data is unavailable."));
return;
}
// 第二步:在入口处组装数据源和编辑器,两个对象的生命周期覆盖整个模态会话。
nmAnalyzeManagerReservoirDataSource oDataSource(pDataManager);
nmWxReservoirPropertiesEditor oEditor(&oDataSource, this);
// 第三步:先建立工作副本;初始化失败时不打开不完整的参数界面。
QString sError;
if (!oEditor.initialize(sError))
{
QMessageBox::warning(this, tr("Reservoir properties"), sError);
return;
}
// TODO: 修改完参数应该刷新画布
// 第四步:确定时由编辑会话一次性提交,取消时真实数据保持不变。
oEditor.exec();
}
void nmSubWndMain::geoLayering()

@ -0,0 +1,852 @@
#include "nmReservoirParameterCatalog.h"
#include "nmReservoirPropertiesDataSource.h"
#include "nmAttrRegistry.h"
#include "nmDataAttribute.h"
#include "nmDataPerforation.h"
#include <QCoreApplication>
namespace
{
/*
* Qt 4.8lupdateeditorText()
*
*/
static const char* const s_pCatalogTranslationMarkers[] =
{
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Reservoir type"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Homogeneous"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Dual porosity pseudo steady state"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Fault flow model"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Region flow model"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Leaky"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Composite limit"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Fracture flow model"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Finite conductivity"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Infinite conductivity"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Position and wellbore"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Storage and fracture"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Basic properties"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Fluid parameters"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Compression and saturation"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Reservoir parameters"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Vertical well"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Vertical fractured well"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Horizontal fractured well"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Dual-medium parameters"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Geometry"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Flow properties"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Conductivity properties"),
QT_TRANSLATE_NOOP("nmWxReservoirPropertiesEditor", "Region properties")
};
/** @brief 返回当前语言下的界面文字。 */
QString editorText(const char* pText)
{
Q_UNUSED(s_pCatalogTranslationMarkers);
return QCoreApplication::translate("nmWxReservoirPropertiesEditor", pText);
}
/** @brief 创建一个稳定枚举选项。 */
nmReservoirEnumOption enumOption(const QString& sCode, const char* pText)
{
nmReservoirEnumOption oOption;
oOption.m_sCode = sCode;
oOption.m_sText = editorText(pText);
return oOption;
}
/** @brief 创建一个数值字段描述。 */
nmReservoirParameterFieldInfo numericField(
const QString& sParameterId,
const QString& sBaseParameterId,
nmReservoirParameterEditorType eEditorType)
{
nmReservoirParameterFieldInfo oField;
oField.m_sParameterId = sParameterId;
oField.m_sBaseParameterId = sBaseParameterId;
oField.m_eEditorType = eEditorType;
oField.m_bUnitEnabled = true;
return oField;
}
/** @brief 创建一个浮点数字段描述。 */
nmReservoirParameterFieldInfo numericField(
const QString& sParameterId,
const QString& sBaseParameterId)
{
return numericField(sParameterId, sBaseParameterId,
NM_RESERVOIR_EDITOR_NUMBER);
}
/** @brief 创建一个明确不显示单位的浮点数字段。 */
nmReservoirParameterFieldInfo unitlessNumericField(
const QString& sParameterId,
const QString& sBaseParameterId)
{
nmReservoirParameterFieldInfo oField =
numericField(sParameterId, sBaseParameterId);
oField.m_bUnitEnabled = false;
return oField;
}
/** @brief 创建储层类型字段。 */
nmReservoirParameterFieldInfo reservoirModelField(
const QString& sParameterId)
{
nmReservoirParameterFieldInfo oField;
oField.m_sParameterId = sParameterId;
oField.m_sLabel = editorText("Reservoir type");
oField.m_eEditorType = NM_RESERVOIR_EDITOR_ENUM;
oField.m_bUnitEnabled = false;
oField.m_listOptions.append(enumOption("homogeneous", "Homogeneous"));
oField.m_listOptions.append(enumOption(
"dual_porosity_pss", "Dual porosity pseudo steady state"));
return oField;
}
/** @brief 创建断层或复合区流动模型字段。 */
nmReservoirParameterFieldInfo leakageModelField(
const QString& sParameterId,
const char* pLabel)
{
nmReservoirParameterFieldInfo oField;
oField.m_sParameterId = sParameterId;
oField.m_sLabel = editorText(pLabel);
oField.m_eEditorType = NM_RESERVOIR_EDITOR_ENUM;
oField.m_bUnitEnabled = false;
oField.m_listOptions.append(enumOption("leaky", "Leaky"));
oField.m_listOptions.append(enumOption(
"composite_limit", "Composite limit"));
return oField;
}
/** @brief 创建裂缝导流模型字段。 */
nmReservoirParameterFieldInfo fractureModelField(
const QString& sParameterId)
{
nmReservoirParameterFieldInfo oField;
oField.m_sParameterId = sParameterId;
oField.m_sLabel = editorText("Fracture flow model");
oField.m_eEditorType = NM_RESERVOIR_EDITOR_ENUM;
oField.m_bUnitEnabled = false;
oField.m_listOptions.append(enumOption(
"finite_conductivity", "Finite conductivity"));
oField.m_listOptions.append(enumOption(
"infinite_conductivity", "Infinite conductivity"));
return oField;
}
/** @brief 创建页面上的一个参数分组。 */
nmReservoirParameterGroupInfo parameterGroup(
const char* pTitle,
int nRow,
int nColumn,
int nColumnSpan)
{
nmReservoirParameterGroupInfo oGroup;
oGroup.m_sTitle = editorText(pTitle);
oGroup.m_nRow = nRow;
oGroup.m_nColumn = nColumn;
oGroup.m_nRowSpan = 1;
oGroup.m_nColumnSpan = nColumnSpan;
oGroup.m_bValueRightAligned = false;
return oGroup;
}
/** @brief 为参数ID添加对象编码前缀。 */
QString prefixed(const QString& sObjectCode, const char* pParameterId)
{
return sObjectCode + "_" + QString::fromLatin1(pParameterId);
}
/** @brief 生成无井编码时的会话编码。 */
QString wellCode(const nmDataWellBase& oWell,
const QString& sFallbackPrefix,
int nIndex)
{
QString sCode = oWell.getWellCode();
if (sCode.isEmpty())
{
sCode = QString("%1%2").arg(sFallbackPrefix)
.arg(nIndex + 1, 4, 10, QChar('0'));
}
return sCode;
}
/** @brief 创建井对象的分组描述。 */
void createWellGroups(nmReservoirPropertyObjectInfo& oInfo,
NM_WELL_MODEL eWellType,
bool bHasPerforation)
{
nmReservoirParameterGroupInfo oPosition =
parameterGroup("Position and wellbore", 0, 0, 1);
oPosition.m_listFields
<< numericField(prefixed(oInfo.m_sObjectCode, "W_X"), "W_X")
<< numericField(prefixed(oInfo.m_sObjectCode, "W_Y"), "W_Y")
<< numericField(prefixed(oInfo.m_sObjectCode, "W_Rw"), "W_Rw");
if (bHasPerforation)
{
oPosition.m_listFields.append(numericField(
prefixed(oInfo.m_sObjectCode, "W_Skin"), "W_Skin"));
}
nmReservoirParameterGroupInfo oStorage =
parameterGroup("Storage and fracture", 0, 1, 1);
oStorage.m_listFields.append(numericField(
prefixed(oInfo.m_sObjectCode, "W_C"), "W_C"));
if (eWellType == Vertical_Fractured_Well)
{
oStorage.m_listFields
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_FractureHalfLength"),
"W_FractureHalfLength")
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_FractureAngle"),
"W_FractureAngle")
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_Dfc"), "W_Dfc");
}
else if (eWellType == Horizontal_Fractured_Well)
{
oStorage.m_listFields
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_WellLength"),
"W_WellLength")
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_DrainAngle"),
"W_DrainAngle")
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_NumberOfFractures"),
"W_NumberOfFractures",
NM_RESERVOIR_EDITOR_INTEGER)
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_FractureHalfLength"),
"W_FractureHalfLength")
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_FractureAngle"),
"W_FractureAngle")
<< numericField(prefixed(oInfo.m_sObjectCode,
"W_Dfc"), "W_Dfc");
}
oInfo.m_listGroups << oPosition << oStorage;
}
/** @brief 注册一口井的全部可编辑数值属性。 */
void registerWell(nmAttrRegistry* pRegistry,
nmDataWellBase& oWell,
const QString& sCode)
{
pRegistry->regAttr(prefixed(sCode, "W_X"), &oWell.getX());
pRegistry->regAttr(prefixed(sCode, "W_Y"), &oWell.getY());
pRegistry->regAttr(prefixed(sCode, "W_Rw"), &oWell.getRadius());
pRegistry->regAttr(prefixed(sCode, "W_C"), &oWell.getWellboreStorage());
if (oWell.getPerforationCount() > 0 &&
oWell.getPerforation(0) != nullptr)
{
pRegistry->regAttr(prefixed(sCode, "W_Skin"),
&oWell.getPerforation(0)->getSkin());
}
}
/** @brief 判断对象描述中是否包含指定枚举字段。 */
bool containsEnumField(const nmReservoirPropertyObjectInfo& oInfo,
const QString& sParameterId)
{
for (int nGroupIndex = 0;
nGroupIndex < oInfo.m_listGroups.size();
++nGroupIndex)
{
const nmReservoirParameterGroupInfo& oGroup =
oInfo.m_listGroups[nGroupIndex];
for (int nFieldIndex = 0;
nFieldIndex < oGroup.m_listFields.size();
++nFieldIndex)
{
const nmReservoirParameterFieldInfo& oField =
oGroup.m_listFields[nFieldIndex];
if (oField.m_eEditorType == NM_RESERVOIR_EDITOR_ENUM &&
oField.m_sParameterId == sParameterId)
{
return true;
}
}
}
return false;
}
}
void nmReservoirParameterCatalog::registerSnapshot(
nmAttrRegistry* pRegistry,
nmReservoirPropertiesSnapshot& oSnapshot)
{
if (pRegistry == nullptr)
{
return;
}
// 第一步:重新建立编辑会话独占的注册表,杜绝与实时数据交叉连接。
pRegistry->clear();
pRegistry->regAttr("h", &oSnapshot.m_oReservoir.getThickness());
pRegistry->regAttr("Pi", &oSnapshot.m_oReservoir.getInitialPressure());
pRegistry->regAttr("K", &oSnapshot.m_oReservoir.getPermeability());
pRegistry->regAttr("phi", &oSnapshot.m_oReservoir.getPorosity());
pRegistry->regAttr("Cti", &oSnapshot.m_oReservoir.getCt());
pRegistry->regAttr("Cf", &oSnapshot.m_oReservoir.getCf());
pRegistry->regAttr("Soi", &oSnapshot.m_oReservoir.getSoi());
pRegistry->regAttr("Swi", &oSnapshot.m_oReservoir.getSwi());
pRegistry->regAttr("Bo", &oSnapshot.m_oReservoir.getBo());
pRegistry->regAttr("Miuo", &oSnapshot.m_oReservoir.getMiuo());
pRegistry->regAttr("KxKy", &oSnapshot.m_oReservoir.getKxKy());
// 第二步:注册求解器支持的三类井及其压裂参数。
for (int nIndex = 0;
nIndex < oSnapshot.m_vecVerticalWells.size();
++nIndex)
{
nmDataVerticalWell& oWell = oSnapshot.m_vecVerticalWells[nIndex];
registerWell(pRegistry, oWell, wellCode(oWell, "VWELL", nIndex));
}
for (int nIndex = 0;
nIndex < oSnapshot.m_vecVerticalFracturedWells.size();
++nIndex)
{
nmDataVerticalFracturedWell& oWell =
oSnapshot.m_vecVerticalFracturedWells[nIndex];
QString sCode = wellCode(oWell, "VFWELL", nIndex);
registerWell(pRegistry, oWell, sCode);
pRegistry->regAttr(prefixed(sCode, "W_FractureHalfLength"),
&oWell.getFractureHalfLength());
pRegistry->regAttr(prefixed(sCode, "W_FractureAngle"),
&oWell.getFractureAngle());
pRegistry->regAttr(prefixed(sCode, "W_Dfc"), &oWell.getDfc());
}
for (int nIndex = 0;
nIndex < oSnapshot.m_vecHorizontalFracturedWells.size();
++nIndex)
{
nmDataHorizontalFracturedWell& oWell =
oSnapshot.m_vecHorizontalFracturedWells[nIndex];
QString sCode = wellCode(oWell, "HFWELL", nIndex);
registerWell(pRegistry, oWell, sCode);
pRegistry->regAttr(prefixed(sCode, "W_WellLength"),
&oWell.getWellLength());
pRegistry->regAttr(prefixed(sCode, "W_DrainAngle"),
&oWell.getDrainAngle());
pRegistry->regAttr(prefixed(sCode, "W_NumberOfFractures"),
&oWell.getNumberOfFractures());
pRegistry->regAttr(prefixed(sCode, "W_FractureHalfLength"),
&oWell.getFractureHalfLength());
pRegistry->regAttr(prefixed(sCode, "W_FractureAngle"),
&oWell.getFractureAngle());
pRegistry->regAttr(prefixed(sCode, "W_Dfc"), &oWell.getDfc());
}
// 第三步:注册区域标记和复合区数值参数。
for (int nIndex = 0;
nIndex < oSnapshot.m_vecRegionMarks.size();
++nIndex)
{
QString sCode = QString("RMARK%1")
.arg(nIndex + 1, 4, 10, QChar('0'));
nmDataRegionMark& oMark = oSnapshot.m_vecRegionMarks[nIndex];
pRegistry->regAttr(prefixed(sCode, "RM_ComW"), &oMark.getComW());
pRegistry->regAttr(prefixed(sCode, "RM_ComKr"), &oMark.getComKr());
pRegistry->regAttr(prefixed(sCode, "RM_NetToGross"),
&oMark.getNetToGross());
}
for (int nIndex = 0;
nIndex < oSnapshot.m_vecRegions.size();
++nIndex)
{
QString sCode = QString("REGION%1")
.arg(nIndex + 1, 4, 10, QChar('0'));
pRegistry->regAttr(prefixed(sCode, "R_Leakage"),
&oSnapshot.m_vecRegions[nIndex]
.getRegionLeakage());
}
// 第四步注册断层和普通裂缝DFN由专用功能维护。
for (int nIndex = 0;
nIndex < oSnapshot.m_vecFaults.size();
++nIndex)
{
QString sCode = QString("FAULT%1")
.arg(nIndex + 1, 4, 10, QChar('0'));
nmDataFault& oFault = oSnapshot.m_vecFaults[nIndex];
pRegistry->regAttr(prefixed(sCode, "FT_X0"), &oFault.getStartX());
pRegistry->regAttr(prefixed(sCode, "FT_Y0"), &oFault.getStartY());
pRegistry->regAttr(prefixed(sCode, "FT_X1"), &oFault.getEndX());
pRegistry->regAttr(prefixed(sCode, "FT_Y1"), &oFault.getEndY());
pRegistry->regAttr(prefixed(sCode, "FT_Leakage"),
&oFault.getFaultLeakage());
}
int nVisibleFracture = 0;
for (int nIndex = 0;
nIndex < oSnapshot.m_vecFractures.size();
++nIndex)
{
nmDataFracture& oFracture = oSnapshot.m_vecFractures[nIndex];
if (oFracture.getFractureType().getValue().toString() == "DFN")
{
continue;
}
QString sCode = QString("FRAC%1")
.arg(++nVisibleFracture, 4, 10, QChar('0'));
pRegistry->regAttr(prefixed(sCode, "F_X0"),
&oFracture.getStartX());
pRegistry->regAttr(prefixed(sCode, "F_Y0"),
&oFracture.getStartY());
pRegistry->regAttr(prefixed(sCode, "F_X1"),
&oFracture.getEndX());
pRegistry->regAttr(prefixed(sCode, "F_Y1"),
&oFracture.getEndY());
pRegistry->regAttr(prefixed(sCode, "F_FC"),
&oFracture.getFractureDfc());
pRegistry->regAttr(prefixed(sCode, "F_DW"),
&oFracture.getFractureDw());
}
}
QList<nmReservoirPropertyObjectInfo>
nmReservoirParameterCatalog::createObjectInfos(
nmReservoirPropertiesCategory eCategory,
nmReservoirPropertiesSnapshot& oSnapshot)
{
QList<nmReservoirPropertyObjectInfo> listInfos;
if (eCategory == NM_RESERVOIR_PAGE_RESERVOIR)
{
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_RESERVOIR;
oInfo.m_nSourceIndex = 0;
oInfo.m_sObjectCode = "RESERVOIR";
oInfo.m_sDisplayName = editorText("Reservoir parameters");
nmReservoirParameterGroupInfo oBasic =
parameterGroup("Basic properties", 0, 0, 1);
oBasic.m_nRowSpan = 2;
oBasic.m_bValueRightAligned = true;
oBasic.m_listFields
<< numericField("Pi", "Pi")
<< numericField("K", "K")
<< numericField("h", "h")
<< numericField("phi", "phi");
nmReservoirParameterGroupInfo oFluid =
parameterGroup("Fluid parameters", 0, 1, 1);
oFluid.m_listFields
<< reservoirModelField("ReservoirType")
<< numericField("Miuo", "Miuo")
<< unitlessNumericField("Bo", "Bo")
<< numericField("KxKy", "KxKy");
nmReservoirParameterGroupInfo oCompression =
parameterGroup("Compression and saturation", 1, 1, 1);
oCompression.m_listFields
<< numericField("Cti", "Cti")
<< numericField("Cf", "Cf")
<< numericField("Soi", "Soi")
<< numericField("Swi", "Swi");
oInfo.m_listGroups << oBasic << oFluid << oCompression;
listInfos.append(oInfo);
return listInfos;
}
if (eCategory == NM_RESERVOIR_PAGE_WELL)
{
for (int nIndex = 0;
nIndex < oSnapshot.m_vecVerticalWells.size();
++nIndex)
{
nmDataVerticalWell& oWell =
oSnapshot.m_vecVerticalWells[nIndex];
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_VERTICAL_WELL;
oInfo.m_nSourceIndex = nIndex;
oInfo.m_sObjectCode = wellCode(oWell, "VWELL", nIndex);
oInfo.m_sDisplayName = oWell.getWellName() + " - " +
editorText("Vertical well");
createWellGroups(oInfo, Vertical_Well,
oWell.getPerforationCount() > 0);
listInfos.append(oInfo);
}
for (int nIndex = 0;
nIndex < oSnapshot.m_vecVerticalFracturedWells.size();
++nIndex)
{
nmDataVerticalFracturedWell& oWell =
oSnapshot.m_vecVerticalFracturedWells[nIndex];
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType =
NM_RESERVOIR_OBJECT_VERTICAL_FRAC_WELL;
oInfo.m_nSourceIndex = nIndex;
oInfo.m_sObjectCode = wellCode(oWell, "VFWELL", nIndex);
oInfo.m_sDisplayName = oWell.getWellName() + " - " +
editorText("Vertical fractured well");
createWellGroups(oInfo, Vertical_Fractured_Well,
oWell.getPerforationCount() > 0);
listInfos.append(oInfo);
}
for (int nIndex = 0;
nIndex < oSnapshot.m_vecHorizontalFracturedWells.size();
++nIndex)
{
nmDataHorizontalFracturedWell& oWell =
oSnapshot.m_vecHorizontalFracturedWells[nIndex];
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType =
NM_RESERVOIR_OBJECT_HORIZONTAL_FRAC_WELL;
oInfo.m_nSourceIndex = nIndex;
oInfo.m_sObjectCode = wellCode(oWell, "HFWELL", nIndex);
oInfo.m_sDisplayName = oWell.getWellName() + " - " +
editorText("Horizontal fractured well");
createWellGroups(oInfo, Horizontal_Fractured_Well,
oWell.getPerforationCount() > 0);
listInfos.append(oInfo);
}
return listInfos;
}
if (eCategory == NM_RESERVOIR_PAGE_REGION_MARK)
{
for (int nIndex = 0;
nIndex < oSnapshot.m_vecRegionMarks.size();
++nIndex)
{
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_REGION_MARK;
oInfo.m_nSourceIndex = nIndex;
oInfo.m_sObjectCode = QString("RMARK%1")
.arg(nIndex + 1, 4, 10, QChar('0'));
oInfo.m_sDisplayName =
oSnapshot.m_vecRegionMarks[nIndex].getRegionMarkName();
nmReservoirParameterGroupInfo oDual =
parameterGroup("Dual-medium parameters", 0, 0, 2);
oDual.m_listFields
<< reservoirModelField(prefixed(
oInfo.m_sObjectCode, "RM_ReservoirType"))
<< numericField(prefixed(
oInfo.m_sObjectCode, "RM_ComW"), "RM_ComW")
<< numericField(prefixed(
oInfo.m_sObjectCode, "RM_ComKr"), "RM_ComKr")
<< numericField(prefixed(
oInfo.m_sObjectCode, "RM_NetToGross"),
"RM_NetToGross");
oInfo.m_listGroups.append(oDual);
listInfos.append(oInfo);
}
return listInfos;
}
if (eCategory == NM_RESERVOIR_PAGE_FAULT)
{
for (int nIndex = 0;
nIndex < oSnapshot.m_vecFaults.size();
++nIndex)
{
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_FAULT;
oInfo.m_nSourceIndex = nIndex;
oInfo.m_sObjectCode = QString("FAULT%1")
.arg(nIndex + 1, 4, 10, QChar('0'));
oInfo.m_sDisplayName =
oSnapshot.m_vecFaults[nIndex].getFaultName();
nmReservoirParameterGroupInfo oGeometry =
parameterGroup("Geometry", 0, 0, 1);
oGeometry.m_listFields
<< numericField(prefixed(
oInfo.m_sObjectCode, "FT_X0"), "FT_X0")
<< numericField(prefixed(
oInfo.m_sObjectCode, "FT_Y0"), "FT_Y0")
<< numericField(prefixed(
oInfo.m_sObjectCode, "FT_X1"), "FT_X1")
<< numericField(prefixed(
oInfo.m_sObjectCode, "FT_Y1"), "FT_Y1");
nmReservoirParameterGroupInfo oFlow =
parameterGroup("Flow properties", 0, 1, 1);
oFlow.m_listFields
<< leakageModelField(prefixed(
oInfo.m_sObjectCode, "FT_FlowModel"),
"Fault flow model")
<< numericField(prefixed(
oInfo.m_sObjectCode, "FT_Leakage"),
"FT_Leakage");
oInfo.m_listGroups << oGeometry << oFlow;
listInfos.append(oInfo);
}
return listInfos;
}
if (eCategory == NM_RESERVOIR_PAGE_FRACTURE)
{
int nVisibleFracture = 0;
for (int nIndex = 0;
nIndex < oSnapshot.m_vecFractures.size();
++nIndex)
{
nmDataFracture& oFracture =
oSnapshot.m_vecFractures[nIndex];
if (oFracture.getFractureType().getValue().toString() ==
"DFN")
{
continue;
}
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_FRACTURE;
oInfo.m_nSourceIndex = nIndex;
oInfo.m_sObjectCode = QString("FRAC%1")
.arg(++nVisibleFracture, 4, 10, QChar('0'));
oInfo.m_sDisplayName = oFracture.getFractureName();
nmReservoirParameterGroupInfo oGeometry =
parameterGroup("Geometry", 0, 0, 1);
oGeometry.m_listFields
<< numericField(prefixed(
oInfo.m_sObjectCode, "F_X0"), "F_X0")
<< numericField(prefixed(
oInfo.m_sObjectCode, "F_Y0"), "F_Y0")
<< numericField(prefixed(
oInfo.m_sObjectCode, "F_X1"), "F_X1")
<< numericField(prefixed(
oInfo.m_sObjectCode, "F_Y1"), "F_Y1");
nmReservoirParameterGroupInfo oFlow =
parameterGroup("Conductivity properties", 0, 1, 1);
oFlow.m_listFields
<< fractureModelField(prefixed(
oInfo.m_sObjectCode, "F_FlowModel"))
<< numericField(prefixed(
oInfo.m_sObjectCode, "F_FC"), "F_FC")
<< numericField(prefixed(
oInfo.m_sObjectCode, "F_DW"), "F_DW");
oInfo.m_listGroups << oGeometry << oFlow;
listInfos.append(oInfo);
}
return listInfos;
}
if (eCategory == NM_RESERVOIR_PAGE_COMPOSITE_REGION)
{
for (int nIndex = 0;
nIndex < oSnapshot.m_vecRegions.size();
++nIndex)
{
nmReservoirPropertyObjectInfo oInfo;
oInfo.m_eCategory = eCategory;
oInfo.m_eObjectType = NM_RESERVOIR_OBJECT_REGION;
oInfo.m_nSourceIndex = nIndex;
oInfo.m_sObjectCode = QString("REGION%1")
.arg(nIndex + 1, 4, 10, QChar('0'));
oInfo.m_sDisplayName =
oSnapshot.m_vecRegions[nIndex].getRegoinName();
nmReservoirParameterGroupInfo oRegion =
parameterGroup("Region properties", 0, 0, 2);
oRegion.m_listFields
<< leakageModelField(prefixed(
oInfo.m_sObjectCode, "R_FlowModel"),
"Region flow model")
<< numericField(prefixed(
oInfo.m_sObjectCode, "R_Leakage"),
"R_Leakage");
oInfo.m_listGroups.append(oRegion);
listInfos.append(oInfo);
}
}
return listInfos;
}
QString nmReservoirParameterCatalog::enumCodeFromStoredValue(
const QString& sParameterId,
const QString& sStoredValue)
{
QString sValue = sStoredValue.trimmed().toLower();
if (sParameterId.endsWith("ReservoirType"))
{
return sValue.contains("dual")
? "dual_porosity_pss" : "homogeneous";
}
if (sParameterId.endsWith("F_FlowModel"))
{
return sValue.contains("infinite")
? "infinite_conductivity" : "finite_conductivity";
}
if (sParameterId.endsWith("FlowModel"))
{
return sValue.contains("leaky") ? "leaky" : "composite_limit";
}
return QString();
}
QString nmReservoirParameterCatalog::storedValueFromEnumCode(
const QString& sParameterId,
const QString& sCode)
{
if (sParameterId.endsWith("ReservoirType"))
{
return sCode == "dual_porosity_pss"
? "Dual porosity pseudo steady state" : "Homogeneous";
}
if (sParameterId.endsWith("F_FlowModel"))
{
return sCode == "infinite_conductivity"
? "Infinite Conductivity" : "Finite Conductivity";
}
if (sParameterId.endsWith("FlowModel"))
{
return sCode == "leaky" ? "Leaky" : "Composite limit";
}
return QString();
}
QString nmReservoirParameterCatalog::enumCode(
const nmReservoirPropertiesSnapshot& oSnapshot,
const QString& sParameterId)
{
if (sParameterId == "ReservoirType")
{
nmDataReservoir& oReservoir =
const_cast<nmDataReservoir&>(oSnapshot.m_oReservoir);
return enumCodeFromStoredValue(
sParameterId,
oReservoir.getReservoirType().getValue().toString());
}
QList<nmReservoirPropertyObjectInfo> listAll;
nmReservoirPropertiesSnapshot& oWritable =
const_cast<nmReservoirPropertiesSnapshot&>(oSnapshot);
listAll << createObjectInfos(NM_RESERVOIR_PAGE_FAULT,
oWritable)
<< createObjectInfos(NM_RESERVOIR_PAGE_FRACTURE,
oWritable)
<< createObjectInfos(NM_RESERVOIR_PAGE_COMPOSITE_REGION,
oWritable)
<< createObjectInfos(NM_RESERVOIR_PAGE_REGION_MARK,
oWritable);
for (int nInfoIndex = 0;
nInfoIndex < listAll.size();
++nInfoIndex)
{
const nmReservoirPropertyObjectInfo& oInfo =
listAll[nInfoIndex];
if (!containsEnumField(oInfo, sParameterId))
{
continue;
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION_MARK)
{
nmDataRegionMark& oMark =
oWritable.m_vecRegionMarks[oInfo.m_nSourceIndex];
return enumCodeFromStoredValue(
sParameterId,
oMark.getReservoirType().getValue().toString());
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FAULT)
{
nmDataFault& oFault =
oWritable.m_vecFaults[oInfo.m_nSourceIndex];
return enumCodeFromStoredValue(
sParameterId,
oFault.getFaultFlowModel().getValue().toString());
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FRACTURE)
{
nmDataFracture& oFracture =
oWritable.m_vecFractures[oInfo.m_nSourceIndex];
return enumCodeFromStoredValue(
sParameterId,
oFracture.getFractureFlowModel()
.getValue().toString());
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION)
{
nmDataRegion& oRegion =
oWritable.m_vecRegions[oInfo.m_nSourceIndex];
return enumCodeFromStoredValue(
sParameterId,
oRegion.getRegionFlowModel().getValue().toString());
}
}
return QString();
}
bool nmReservoirParameterCatalog::setEnumCode(
nmReservoirPropertiesSnapshot& oSnapshot,
const QString& sParameterId,
const QString& sCode)
{
QString sStoredValue =
storedValueFromEnumCode(sParameterId, sCode);
if (sStoredValue.isEmpty())
{
return false;
}
if (sParameterId == "ReservoirType")
{
oSnapshot.m_oReservoir.getReservoirType()
.setValue(sStoredValue);
return true;
}
QList<nmReservoirPropertyObjectInfo> listAll;
listAll << createObjectInfos(NM_RESERVOIR_PAGE_FAULT,
oSnapshot)
<< createObjectInfos(NM_RESERVOIR_PAGE_FRACTURE,
oSnapshot)
<< createObjectInfos(NM_RESERVOIR_PAGE_COMPOSITE_REGION,
oSnapshot)
<< createObjectInfos(NM_RESERVOIR_PAGE_REGION_MARK,
oSnapshot);
for (int nInfoIndex = 0;
nInfoIndex < listAll.size();
++nInfoIndex)
{
const nmReservoirPropertyObjectInfo& oInfo =
listAll[nInfoIndex];
if (!containsEnumField(oInfo, sParameterId))
{
continue;
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION_MARK)
{
oSnapshot.m_vecRegionMarks[oInfo.m_nSourceIndex]
.getReservoirType().setValue(sStoredValue);
return true;
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FAULT)
{
oSnapshot.m_vecFaults[oInfo.m_nSourceIndex]
.getFaultFlowModel().setValue(sStoredValue);
return true;
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FRACTURE)
{
oSnapshot.m_vecFractures[oInfo.m_nSourceIndex]
.getFractureFlowModel().setValue(sStoredValue);
return true;
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION)
{
oSnapshot.m_vecRegions[oInfo.m_nSourceIndex]
.getRegionFlowModel().setValue(sStoredValue);
return true;
}
}
return false;
}

@ -0,0 +1,429 @@
#include "nmReservoirParameterField.h"
#include "iParaItemCtrl.h"
#include "iParameter.h"
#include "iSysParaHelper.h"
#include "mModuleDefines.h"
#include <QComboBox>
#include <QCoreApplication>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSizePolicy>
namespace
{
/* Qt 4.8的lupdate需要显式标记间接翻译字符串。 */
static const char* const s_pFieldTranslationMarkers[] =
{
QT_TRANSLATE_NOOP("nmReservoirParameterField", "The parameter layout is unavailable."),
QT_TRANSLATE_NOOP("nmReservoirParameterField", "Parameter definition not found: %1"),
QT_TRANSLATE_NOOP("nmReservoirParameterField", "Failed to bind the unit for parameter: %1")
};
/** @brief 返回轻量字段类使用的翻译文字。 */
QString fieldText(const char* pText)
{
Q_UNUSED(s_pFieldTranslationMarkers);
return QCoreApplication::translate(
"nmReservoirParameterField", pText);
}
}
nmReservoirParameterField::nmReservoirParameterField(
const nmReservoirParameterFieldInfo& oFieldInfo,
nmIReservoirParameterUnitService* pUnitService,
QObject* pParent)
: QObject(pParent),
m_oFieldInfo(oFieldInfo),
m_pUnitService(pUnitService),
m_pParameter(nullptr),
m_pLabel(nullptr),
m_pLineEdit(nullptr),
m_pValueComboBox(nullptr),
m_pUnitComboBox(nullptr),
m_pUnitLabel(nullptr),
m_bVisible(true),
m_bUpdating(false)
{
}
nmReservoirParameterField::~nmReservoirParameterField()
{
m_pUnitService = nullptr;
m_pParameter = nullptr;
m_pLabel = nullptr;
m_pLineEdit = nullptr;
m_pValueComboBox = nullptr;
m_pUnitComboBox = nullptr;
m_pUnitLabel = nullptr;
}
bool nmReservoirParameterField::initialize(QGridLayout* pLayout,
int nRow,
QString& sError)
{
if (pLayout == nullptr)
{
sError = fieldText("The parameter layout is unavailable.");
return false;
}
// 第一步枚举字段不依赖XML数值定义直接创建稳定code下拉框。
if (isEnum())
{
createEnumEditor(pLayout, nRow);
sError.clear();
return true;
}
// 第二步数值字段必须取得NM系列的基础参数定义。
m_pParameter = _paraHelper->getPara(
m_oFieldInfo.m_sBaseParameterId, s_Nm_Serie);
if (m_pParameter == nullptr)
{
sError = fieldText("Parameter definition not found: %1")
.arg(m_oFieldInfo.m_sBaseParameterId);
return false;
}
// 第三步:创建原型中的标签、输入框和单位列,不增加字段外框。
return createNumericEditor(pLayout, nRow, sError);
}
QString nmReservoirParameterField::parameterId() const
{
return m_oFieldInfo.m_sParameterId;
}
QString nmReservoirParameterField::baseParameterId() const
{
return m_oFieldInfo.m_sBaseParameterId;
}
bool nmReservoirParameterField::isEnum() const
{
return m_oFieldInfo.m_eEditorType == NM_RESERVOIR_EDITOR_ENUM;
}
void nmReservoirParameterField::setValueRightAligned(bool bRightAligned)
{
if (m_pLineEdit != nullptr)
{
m_pLineEdit->setAlignment(
(bRightAligned ? Qt::AlignRight : Qt::AlignLeft) |
Qt::AlignVCenter);
}
}
int nmReservoirParameterField::editorMinimumWidth()
{
return 140;
}
int nmReservoirParameterField::unitMinimumWidth()
{
return 86;
}
int nmReservoirParameterField::editorHeight()
{
return 25;
}
bool nmReservoirParameterField::createNumericEditor(
QGridLayout* pLayout,
int nRow,
QString& sError)
{
QWidget* pParentWidget = pLayout->parentWidget();
m_pLabel = new QLabel(m_pParameter->m_sAlias, pParentWidget);
m_pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
m_pLabel->setWordWrap(false);
m_pLabel->setSizePolicy(QSizePolicy::Preferred,
QSizePolicy::Fixed);
m_pLineEdit = new QLineEdit(pParentWidget);
m_pLineEdit->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_pLineEdit->setFixedHeight(editorHeight());
m_pLineEdit->setMinimumWidth(editorMinimumWidth());
m_pLineEdit->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Fixed);
pLayout->addWidget(m_pLabel, nRow, 0);
pLayout->addWidget(m_pLineEdit, nRow, 1);
// 有单位参数直接注册到主对话框绑定器;字段只按控件指针读写,
// 因此多口井共用同一基础参数ID时也不会发生串值。
if (m_oFieldInfo.m_bUnitEnabled &&
!m_pParameter->m_sUnit.isEmpty())
{
m_pUnitComboBox = new QComboBox(pParentWidget);
m_pUnitComboBox->setFixedHeight(editorHeight());
m_pUnitComboBox->setMinimumWidth(unitMinimumWidth());
m_pUnitComboBox->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Fixed);
pLayout->addWidget(m_pUnitComboBox, nRow, 2);
if (m_pUnitService == nullptr ||
!m_pUnitService->bindUnitField(
m_pLineEdit,
m_pUnitComboBox,
m_oFieldInfo.m_sBaseParameterId))
{
sError = fieldText(
"Failed to bind the unit for parameter: %1")
.arg(m_oFieldInfo.m_sBaseParameterId);
return false;
}
}
else
{
// 无单位参数保留空列用于对齐,不显示额外的单位说明文字。
m_pUnitLabel = new QLabel(pParentWidget);
m_pUnitLabel->setMinimumWidth(unitMinimumWidth());
m_pUnitLabel->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Fixed);
pLayout->addWidget(m_pUnitLabel, nRow, 2);
}
updateValidationAppearance(true);
sError.clear();
return true;
}
void nmReservoirParameterField::createEnumEditor(QGridLayout* pLayout,
int nRow)
{
QWidget* pParentWidget = pLayout->parentWidget();
m_pLabel = new QLabel(m_oFieldInfo.m_sLabel, pParentWidget);
m_pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
m_pLabel->setWordWrap(false);
m_pLabel->setSizePolicy(QSizePolicy::Preferred,
QSizePolicy::Fixed);
m_pValueComboBox = new QComboBox(pParentWidget);
m_pValueComboBox->setFixedHeight(editorHeight());
m_pValueComboBox->setMinimumWidth(editorMinimumWidth());
m_pValueComboBox->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Fixed);
for (int nIndex = 0;
nIndex < m_oFieldInfo.m_listOptions.size();
++nIndex)
{
const nmReservoirEnumOption& oOption =
m_oFieldInfo.m_listOptions[nIndex];
m_pValueComboBox->addItem(oOption.m_sText, oOption.m_sCode);
}
// 枚举参数没有单位,空白单位列仅用于保持各行编辑器对齐。
m_pUnitLabel = new QLabel(pParentWidget);
m_pUnitLabel->setMinimumWidth(unitMinimumWidth());
m_pUnitLabel->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Fixed);
pLayout->addWidget(m_pLabel, nRow, 0);
pLayout->addWidget(m_pValueComboBox, nRow, 1);
pLayout->addWidget(m_pUnitLabel, nRow, 2);
}
void nmReservoirParameterField::setBaseValue(const QVariant& oValue)
{
if (m_pLineEdit == nullptr || !oValue.isValid())
{
return;
}
m_bUpdating = true;
double dValue = oValue.toDouble();
if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr)
{
m_pUnitService->setUnitBaseValue(m_pLineEdit, dValue);
}
else
{
m_pLineEdit->setText(formatBaseValue(dValue));
}
m_bUpdating = false;
validate();
}
QVariant nmReservoirParameterField::baseValue(bool& bValid)
{
bValid = validate();
if (!bValid || m_pLineEdit == nullptr)
{
return QVariant();
}
if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr)
{
return QVariant(m_pUnitService->unitBaseValue(m_pLineEdit));
}
bool bNumberOk = false;
double dValue = m_pLineEdit->text().trimmed().toDouble(&bNumberOk);
return bNumberOk ? QVariant(dValue) : QVariant();
}
void nmReservoirParameterField::setEnumCode(const QString& sCode)
{
if (m_pValueComboBox == nullptr)
{
return;
}
int nIndex = m_pValueComboBox->findData(sCode);
m_bUpdating = true;
m_pValueComboBox->setCurrentIndex(nIndex >= 0 ? nIndex : 0);
m_bUpdating = false;
}
QString nmReservoirParameterField::enumCode() const
{
if (m_pValueComboBox == nullptr ||
m_pValueComboBox->currentIndex() < 0)
{
return QString();
}
return m_pValueComboBox->itemData(
m_pValueComboBox->currentIndex()).toString();
}
bool nmReservoirParameterField::validate()
{
if (!m_bVisible || isEnum())
{
return true;
}
if (m_pLineEdit == nullptr || m_pParameter == nullptr)
{
return false;
}
bool bValid = false;
if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr)
{
bValid = m_pUnitService->isUnitInputValid(m_pLineEdit);
}
else
{
bool bNumberOk = false;
double dValue = m_pLineEdit->text().trimmed()
.toDouble(&bNumberOk);
bValid = bNumberOk && dValue == dValue &&
dValue >= m_pParameter->m_dMin &&
dValue <= m_pParameter->m_dMax;
if (bValid &&
m_oFieldInfo.m_eEditorType == NM_RESERVOIR_EDITOR_INTEGER)
{
bValid = dValue == static_cast<int>(dValue);
}
}
updateValidationAppearance(bValid);
return bValid;
}
void nmReservoirParameterField::setFieldVisible(bool bVisible)
{
m_bVisible = bVisible;
if (m_pLabel != nullptr)
{
m_pLabel->setVisible(bVisible);
}
if (m_pLineEdit != nullptr)
{
m_pLineEdit->setVisible(bVisible);
}
if (m_pValueComboBox != nullptr)
{
m_pValueComboBox->setVisible(bVisible);
}
if (m_pUnitComboBox != nullptr)
{
m_pUnitComboBox->setVisible(bVisible);
}
if (m_pUnitLabel != nullptr)
{
m_pUnitLabel->setVisible(bVisible);
}
}
bool nmReservoirParameterField::isFieldVisible() const
{
return m_bVisible;
}
QLineEdit* nmReservoirParameterField::valueEditor() const
{
return m_pLineEdit;
}
QComboBox* nmReservoirParameterField::enumEditor() const
{
return m_pValueComboBox;
}
QString nmReservoirParameterField::formatBaseValue(double dValue) const
{
if (m_pParameter == nullptr)
{
return QString::number(dValue, 'g', 15);
}
if (m_oFieldInfo.m_eEditorType == NM_RESERVOIR_EDITOR_INTEGER ||
m_pParameter->m_nDigit == 0)
{
return QString::number(dValue, 'f', 0);
}
if (m_pParameter->m_bScientific)
{
return QString::number(dValue, 'e', m_pParameter->m_nDigit);
}
QString sValue = QString::number(
dValue, 'f', m_pParameter->m_nDigit);
while (sValue.contains('.') && sValue.endsWith('0'))
{
sValue.chop(1);
}
if (sValue.endsWith('.'))
{
sValue.chop(1);
}
return sValue;
}
void nmReservoirParameterField::updateValidationAppearance(bool bValid)
{
Q_UNUSED(bValid);
if (m_pLineEdit == nullptr || m_pParameter == nullptr)
{
return;
}
QString sCurrentUnit;
iUnitGroup* pUnitGroup = nullptr;
if (m_pUnitComboBox != nullptr && m_pUnitService != nullptr)
{
sCurrentUnit = m_pUnitService->currentUnit(m_pLineEdit);
pUnitGroup = m_pUnitService->unitGroup(m_pLineEdit);
}
QString sRange = iParaItemCtrl::getScaleStrOf(
m_pParameter, sCurrentUnit, pUnitGroup);
QString sHelp = m_pParameter->m_sDesc;
if (!sRange.isEmpty())
{
if (!sHelp.isEmpty())
{
sHelp += "\n";
}
sHelp += sRange;
}
m_pLineEdit->setToolTip(sHelp);
m_pLineEdit->setWhatsThis(sHelp);
if (m_pLabel != nullptr)
{
m_pLabel->setWhatsThis(sHelp);
}
}

@ -0,0 +1,539 @@
#include "nmReservoirParameterPage.h"
#include "nmReservoirParameterField.h"
#include "nmReservoirPropertiesSession.h"
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QSizePolicy>
#include <QVBoxLayout>
nmReservoirParameterPage::nmReservoirParameterPage(
nmReservoirPropertiesCategory eCategory,
nmReservoirPropertiesSession* pSession,
nmIReservoirParameterUnitService* pUnitService,
QWidget* pParent)
: QWidget(pParent),
m_eCategory(eCategory),
m_pSession(pSession),
m_pUnitService(pUnitService),
m_nCurrentObjectIndex(-1),
m_bUpdatingObject(false),
m_bBuildingFields(false),
m_pTitleLabel(nullptr),
m_pObjectLabel(nullptr),
m_pObjectComboBox(nullptr),
m_pGroupsWidget(nullptr),
m_pGroupsLayout(nullptr),
m_pMainLayout(nullptr),
m_pEmptyLabel(nullptr)
{
createUi();
}
nmReservoirParameterPage::~nmReservoirParameterPage()
{
m_listFields.clear();
m_mapFields.clear();
m_mapEditorFields.clear();
m_pSession = nullptr;
m_pUnitService = nullptr;
}
void nmReservoirParameterPage::createUi()
{
// 第一步:创建原型中的单层内容头,储层页自动隐藏对象选择器。
m_pMainLayout = new QVBoxLayout(this);
m_pMainLayout->setContentsMargins(8, 6, 8, 8);
m_pMainLayout->setSpacing(8);
QWidget* pHeaderWidget = new QWidget(this);
QHBoxLayout* pHeaderLayout = new QHBoxLayout(pHeaderWidget);
pHeaderLayout->setContentsMargins(8, 4, 8, 4);
pHeaderLayout->setSpacing(9);
m_pTitleLabel = new QLabel(categoryTitle(), pHeaderWidget);
pHeaderLayout->addWidget(m_pTitleLabel, 1);
m_pObjectLabel = new QLabel(objectSelectorLabel(), pHeaderWidget);
m_pObjectComboBox = new QComboBox(pHeaderWidget);
m_pObjectComboBox->setMinimumWidth(260);
m_pObjectComboBox->setMinimumHeight(27);
pHeaderLayout->addWidget(m_pObjectLabel);
pHeaderLayout->addWidget(m_pObjectComboBox);
// 储层页只有一个固定对象,顶部分类按钮已经说明当前页面,
// 不再重复显示无操作价值的页标题;其他页面保留对象选择区。
if (m_eCategory == NM_RESERVOIR_PAGE_RESERVOIR)
{
pHeaderWidget->hide();
}
else
{
m_pMainLayout->addWidget(pHeaderWidget);
}
// 第二步:分组载体没有边框,仅用于实现原型中的两列网格。
m_pGroupsWidget = new QWidget(this);
m_pGroupsLayout = new QGridLayout(m_pGroupsWidget);
m_pGroupsLayout->setContentsMargins(0, 0, 0, 0);
m_pGroupsLayout->setHorizontalSpacing(14);
m_pGroupsLayout->setVerticalSpacing(14);
m_pGroupsLayout->setColumnStretch(0, 1);
m_pGroupsLayout->setColumnStretch(1, 1);
m_pMainLayout->addWidget(m_pGroupsWidget);
m_pMainLayout->addStretch(1);
connect(m_pObjectComboBox, SIGNAL(currentIndexChanged(int)),
this, SLOT(slotObjectChanged(int)));
}
QString nmReservoirParameterPage::categoryTitle() const
{
switch (m_eCategory)
{
case NM_RESERVOIR_PAGE_RESERVOIR:
return tr("Reservoir parameters");
case NM_RESERVOIR_PAGE_WELL:
return tr("Well parameters");
case NM_RESERVOIR_PAGE_FAULT:
return tr("Fault parameters");
case NM_RESERVOIR_PAGE_FRACTURE:
return tr("Fracture parameters");
case NM_RESERVOIR_PAGE_COMPOSITE_REGION:
return tr("Composite region parameters");
case NM_RESERVOIR_PAGE_REGION_MARK:
return tr("Region mark parameters");
default:
return QString();
}
}
QString nmReservoirParameterPage::objectSelectorLabel() const
{
switch (m_eCategory)
{
case NM_RESERVOIR_PAGE_WELL:
return tr("Select well:");
case NM_RESERVOIR_PAGE_FAULT:
return tr("Select fault:");
case NM_RESERVOIR_PAGE_FRACTURE:
return tr("Select fracture:");
case NM_RESERVOIR_PAGE_COMPOSITE_REGION:
return tr("Select composite region:");
case NM_RESERVOIR_PAGE_REGION_MARK:
return tr("Select region mark:");
default:
return tr("Select object:");
}
}
bool nmReservoirParameterPage::reloadObjects(QString& sError)
{
// 第一步重新取得会话对象描述并把稳定对象code放入UserRole。
m_listObjectInfos = m_pSession != nullptr
? m_pSession->objectInfos(m_eCategory)
: QList<nmReservoirPropertyObjectInfo>();
m_bUpdatingObject = true;
m_pObjectComboBox->clear();
for (int nIndex = 0; nIndex < m_listObjectInfos.size(); ++nIndex)
{
const nmReservoirPropertyObjectInfo& oInfo =
m_listObjectInfos[nIndex];
m_pObjectComboBox->addItem(oInfo.m_sDisplayName,
oInfo.m_sObjectCode);
}
m_bUpdatingObject = false;
bool bShowSelector = m_eCategory != NM_RESERVOIR_PAGE_RESERVOIR;
m_pObjectLabel->setVisible(bShowSelector);
m_pObjectComboBox->setVisible(bShowSelector);
// 第二步:构建首个对象;空分类显示无额外面板的空状态。
return buildObject(m_listObjectInfos.isEmpty() ? -1 : 0, sError);
}
bool nmReservoirParameterPage::hasEditableObjects() const
{
return !m_listObjectInfos.isEmpty();
}
void nmReservoirParameterPage::slotObjectChanged(int nIndex)
{
if (m_bUpdatingObject || nIndex == m_nCurrentObjectIndex)
{
return;
}
// 第一步:离开旧对象前校验并保存,非法输入时回退对象选择。
int nPreviousIndex = m_nCurrentObjectIndex;
if (nPreviousIndex >= 0 && !commitPendingEdits())
{
m_bUpdatingObject = true;
m_pObjectComboBox->setCurrentIndex(nPreviousIndex);
m_bUpdatingObject = false;
return;
}
// 第二步:构建新对象;目录已在初始化阶段验证,正常切换不会失败。
QString sError;
if (!buildObject(nIndex, sError))
{
m_bUpdatingObject = true;
m_pObjectComboBox->setCurrentIndex(nPreviousIndex);
m_bUpdatingObject = false;
}
}
bool nmReservoirParameterPage::buildObject(int nObjectIndex,
QString& sError)
{
m_bBuildingFields = true;
clearObjectEditors();
m_nCurrentObjectIndex = nObjectIndex;
if (nObjectIndex < 0 || nObjectIndex >= m_listObjectInfos.size())
{
m_pEmptyLabel = new QLabel(
tr("No editable objects in this category."),
m_pGroupsWidget);
m_pEmptyLabel->setAlignment(Qt::AlignCenter);
m_pEmptyLabel->setMinimumHeight(260);
m_pGroupsLayout->addWidget(m_pEmptyLabel, 0, 0, 1, 2);
m_bBuildingFields = false;
sError.clear();
updateValidity();
return true;
}
// 第一步:按目录直接创建分组,页面不再包含对象类型专用布局代码。
const nmReservoirPropertyObjectInfo& oObjectInfo =
m_listObjectInfos[nObjectIndex];
for (int nGroupIndex = 0;
nGroupIndex < oObjectInfo.m_listGroups.size();
++nGroupIndex)
{
if (!buildGroup(oObjectInfo.m_listGroups[nGroupIndex], sError))
{
m_bBuildingFields = false;
return false;
}
}
// 第二步:加载工作副本,随后只调整行可见性,不重建页面。
for (int nFieldIndex = 0;
nFieldIndex < m_listFields.size();
++nFieldIndex)
{
loadFieldValue(m_listFields[nFieldIndex]);
}
applyVisibilityRules();
m_bBuildingFields = false;
sError.clear();
updateValidity();
return true;
}
void nmReservoirParameterPage::clearObjectEditors()
{
m_listFields.clear();
m_mapFields.clear();
m_mapEditorFields.clear();
m_pEmptyLabel = nullptr;
// 分组载体本身保留,只删除其直接子控件和布局项。
while (m_pGroupsLayout->count() > 0)
{
QLayoutItem* pItem = m_pGroupsLayout->takeAt(0);
if (pItem->widget() != nullptr)
{
delete pItem->widget();
}
delete pItem;
}
}
bool nmReservoirParameterPage::buildGroup(
const nmReservoirParameterGroupInfo& oGroupInfo,
QString& sError)
{
// 承载器始终横向铺满网格单元;只通过内部弹簧控制垂直位置,
// 避免直接设置对齐标志后分组框退回建议宽度。
QWidget* pGroupContainer = new QWidget(m_pGroupsWidget);
pGroupContainer->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
QVBoxLayout* pGroupContainerLayout =
new QVBoxLayout(pGroupContainer);
pGroupContainerLayout->setContentsMargins(0, 0, 0, 0);
pGroupContainerLayout->setSpacing(0);
QGroupBox* pGroupBox = new QGroupBox(
oGroupInfo.m_sTitle, pGroupContainer);
// 参数框只在水平方向随页面宽度调整,垂直方向始终采用内容建议高度,
// 避免跨行分组被网格拉伸成大面积空白框。
pGroupBox->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Fixed);
QGridLayout* pFieldLayout = new QGridLayout(pGroupBox);
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, nmReservoirParameterField::editorMinimumWidth());
pFieldLayout->setColumnMinimumWidth(
2, nmReservoirParameterField::unitMinimumWidth());
// 每个字段只把三个直接控件放入此网格,不产生字段外框或子面板。
for (int nFieldIndex = 0;
nFieldIndex < oGroupInfo.m_listFields.size();
++nFieldIndex)
{
nmReservoirParameterField* pField =
new nmReservoirParameterField(
oGroupInfo.m_listFields[nFieldIndex],
m_pUnitService,
pGroupBox);
if (!pField->initialize(pFieldLayout, nFieldIndex, sError))
{
delete pGroupContainer;
return false;
}
pField->setValueRightAligned(
oGroupInfo.m_bValueRightAligned);
if (pField->valueEditor() != nullptr)
{
connect(pField->valueEditor(), SIGNAL(textChanged(QString)),
this, SLOT(slotFieldValueEdited()));
m_mapEditorFields.insert(pField->valueEditor(), pField);
}
if (pField->enumEditor() != nullptr)
{
connect(pField->enumEditor(), SIGNAL(currentIndexChanged(int)),
this, SLOT(slotEnumIndexChanged(int)));
m_mapEditorFields.insert(pField->enumEditor(), pField);
}
m_listFields.append(pField);
m_mapFields.insert(pField->parameterId(), pField);
}
// 跨越多行的分组在承载区域垂直居中,普通分组保持顶部排列。
if (oGroupInfo.m_nRowSpan > 1)
{
pGroupContainerLayout->addStretch(1);
}
pGroupContainerLayout->addWidget(pGroupBox);
pGroupContainerLayout->addStretch(1);
m_pGroupsLayout->addWidget(
pGroupContainer,
oGroupInfo.m_nRow,
oGroupInfo.m_nColumn,
oGroupInfo.m_nRowSpan,
oGroupInfo.m_nColumnSpan);
return true;
}
void nmReservoirParameterPage::loadFieldValue(
nmReservoirParameterField* pField)
{
if (pField == nullptr || m_pSession == nullptr)
{
return;
}
if (pField->isEnum())
{
pField->setEnumCode(m_pSession->enumCode(
pField->parameterId()));
}
else
{
pField->setBaseValue(m_pSession->value(
pField->parameterId()));
}
}
bool nmReservoirParameterPage::commitPendingEdits()
{
if (m_pSession == nullptr)
{
return false;
}
// 第一步:先完整校验,避免部分字段已写入而后续字段失败。
if (!isInputValid())
{
updateValidity();
return false;
}
// 第二步:逐行按编辑器指针获取基准值,再统一写入工作副本。
QMap<QString, QVariant> mapValues;
for (int nFieldIndex = 0;
nFieldIndex < m_listFields.size();
++nFieldIndex)
{
nmReservoirParameterField* pField =
m_listFields[nFieldIndex];
if (pField == nullptr || pField->isEnum() ||
!pField->isFieldVisible())
{
continue;
}
bool bValid = false;
QVariant oValue = pField->baseValue(bValid);
if (!bValid || !oValue.isValid())
{
updateValidity();
return false;
}
mapValues.insert(pField->parameterId(), oValue);
}
m_pSession->setValues(mapValues);
updateValidity();
return true;
}
bool nmReservoirParameterPage::isInputValid()
{
for (int nFieldIndex = 0;
nFieldIndex < m_listFields.size();
++nFieldIndex)
{
nmReservoirParameterField* pField =
m_listFields[nFieldIndex];
if (pField != nullptr && pField->isFieldVisible() &&
!pField->validate())
{
return false;
}
}
return true;
}
void nmReservoirParameterPage::slotFieldValueEdited()
{
if (!m_bBuildingFields)
{
nmReservoirParameterField* pField =
m_mapEditorFields.value(sender(), nullptr);
if (pField != nullptr)
{
pField->validate();
}
updateValidity();
}
}
void nmReservoirParameterPage::slotEnumIndexChanged(int nIndex)
{
Q_UNUSED(nIndex);
if (m_bBuildingFields || m_pSession == nullptr)
{
return;
}
nmReservoirParameterField* pField =
m_mapEditorFields.value(sender(), nullptr);
if (pField == nullptr)
{
return;
}
QString sParameterId = pField->parameterId();
// 第一步联动前保存当前可见数值非法时回滚枚举code。
QString sPreviousCode = m_pSession->enumCode(sParameterId);
if (!commitPendingEdits())
{
m_bBuildingFields = true;
pField->setEnumCode(sPreviousCode);
m_bBuildingFields = false;
return;
}
// 第二步只用稳定code更新工作副本显示文字不参与业务判断。
if (!m_pSession->setEnumCode(sParameterId, pField->enumCode()))
{
m_bBuildingFields = true;
pField->setEnumCode(sPreviousCode);
m_bBuildingFields = false;
return;
}
// 第三步:仅显示或隐藏行,隐藏参数继续保留在工作副本中。
applyVisibilityRules();
updateValidity();
}
void nmReservoirParameterPage::applyVisibilityRules()
{
if (m_pSession == nullptr || m_nCurrentObjectIndex < 0 ||
m_nCurrentObjectIndex >= m_listObjectInfos.size())
{
return;
}
const nmReservoirPropertyObjectInfo& oInfo =
m_listObjectInfos[m_nCurrentObjectIndex];
// 区域标记为均质储层时隐藏双重介质参数。
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION_MARK)
{
QString sModelId = oInfo.m_sObjectCode +
"_RM_ReservoirType";
bool bDual = m_pSession->enumCode(sModelId) != "homogeneous";
setFieldVisible(oInfo.m_sObjectCode + "_RM_ComW", bDual);
setFieldVisible(oInfo.m_sObjectCode + "_RM_ComKr", bDual);
setFieldVisible(oInfo.m_sObjectCode + "_RM_NetToGross", bDual);
}
// 无限导流裂缝不显示有限导流能力和裂缝宽度参数。
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FRACTURE)
{
QString sModelId = oInfo.m_sObjectCode + "_F_FlowModel";
bool bFinite = m_pSession->enumCode(sModelId) !=
"infinite_conductivity";
setFieldVisible(oInfo.m_sObjectCode + "_F_FC", bFinite);
setFieldVisible(oInfo.m_sObjectCode + "_F_DW", bFinite);
}
// 非泄漏断层或复合区不显示泄漏系数。
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_FAULT)
{
bool bLeaky = m_pSession->enumCode(
oInfo.m_sObjectCode + "_FT_FlowModel") == "leaky";
setFieldVisible(oInfo.m_sObjectCode + "_FT_Leakage", bLeaky);
}
if (oInfo.m_eObjectType == NM_RESERVOIR_OBJECT_REGION)
{
bool bLeaky = m_pSession->enumCode(
oInfo.m_sObjectCode + "_R_FlowModel") == "leaky";
setFieldVisible(oInfo.m_sObjectCode + "_R_Leakage", bLeaky);
}
}
void nmReservoirParameterPage::setFieldVisible(
const QString& sParameterId,
bool bVisible)
{
nmReservoirParameterField* pField =
m_mapFields.value(sParameterId, nullptr);
if (pField != nullptr)
{
pField->setFieldVisible(bVisible);
}
}
void nmReservoirParameterPage::updateValidity()
{
emit sigValidityChanged(isInputValid());
}
nmReservoirPropertiesCategory nmReservoirParameterPage::category() const
{
return m_eCategory;
}

@ -0,0 +1,119 @@
#include "nmReservoirPropertiesDataSource.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmWxParameterProperty.h"
nmReservoirPropertiesSnapshot::nmReservoirPropertiesSnapshot()
{
}
nmIReservoirPropertiesDataSource::~nmIReservoirPropertiesDataSource()
{
}
nmAnalyzeManagerReservoirDataSource::nmAnalyzeManagerReservoirDataSource(
nmDataAnalyzeManager* pDataManager)
: m_pDataManager(pDataManager)
{
}
nmAnalyzeManagerReservoirDataSource::~nmAnalyzeManagerReservoirDataSource()
{
m_pDataManager = nullptr;
}
bool nmAnalyzeManagerReservoirDataSource::loadSnapshot(
nmReservoirPropertiesSnapshot& oSnapshot,
QString& sError) const
{
// 第一步:确认入口已经注入有效的数据管理器。
if (m_pDataManager == nullptr)
{
sError = QObject::tr("The current numerical well-test data is unavailable.");
return false;
}
// 第二步:复制储层和几何对象,确保界面编辑不会触碰实时数据。
oSnapshot.m_oReservoir = m_pDataManager->getReservoirDataCopy();
oSnapshot.m_vecRegionMarks = m_pDataManager->getRegionMarkDataListCopy();
oSnapshot.m_vecFaults = m_pDataManager->getFaultDataListCopy();
oSnapshot.m_vecFractures = m_pDataManager->getFractureDataListCopy();
oSnapshot.m_vecRegions = m_pDataManager->getRegionDataListCopy();
oSnapshot.m_vecVerticalWells.clear();
oSnapshot.m_vecVerticalFracturedWells.clear();
oSnapshot.m_vecHorizontalFracturedWells.clear();
// 第三步:按最具体的井型复制数据,显式排除不受求解器支持的普通水平井。
QVector<nmDataWellBase*> vecWells = m_pDataManager->getWellDataList();
for (int nIndex = 0; nIndex < vecWells.size(); ++nIndex)
{
nmDataWellBase* pWell = vecWells[nIndex];
if (pWell == nullptr)
{
continue;
}
nmDataVerticalFracturedWell* pVerticalFractured =
dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
nmDataHorizontalFracturedWell* pHorizontalFractured =
dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
nmDataVerticalWell* pVertical = dynamic_cast<nmDataVerticalWell*>(pWell);
if (pVerticalFractured != nullptr)
{
oSnapshot.m_vecVerticalFracturedWells.append(*pVerticalFractured);
}
else if (pHorizontalFractured != nullptr)
{
oSnapshot.m_vecHorizontalFracturedWells.append(*pHorizontalFractured);
}
else if (pVertical != nullptr)
{
oSnapshot.m_vecVerticalWells.append(*pVertical);
}
}
sError.clear();
return true;
}
bool nmAnalyzeManagerReservoirDataSource::commitSnapshot(
const nmReservoirPropertiesSnapshot& oSnapshot,
bool bGeometryChanged,
QString& sError)
{
// 第一步:提交前再次校验数据管理器生命周期。
if (m_pDataManager == nullptr)
{
sError = QObject::tr("The current numerical well-test data is unavailable.");
return false;
}
// 第二步:按对象类型批量替换现有数据,不改变对象地址和列表结构。
m_pDataManager->updateVerticalWells(oSnapshot.m_vecVerticalWells);
m_pDataManager->updateVerticalFracturedWells(oSnapshot.m_vecVerticalFracturedWells);
m_pDataManager->updateHorizontalFracturedWells(oSnapshot.m_vecHorizontalFracturedWells);
m_pDataManager->updateFaultData(oSnapshot.m_vecFaults);
m_pDataManager->updateFractureData(oSnapshot.m_vecFractures);
m_pDataManager->updateRegionData(oSnapshot.m_vecRegions);
m_pDataManager->updateRegionMarkData(oSnapshot.m_vecRegionMarks);
m_pDataManager->updateReservoirData(oSnapshot.m_oReservoir);
// 第三步:重建实时注册表映射,并在几何变化时统一通知画布和网格。
m_pDataManager->syncWellAttrs();
m_pDataManager->syncGeometryAttrs();
if (bGeometryChanged)
{
m_pDataManager->notifyDataChanged();
m_pDataManager->updateWellPlotByDataManager();
}
// 第四步:兼容仍在使用旧参数树的窗口,提交完成后只刷新一次表格入口。
nmWxParameterProperty::notifyUpdateTable();
sError.clear();
return true;
}

@ -0,0 +1,242 @@
#include "nmReservoirPropertiesSession.h"
#include "nmAttrRegistry.h"
#include "nmDataAttribute.h"
namespace
{
/** @brief 比较两个QVariant数值保留字符串和整数的精确语义。 */
bool sameValue(const QVariant& oLeft, const QVariant& oRight)
{
return oLeft == oRight;
}
/** @brief 比较两口井的网格相关参数。 */
bool sameWellGeometry(nmDataWellBase& oLeft, nmDataWellBase& oRight)
{
return sameValue(oLeft.getX().getValue(), oRight.getX().getValue()) &&
sameValue(oLeft.getY().getValue(), oRight.getY().getValue()) &&
sameValue(oLeft.getRadius().getValue(), oRight.getRadius().getValue());
}
}
nmReservoirPropertiesSession::nmReservoirPropertiesSession(
nmIReservoirPropertiesDataSource* pDataSource)
: m_pDataSource(pDataSource),
m_pAttrRegistry(new nmAttrRegistry),
m_bInitialized(false)
{
}
nmReservoirPropertiesSession::~nmReservoirPropertiesSession()
{
delete m_pAttrRegistry;
m_pAttrRegistry = nullptr;
m_pDataSource = nullptr;
}
bool nmReservoirPropertiesSession::initialize(QString& sError)
{
// 第一步:读取一次真实数据,作为取消和变更检测的基线。
if (m_pDataSource == nullptr ||
!m_pDataSource->loadSnapshot(m_oOriginalSnapshot, sError))
{
m_bInitialized = false;
return false;
}
// 第二步:复制出独立工作副本,后续界面修改只写入此对象。
m_oWorkingSnapshot = m_oOriginalSnapshot;
nmReservoirParameterCatalog::registerSnapshot(m_pAttrRegistry,
m_oWorkingSnapshot);
m_bInitialized = true;
sError.clear();
return true;
}
QList<nmReservoirPropertyObjectInfo> nmReservoirPropertiesSession::objectInfos(
nmReservoirPropertiesCategory eCategory)
{
if (!m_bInitialized)
{
return QList<nmReservoirPropertyObjectInfo>();
}
return nmReservoirParameterCatalog::createObjectInfos(eCategory,
m_oWorkingSnapshot);
}
QVariant nmReservoirPropertiesSession::value(const QString& sParameterId) const
{
if (!m_bInitialized || m_pAttrRegistry == nullptr)
{
return QVariant();
}
QStringList listIds;
listIds << sParameterId;
return m_pAttrRegistry->collect(listIds).value(sParameterId, QVariant());
}
bool nmReservoirPropertiesSession::setValue(const QString& sParameterId,
const QVariant& oValue)
{
if (!m_bInitialized || m_pAttrRegistry == nullptr ||
!m_pAttrRegistry->registeredNames().contains(sParameterId))
{
return false;
}
m_pAttrRegistry->setValue(sParameterId, oValue);
return true;
}
void nmReservoirPropertiesSession::setValues(
const QMap<QString, QVariant>& mapValues)
{
QMap<QString, QVariant>::const_iterator oIterator = mapValues.constBegin();
for (; oIterator != mapValues.constEnd(); ++oIterator)
{
setValue(oIterator.key(), oIterator.value());
}
}
QString nmReservoirPropertiesSession::enumCode(const QString& sParameterId) const
{
if (!m_bInitialized)
{
return QString();
}
return nmReservoirParameterCatalog::enumCode(m_oWorkingSnapshot,
sParameterId);
}
bool nmReservoirPropertiesSession::setEnumCode(const QString& sParameterId,
const QString& sCode)
{
if (!m_bInitialized)
{
return false;
}
return nmReservoirParameterCatalog::setEnumCode(m_oWorkingSnapshot,
sParameterId,
sCode);
}
bool nmReservoirPropertiesSession::commit(QString& sError)
{
if (!m_bInitialized || m_pDataSource == nullptr)
{
sError = QObject::tr("The reservoir properties editing session is unavailable.");
return false;
}
// 第一步:在提交前计算几何变更,供数据源决定是否重建网格和刷新画布。
bool bGeometryChanged = isGeometryChanged();
// 第二步:将完整工作副本一次性交给数据源;失败时继续保留当前编辑内容。
if (!m_pDataSource->commitSnapshot(m_oWorkingSnapshot,
bGeometryChanged,
sError))
{
return false;
}
// 第三步:提交成功后更新基线,防止同一会话重复识别旧变更。
m_oOriginalSnapshot = m_oWorkingSnapshot;
sError.clear();
return true;
}
const nmReservoirPropertiesSnapshot&
nmReservoirPropertiesSession::workingSnapshot() const
{
return m_oWorkingSnapshot;
}
bool nmReservoirPropertiesSession::isGeometryChanged() const
{
// 第一步:对象数量变化视为几何变化,虽然当前对话框本身不提供增删操作。
if (m_oOriginalSnapshot.m_vecVerticalWells.size() !=
m_oWorkingSnapshot.m_vecVerticalWells.size() ||
m_oOriginalSnapshot.m_vecVerticalFracturedWells.size() !=
m_oWorkingSnapshot.m_vecVerticalFracturedWells.size() ||
m_oOriginalSnapshot.m_vecHorizontalFracturedWells.size() !=
m_oWorkingSnapshot.m_vecHorizontalFracturedWells.size())
{
return true;
}
// 第二步:比较三类井的位置和井径。
for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecVerticalWells.size(); ++nIndex)
{
nmDataVerticalWell& oOriginal =
const_cast<nmDataVerticalWell&>(m_oOriginalSnapshot.m_vecVerticalWells[nIndex]);
nmDataVerticalWell& oWorking =
const_cast<nmDataVerticalWell&>(m_oWorkingSnapshot.m_vecVerticalWells[nIndex]);
if (!sameWellGeometry(oOriginal, oWorking))
{
return true;
}
}
for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecVerticalFracturedWells.size(); ++nIndex)
{
nmDataVerticalFracturedWell& oOriginal = const_cast<nmDataVerticalFracturedWell&>(
m_oOriginalSnapshot.m_vecVerticalFracturedWells[nIndex]);
nmDataVerticalFracturedWell& oWorking = const_cast<nmDataVerticalFracturedWell&>(
m_oWorkingSnapshot.m_vecVerticalFracturedWells[nIndex]);
if (!sameWellGeometry(oOriginal, oWorking) ||
!sameValue(oOriginal.getFractureHalfLength().getValue(),
oWorking.getFractureHalfLength().getValue()) ||
!sameValue(oOriginal.getFractureAngle().getValue(),
oWorking.getFractureAngle().getValue()))
{
return true;
}
}
for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecHorizontalFracturedWells.size(); ++nIndex)
{
nmDataHorizontalFracturedWell& oOriginal =
const_cast<nmDataHorizontalFracturedWell&>(
m_oOriginalSnapshot.m_vecHorizontalFracturedWells[nIndex]);
nmDataHorizontalFracturedWell& oWorking =
const_cast<nmDataHorizontalFracturedWell&>(
m_oWorkingSnapshot.m_vecHorizontalFracturedWells[nIndex]);
if (!sameWellGeometry(oOriginal, oWorking) ||
!sameValue(oOriginal.getWellLength().getValue(), oWorking.getWellLength().getValue()) ||
!sameValue(oOriginal.getDrainAngle().getValue(), oWorking.getDrainAngle().getValue()) ||
!sameValue(oOriginal.getNumberOfFractures().getValue(),
oWorking.getNumberOfFractures().getValue()) ||
!sameValue(oOriginal.getFractureHalfLength().getValue(),
oWorking.getFractureHalfLength().getValue()) ||
!sameValue(oOriginal.getFractureAngle().getValue(),
oWorking.getFractureAngle().getValue()))
{
return true;
}
}
// 第三步:断层和裂缝端点也直接参与网格,因此比较完整点坐标。
if (m_oOriginalSnapshot.m_vecFaults.size() != m_oWorkingSnapshot.m_vecFaults.size() ||
m_oOriginalSnapshot.m_vecFractures.size() != m_oWorkingSnapshot.m_vecFractures.size())
{
return true;
}
for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecFaults.size(); ++nIndex)
{
if (m_oOriginalSnapshot.m_vecFaults[nIndex].getFaultPoints() !=
m_oWorkingSnapshot.m_vecFaults[nIndex].getFaultPoints())
{
return true;
}
}
for (int nIndex = 0; nIndex < m_oWorkingSnapshot.m_vecFractures.size(); ++nIndex)
{
if (m_oOriginalSnapshot.m_vecFractures[nIndex].getFracturePoints() !=
m_oWorkingSnapshot.m_vecFractures[nIndex].getFracturePoints())
{
return true;
}
}
return false;
}

@ -408,7 +408,7 @@ void nmWxAutomaticFitting::setupParameterTable()
m_parameterTable->setItem(0, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMin().getValue().toDouble())));
m_parameterTable->setItem(0, 3, new QTableWidgetItem(QString::number(reservoirData.getPermeability().getValue().toDouble())));
m_parameterTable->setItem(0, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMax().getValue().toDouble())));
m_parameterTable->setItem(0, 5, new QTableWidgetItem(tr("Darcy")));
m_parameterTable->setItem(0, 5, new QTableWidgetItem(tr("D")));
// 表皮系数 (Skin)
m_parameterTable->setItem(1, 0, new QTableWidgetItem("2"));

@ -0,0 +1,544 @@
#include "nmWxReservoirPropertiesEditor.h"
#include "nmReservoirPropertiesDataSource.h"
#include "nmReservoirPropertiesSession.h"
#include "nmReservoirParameterPage.h"
#include "iUnitGroup.h"
#include "iUnitHelper.h"
#include "iUnitWxBinder.h"
#include "mModuleDefines.h"
#include "ZxBaseUtil.h"
#include <QAbstractButton>
#include <QButtonGroup>
#include <QDialogButtonBox>
#include <QFont>
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPalette>
#include <QPushButton>
#include <QStackedWidget>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWhatsThis>
nmWxReservoirPropertiesEditor::nmWxReservoirPropertiesEditor(
nmIReservoirPropertiesDataSource* pDataSource,
QWidget* pParent)
: iDlgBase(pParent),
m_pDataSource(pDataSource),
m_pSession(new nmReservoirPropertiesSession(pDataSource)),
m_pCategoryButtonGroup(nullptr),
m_pStackedWidget(nullptr),
m_pButtonBox(nullptr),
m_pFooterHelpButton(nullptr),
m_pStatusLabel(nullptr),
m_nCurrentCategory(NM_RESERVOIR_PAGE_RESERVOIR),
m_bInitialized(false)
{
for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex)
{
m_pPages[nIndex] = nullptr;
}
// 单位绑定器必须先切换到数值试井参数系列,再创建任何参数字段。
setParaSrcTag(s_Nm_Serie);
createUi();
setWindowTitle(tr("Reservoir properties settings"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
// 首次打开使用建议尺寸,后续最小宽高均由页面内容和布局自动决定。
resize(880, 520);
}
nmWxReservoirPropertiesEditor::~nmWxReservoirPropertiesEditor()
{
delete m_pSession;
m_pSession = nullptr;
m_pDataSource = nullptr;
}
void nmWxReservoirPropertiesEditor::createUi()
{
// 第一步创建原型中的顶部ribbon和无边框页面堆栈。
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
pMainLayout->setContentsMargins(12, 10, 12, 0);
pMainLayout->setSpacing(0);
pMainLayout->addWidget(createCategoryBar());
m_pStackedWidget = new QStackedWidget(this);
pMainLayout->addWidget(m_pStackedWidget, 1);
// 第二步:底部左侧放帮助与状态,右侧只放确定和取消。
QWidget* pFooterWidget = new QWidget(this);
QHBoxLayout* pFooterLayout = new QHBoxLayout(pFooterWidget);
pFooterLayout->setContentsMargins(4, 5, 4, 5);
pFooterLayout->setSpacing(8);
m_pFooterHelpButton = new QToolButton(pFooterWidget);
m_pFooterHelpButton->setText(tr("Help"));
m_pFooterHelpButton->setIcon(zxLoadIcon("Help"));
m_pFooterHelpButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
m_pFooterHelpButton->setAutoRaise(true);
pFooterLayout->addWidget(m_pFooterHelpButton);
m_pStatusLabel = new QLabel(pFooterWidget);
m_pStatusLabel->setWordWrap(true);
pFooterLayout->addWidget(m_pStatusLabel, 1);
m_pButtonBox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
Qt::Horizontal,
pFooterWidget);
QPushButton* pOkButton = m_pButtonBox->button(QDialogButtonBox::Ok);
QPushButton* pCancelButton =
m_pButtonBox->button(QDialogButtonBox::Cancel);
if (pOkButton != nullptr)
{
pOkButton->setIcon(zxLoadIcon("OK"));
pOkButton->setText(tr("OK"));
}
if (pCancelButton != nullptr)
{
pCancelButton->setIcon(zxLoadIcon("Cancel"));
pCancelButton->setText(tr("Cancel"));
}
pFooterLayout->addWidget(m_pButtonBox);
pMainLayout->addWidget(pFooterWidget);
connect(m_pButtonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(m_pButtonBox, SIGNAL(rejected()), this, SLOT(reject()));
connect(m_pFooterHelpButton, SIGNAL(clicked()),
this, SLOT(slotHelp()));
}
QWidget* nmWxReservoirPropertiesEditor::createCategoryBar()
{
// 使用无标题分组框承载顶部导航,获得与框架工具栏相近的浅色外边框。
QGroupBox* pCategoryBar = new QGroupBox(this);
// 为边框和内部命令区保留少量间距,窗口拉伸时右侧空白自然扩展。
QHBoxLayout* pBarLayout = new QHBoxLayout(pCategoryBar);
pBarLayout->setContentsMargins(3, 3, 3, 3);
pBarLayout->setSpacing(0);
m_pCategoryButtonGroup = new QButtonGroup(this);
// 第一步:基础组只包含储层参数。
QHBoxLayout* pBasicLayout = nullptr;
QWidget* pBasicGroup = createRibbonGroup(
tr("Basic"), pCategoryBar, pBasicLayout);
pBasicLayout->insertWidget(0, createCategoryButton(
NM_RESERVOIR_PAGE_RESERVOIR,
tr("Reservoir parameters"),
"ResData",
pBasicGroup));
pBarLayout->addWidget(pBasicGroup);
pBarLayout->addWidget(createRibbonSeparator(pCategoryBar));
// 第二步:井筒参数独立成组,避免与储层内部区域对象混在一起。
QHBoxLayout* pWellLayout = nullptr;
QWidget* pWellGroup = createRibbonGroup(
tr("Wells"), pCategoryBar, pWellLayout);
pWellLayout->insertWidget(0, createCategoryButton(
NM_RESERVOIR_PAGE_WELL,
tr("Well parameters"),
"NmWell",
pWellGroup));
pBarLayout->addWidget(pWellGroup);
pBarLayout->addWidget(createRibbonSeparator(pCategoryBar));
// 第三步:四类储层平面对象按用户工作顺序放入同一个几何对象组。
QHBoxLayout* pGeometryLayout = nullptr;
QWidget* pGeometryGroup = createRibbonGroup(
tr("Geometry objects"),
pCategoryBar,
pGeometryLayout);
pGeometryLayout->insertWidget(0, createCategoryButton(
NM_RESERVOIR_PAGE_FAULT,
tr("Faults"),
"NmFault",
pGeometryGroup));
pGeometryLayout->insertWidget(1, createCategoryButton(
NM_RESERVOIR_PAGE_FRACTURE,
tr("Fractures"),
"NmFault",
pGeometryGroup));
pGeometryLayout->insertWidget(2, createCategoryButton(
NM_RESERVOIR_PAGE_COMPOSITE_REGION,
tr("Composite regions"),
"NmRegion",
pGeometryGroup));
pGeometryLayout->insertWidget(3, createCategoryButton(
NM_RESERVOIR_PAGE_REGION_MARK,
tr("Region marks"),
"RegionSelect",
pGeometryGroup));
pBarLayout->addWidget(pGeometryGroup);
pBarLayout->addWidget(createRibbonSeparator(pCategoryBar));
// 第四步:帮助是即时命令,不加入互斥页面按钮组。
QHBoxLayout* pHelpLayout = nullptr;
QWidget* pHelpGroup = createRibbonGroup(
tr("Help"), pCategoryBar, pHelpLayout);
pHelpLayout->insertWidget(0, createHelpButton(pHelpGroup));
pBarLayout->addWidget(pHelpGroup);
pBarLayout->addStretch(1);
connect(m_pCategoryButtonGroup, SIGNAL(buttonClicked(int)),
this, SLOT(slotCategoryButtonClicked(int)));
return pCategoryBar;
}
QWidget* nmWxReservoirPropertiesEditor::createRibbonGroup(
const QString& sCaption,
QWidget* pParent,
QHBoxLayout*& pCommandLayout)
{
QWidget* pGroupWidget = new QWidget(pParent);
// 分组左右保留稳定留白,使按钮不会紧贴相邻分组的竖向分隔线。
QVBoxLayout* pGroupLayout = new QVBoxLayout(pGroupWidget);
pGroupLayout->setContentsMargins(11, 2, 11, 1);
// 按钮区与下方分组标题之间留出间距,避免两行文字贴得过近。
pGroupLayout->setSpacing(4);
QWidget* pCommandWidget = new QWidget(pGroupWidget);
pCommandLayout = new QHBoxLayout(pCommandWidget);
pCommandLayout->setContentsMargins(0, 0, 0, 0);
pCommandLayout->setSpacing(6);
pCommandLayout->addStretch();
pGroupLayout->addWidget(pCommandWidget, 1);
// 分组标题使用主题灰色和下划线,保持框架参考工具栏的层级表现。
QLabel* pCaptionLabel = new QLabel(sCaption, pGroupWidget);
pCaptionLabel->setAlignment(Qt::AlignCenter);
pCaptionLabel->setFixedHeight(16);
QFont oCaptionFont = pCaptionLabel->font();
oCaptionFont.setUnderline(true);
pCaptionLabel->setFont(oCaptionFont);
QPalette oCaptionPalette = pCaptionLabel->palette();
oCaptionPalette.setColor(
QPalette::WindowText,
pGroupWidget->palette().color(QPalette::Mid));
pCaptionLabel->setPalette(oCaptionPalette);
pGroupLayout->addWidget(pCaptionLabel);
return pGroupWidget;
}
QFrame* nmWxReservoirPropertiesEditor::createRibbonSeparator(
QWidget* pParent)
{
// 使用单像素平直竖线避免Sunken双线在端点形成视觉折角。
QFrame* pSeparator = new QFrame(pParent);
pSeparator->setFrameShape(QFrame::VLine);
pSeparator->setFrameShadow(QFrame::Plain);
pSeparator->setLineWidth(1);
QPalette oSeparatorPalette = pSeparator->palette();
oSeparatorPalette.setColor(
QPalette::WindowText,
pParent->palette().color(QPalette::Mid));
pSeparator->setPalette(oSeparatorPalette);
pSeparator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
return pSeparator;
}
QToolButton* nmWxReservoirPropertiesEditor::createCategoryButton(
nmReservoirPropertiesCategory eCategory,
const QString& sText,
const QString& sIconName,
QWidget* pParent)
{
QToolButton* pButton = new QToolButton(pParent);
pButton->setText(sText);
pButton->setIcon(zxLoadIcon(sIconName));
pButton->setIconSize(QSize(25, 25));
pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
// 分类按钮仅执行页面切换命令,不保留持续选中状态。
pButton->setCheckable(false);
pButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_pCategoryButtonGroup->addButton(
pButton, static_cast<int>(eCategory));
return pButton;
}
QToolButton* nmWxReservoirPropertiesEditor::createHelpButton(
QWidget* pParent)
{
QToolButton* pButton = new QToolButton(pParent);
pButton->setText(tr("Help"));
pButton->setIcon(zxLoadIcon("Help"));
pButton->setIconSize(QSize(25, 25));
pButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
pButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
connect(pButton, SIGNAL(clicked()), this, SLOT(slotHelp()));
return pButton;
}
bool nmWxReservoirPropertiesEditor::initialize(QString& sError)
{
// 第一步:建立工作副本,失败时不创建不完整的字段控件。
if (m_pSession == nullptr || !m_pSession->initialize(sError))
{
m_bInitialized = false;
return false;
}
// 第二步创建页面并逐页验证XML参数定义和单位绑定。
createPages();
for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex)
{
if (!m_pPages[nIndex]->reloadObjects(sError))
{
m_bInitialized = false;
return false;
}
}
// 第三步:没有对应对象的分类直接禁用,避免用户进入无操作价值的空页面。
updateCategoryButtonStates();
m_pStackedWidget->setCurrentIndex(NM_RESERVOIR_PAGE_RESERVOIR);
m_bInitialized = true;
validateAllPages();
sError.clear();
return true;
}
bool nmWxReservoirPropertiesEditor::bindUnitField(
QLineEdit* pLineEdit,
QComboBox* pUnitComboBox,
const QString& sBaseParameterId)
{
// 第一步:仍由框架绑定器读取参数定义、范围和常规单位组。
if (!bindUnitPairWxs(pLineEdit, pUnitComboBox,
sBaseParameterId, false))
{
return false;
}
// 第二步单位帮助器对单位名执行了不区分大小写的匹配D可能先命中
// 时间组中的d。渗透率以mD这个无歧义单位定位正确单位组
// 但基准单位仍明确保持为求解器使用的D。
if (sBaseParameterId == QString::fromLatin1("K"))
{
iUnitGroup* pPermeabilityGroup =
iUnitHelper::getUnitGroupByUnit(
QString::fromLatin1("mD"));
zxBindWxPair* pBindPair = m_pUnitBinder != nullptr
? m_pUnitBinder->getWxPairOf(pLineEdit) : nullptr;
if (pPermeabilityGroup == nullptr || pBindPair == nullptr ||
pPermeabilityGroup->m_sType.compare(
QString::fromLatin1("permeability"),
Qt::CaseInsensitive) != 0 ||
pPermeabilityGroup->indexOf(
QString::fromLatin1("D")) < 0)
{
return false;
}
// 第三步:替换错误单位组并重建单位选项。后续数值设置、切换、
// 范围校验和基准值读取继续全部通过框架绑定器完成。
pBindPair->isUpdating = true;
pBindPair->pUnitGroup = pPermeabilityGroup;
pBindPair->sBaseUnit = QString::fromLatin1("D");
pUnitComboBox->clear();
pUnitComboBox->addItems(pPermeabilityGroup->getAllUnitNames());
int nBaseUnitIndex = pUnitComboBox->findText(
QString::fromLatin1("D"),
Qt::MatchExactly | Qt::MatchCaseSensitive);
pUnitComboBox->setCurrentIndex(nBaseUnitIndex);
pBindPair->isUpdating = false;
}
return true;
}
void nmWxReservoirPropertiesEditor::setUnitBaseValue(
QLineEdit* pLineEdit,
double dBaseValue)
{
if (m_pUnitBinder != nullptr)
{
m_pUnitBinder->setBaseValue(pLineEdit, dBaseValue);
}
}
double nmWxReservoirPropertiesEditor::unitBaseValue(
QLineEdit* pLineEdit) const
{
return m_pUnitBinder != nullptr
? m_pUnitBinder->getBaseValue(pLineEdit) : 0.0;
}
bool nmWxReservoirPropertiesEditor::isUnitInputValid(
QLineEdit* pLineEdit)
{
return m_pUnitBinder != nullptr &&
m_pUnitBinder->isInputOK(pLineEdit, true, nullptr);
}
QString nmWxReservoirPropertiesEditor::currentUnit(
QLineEdit* pLineEdit) const
{
return m_pUnitBinder != nullptr
? m_pUnitBinder->getCurrentUnit(pLineEdit) : QString();
}
iUnitGroup* nmWxReservoirPropertiesEditor::unitGroup(
QLineEdit* pLineEdit) const
{
return m_pUnitBinder != nullptr
? m_pUnitBinder->getUnitGroup(pLineEdit) : nullptr;
}
void nmWxReservoirPropertiesEditor::createPages()
{
if (m_pStackedWidget->count() > 0)
{
return;
}
for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex)
{
nmReservoirPropertiesCategory eCategory =
static_cast<nmReservoirPropertiesCategory>(nIndex);
m_pPages[nIndex] = new nmReservoirParameterPage(
eCategory,
m_pSession,
this,
m_pStackedWidget);
connect(m_pPages[nIndex], SIGNAL(sigValidityChanged(bool)),
this, SLOT(slotPageValidityChanged(bool)));
m_pStackedWidget->addWidget(m_pPages[nIndex]);
}
}
void nmWxReservoirPropertiesEditor::slotCategoryButtonClicked(
int nCategory)
{
if (!m_bInitialized || nCategory < 0 ||
nCategory >= NM_RESERVOIR_PAGE_COUNT)
{
return;
}
// 分类按钮状态由对象列表决定;此处再次防御程序主动触发的非法切换。
QAbstractButton* pCategoryButton = m_pCategoryButtonGroup != nullptr
? m_pCategoryButtonGroup->button(nCategory) : nullptr;
if (pCategoryButton == nullptr || !pCategoryButton->isEnabled())
{
return;
}
// 离开当前分类前先校验并保存,非法时保持当前页。
if (m_pPages[m_nCurrentCategory] != nullptr &&
!m_pPages[m_nCurrentCategory]->commitPendingEdits())
{
m_pStatusLabel->setText(
tr("Please correct the invalid parameter values."));
return;
}
m_nCurrentCategory = nCategory;
m_pStackedWidget->setCurrentIndex(nCategory);
m_pStatusLabel->clear();
}
void nmWxReservoirPropertiesEditor::updateCategoryButtonStates()
{
if (m_pCategoryButtonGroup == nullptr)
{
return;
}
// 逐类读取页面已经加载的对象列表,页面不存在或对象为空时禁用命令。
for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex)
{
QAbstractButton* pCategoryButton =
m_pCategoryButtonGroup->button(nIndex);
bool bEnabled = m_pPages[nIndex] != nullptr &&
m_pPages[nIndex]->hasEditableObjects();
if (pCategoryButton != nullptr)
{
pCategoryButton->setEnabled(bEnabled);
}
}
}
void nmWxReservoirPropertiesEditor::slotPageValidityChanged(bool bValid)
{
Q_UNUSED(bValid);
validateAllPages();
}
void nmWxReservoirPropertiesEditor::slotHelp()
{
// 顶部和底部帮助命令共用Qt上下文帮助字段说明和范围来自XML。
QWhatsThis::enterWhatsThisMode();
}
bool nmWxReservoirPropertiesEditor::validateAllPages()
{
bool bAllValid = m_bInitialized;
if (m_bInitialized)
{
for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex)
{
if (m_pPages[nIndex] != nullptr &&
!m_pPages[nIndex]->isInputValid())
{
bAllValid = false;
break;
}
}
}
QPushButton* pOkButton = m_pButtonBox != nullptr
? m_pButtonBox->button(QDialogButtonBox::Ok) : nullptr;
if (pOkButton != nullptr)
{
pOkButton->setEnabled(bAllValid);
}
return bAllValid;
}
void nmWxReservoirPropertiesEditor::accept()
{
if (!m_bInitialized)
{
return;
}
// 第一步:依次提交各页面当前对象尚未离开焦点的输入。
for (int nIndex = 0; nIndex < NM_RESERVOIR_PAGE_COUNT; ++nIndex)
{
if (m_pPages[nIndex] != nullptr &&
!m_pPages[nIndex]->commitPendingEdits())
{
m_nCurrentCategory = nIndex;
m_pStackedWidget->setCurrentIndex(nIndex);
m_pStatusLabel->setText(
tr("Please correct the invalid parameter values."));
validateAllPages();
return;
}
}
// 第二步:把完整工作副本一次性交给数据源。
QString sError;
if (!m_pSession->commit(sError))
{
m_pStatusLabel->setText(sError);
QMessageBox::warning(this, tr("Reservoir properties"), sError);
return;
}
// 第三步:仅在提交成功后关闭对话框。
iDlgBase::accept();
}
void nmWxReservoirPropertiesEditor::reject()
{
// 工作副本从未提前写入真实数据,取消时直接关闭即可。
iDlgBase::reject();
}

@ -1,4 +1,6 @@
QT += core sql gui widgets
QT += core sql gui
greaterThan(QT_MAJOR_VERSION, 4) : QT += widgets
greaterThan(QT_MAJOR_VERSION, 4) : QT += printsupport
#greaterThan(QT_MAJOR_VERSION, 4) : QT += axcontainer printsupport

Loading…
Cancel
Save