Merge branch 'feature/ReservoirPropertiesDlg-Refactoring-20260813' into develop

# Conflicts:
#	Bin/Config/Common/ModelParaDefinesNM_cn.dat   resolved by feature/ReservoirPropertiesDlg-Refactoring-20260813 version
#	Bin/Config/Common/ModelParaDefinesNM_en.dat   resolved by feature/ReservoirPropertiesDlg-Refactoring-20260813 version
#	Bin/Config/Common/UnitDefault_cn.dat   resolved by feature/ReservoirPropertiesDlg-Refactoring-20260813 version
#	Bin/Config/Lang/cn/nmNum_cn.qm   resolved by develop version
#	Bin/XmlFiles/ModelParaDefinesNM_cn.xml
#	Bin/XmlFiles/ModelParaDefinesNM_en.xml
#	Src/nmNum/nmData/nmDataAutomaticFitting.cpp
#	Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp
feature/PebiSolver-Integration-20260818
lh 4 weeks ago
commit 7204c54b07

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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>
@ -6480,4 +6480,75 @@ 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>Object display</source><translation></translation></message>
<message><source>Display:</source><translation></translation></message>
<message><source>No editable objects in this category.</source><translation></translation></message>
<message><source>Initial oil and water saturations must sum to 1.</source><translation> 1</translation></message>
</context>
<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>Boundary type</source><translation></translation></message>
<message><source>Leaky</source><translation></translation></message>
<message><source>Composite limit</source><translation></translation></message>
<message><source>Permeable boundary</source><translation></translation></message>
<message><source>Composite boundary</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>Fractured vertical well</source><translation></translation></message>
<message><source>Multistage fractured horizontal well</source><translation></translation></message>
<message><source>Position and wellbore</source><translation></translation></message>
<message><source>Wellbore storage</source><translation></translation></message>
<message><source>Wellbore storage and fracture</source><translation></translation></message>
<message><source>Basic and medium properties</source><translation></translation></message>
<message><source>Fluid parameters</source><translation></translation></message>
<message><source>Oil phase parameters</source><translation></translation></message>
<message><source>Water phase parameters</source><translation></translation></message>
<message><source>Compressibility</source><translation></translation></message>
<message><source>Compressibility and saturation</source><translation></translation></message>
<message><source>Reservoir 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>Boundary 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,11 +17,16 @@
<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="Darcy" Default="0.001" Max="10" Min="0" Digit="6" Desc="permeability" />
<ParaItem Name="K" Alias="渗透率" Unit="mD" Default="25" Max="10000" 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="Bw" Alias="水体积系数" Unit="" Default="1" Max="10" Min="1e-06" Digit="6" Desc="water formation volume factor" />
<ParaItem Name="Miuw" Alias="水相黏度" Unit="mPa.s" Default="1" Max="10000" Min="1e-06" Digit="6" Desc="water viscosity" />
<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" />
<ParaItem Name="Sgi" Alias="初始含气饱和度" Unit="" Default="0" Max="1" Min="0" Digit="6" Desc="initial gas saturation" />
<ParaItem Name="Swi" Alias="初始含水饱和度" Unit="" Default="0.2" Max="1" Min="0" Digit="6" Desc="initial water saturation" />
</ParaGroup>
@ -32,11 +37,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="deg" 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="deg" 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 +49,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 +58,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 +92,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="Darcy" Default="0.001" Max="10" Min="0" Digit="6" Desc="result permeability" />
<ParaItem Name="Result_K" Alias="渗透率" Unit="mD" Default="25" Max="10000" 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 +106,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,11 +18,16 @@
<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="Darcy" Default="0.001" Max="10" Min="0" Digit="6" Desc="permeability" />
<ParaItem Name="K" Alias="Permeability" Unit="mD" Default="25" Max="10000" 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="Bw" Alias="Water Formation Volume Factor" Unit="" Default="1" Max="10" Min="1e-06" Digit="6" Desc="water formation volume factor" />
<ParaItem Name="Miuw" Alias="Water Viscosity" Unit="mPa.s" Default="1" Max="10000" Min="1e-06" Digit="6" Desc="water viscosity" />
<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" />
<ParaItem Name="Sgi" Alias="Initial Gas Saturation" Unit="" Default="0" Max="1" Min="0" Digit="6" Desc="initial gas saturation" />
<ParaItem Name="Swi" Alias="Initial Water Saturation" Unit="" Default="0.2" Max="1" Min="0" Digit="6" Desc="initial water saturation" />
</ParaGroup>
@ -33,11 +38,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="deg" 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="deg" 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 +50,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 +59,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 +93,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="Darcy" Default="0.001" Max="10" Min="0" Digit="6" Desc="result permeability" />
<ParaItem Name="Result_K" Alias="Permeability" Unit="mD" Default="25" Max="10000" 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 +107,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" />

@ -331,10 +331,18 @@
<Unit ID="U3206" Unit="kgf.s/m^3" k="9.80665"/>
</UnitGroup>
<!-- ==================== 以下为数值试井模块补充扩展单位 ==================== -->
<!-- ==================== 第33组井筒储集系数 ==================== -->
<UnitGroup Type="ToDo3" Name="ToDo3">
<Unit ID="U3301" Unit="m^3/MPa" k="1" Digit="6" Remark="base unit"/>
</UnitGroup>
<!-- ==================== 第34组流动能力 ==================== -->
<UnitGroup Type="flow_capacity" Name="flow_capacity">
<Unit ID="U3401" Unit="mD.m" k="1" Digit="6" Remark="base unit"/>
</UnitGroup>
</TypeOf>
</Root>

@ -19,6 +19,13 @@ class NMCALCULATION_EXPORT nmCalculationUtils {
static bool writeFile(const QStringList& content, const QString &filePath);
static QStringList readFile(const QString & filePath);
/**
* @brief 使西PEBI使西
* @param dPermeabilityMilliDarcy mD
* @return D
*/
static double milliDarcyToDarcy(double dPermeabilityMilliDarcy);
// 调用求解器Kriging接口对targetPoints中的坐标依次进行属性插值.
// measurementPoints与measurementValues按下标一一对应.
// 成功时outputValues与targetPoints数量相同且顺序一致失败时清空outputValues.

@ -1,4 +1,4 @@
#ifndef NMDATAATTRIBUTE_H
#ifndef NMDATAATTRIBUTE_H
#define NMDATAATTRIBUTE_H
#include <QString>
@ -15,7 +15,7 @@ enum UnitType {
UNIT_TYPE_LENGTH, // 长度m, cm, mm, in, ft, km等
UNIT_TYPE_CONDUCTIVITY, // 导流能力md.m, md.ft, m^3
UNIT_TYPE_PRESSURE, // 压力MPa, psia, Pa, kPa, bar等
UNIT_TYPE_PERMEABILITY, // 渗透率md, Darcy, m^2, cm^2等
UNIT_TYPE_PERMEABILITY, // 渗透率mD, D, m^2, cm^2等
UNIT_TYPE_ANGLE, // 角度o (degree), radian
UNIT_TYPE_FLOW_RATE_RECIPROCAL, // 流量倒数1/B/D, 1/m^3/D等
UNIT_TYPE_COMPRESSIBILITY, // 压缩系数m^3/MPa, bbl/psi等

@ -1,4 +1,4 @@
#ifndef NMDATABINARYTOOLS_H
#ifndef NMDATABINARYTOOLS_H
#define NMDATABINARYTOOLS_H
#include <QString>
@ -152,7 +152,7 @@ public:
double dt_Max; // 最大时间间隔
// 注意k, phi, h是每个网格单元一个值数量太大不保存基准值
// Python采样时根据 Base.k_ref 给所有单元赋相同值
double k_ref; // 渗透率参考值(用于采样)
double k_ref; // 渗透率参考值写入场景文件前已转换为D
double phi_ref; // 孔隙度参考值(用于采样)
double h_ref; // 厚度参考值(用于采样)
} Base;

@ -0,0 +1,144 @@
#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; ///< 数值输入是否右对齐。
bool m_bFieldsCentered; ///< 分组内字段是否水平居中并随宽度伸展。
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,168 @@
#pragma once
#include "nmSubWxs_global.h"
#include "nmReservoirParameterCatalog.h"
#include <QColor>
#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 nColumnOffset
* @param sError
* @return true
*/
bool initialize(QGridLayout* pLayout,
int nRow,
int nColumnOffset,
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 设置页面级关联校验错误;空文字表示清除关联错误。 */
void setExternalValidationError(const QString& sError);
/** @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,
int nColumnOffset,
QString& sError);
/** @brief 创建稳定code枚举下拉框。 */
void createEnumEditor(QGridLayout* pLayout,
int nRow,
int nColumnOffset);
/** @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; ///< 无单位字段的空白对齐占位。
QColor m_clrNormalText; ///< 输入框默认文字颜色。
QString m_sExternalValidationError; ///< 页面级关联校验错误文字。
bool m_bVisible; ///< 参数行是否参与编辑。
bool m_bUpdating; ///< 程序写值时阻止业务信号。
};

@ -0,0 +1,123 @@
#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 QGroupBox;
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 构建指定对象的全部分组和字段。 */
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; ///< 阻止加载数据时触发业务信号。
QGroupBox* m_pObjectDisplayGroup; ///< 对象显示控制分组。
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,93 @@
#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();
int m_nSolverModelType; ///< 打开对话框时的求解器模型类型数值。
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; ///< 编辑器是否初始化完成。
};

@ -1,4 +1,4 @@
#include "nmCalculationDllPebiSolverTask.h"
#include "nmCalculationDllPebiSolverTask.h"
#include "singlePhaseSolver.h"
#include "zxLogInstance.h"
#include "nmDataAnalyzeManager.h"
@ -149,8 +149,11 @@ bool applyPropertyInterpolation(
solverValues->resize(interpolationValues.size());
for(int valueIndex = 0; valueIndex < interpolationValues.size(); ++valueIndex) {
// 插值数据已经使用求解器基准单位,结果可直接按网格顺序写入.
(*solverValues)[valueIndex] = interpolationValues[valueIndex];
// 属性插值数据层以mD保存渗透率在写入PEBI数组时转换为D。
(*solverValues)[valueIndex] = dataSet.property == "k"
? nmCalculationUtils::milliDarcyToDarcy(
interpolationValues[valueIndex])
: interpolationValues[valueIndex];
}
*propertyApplied = true;
}
@ -606,7 +609,11 @@ bool nmCalculationDllPebiSolverTask::execPebiMode()
p0.Base.Swi = pReservoirData->getSwi().getValue().toDouble(); // 初始含水饱和度
// 根据网格单元数设置每个单元的参数
size_t cellCount = pGridInstance->getGridOutput2().Trinodexy.size();
p0.Base.k = dVec1(cellCount, pReservoirData->getPermeability().getValue().toDouble());
// 数据层以mD保存渗透率PEBI输入数组要求使用D。
const double dPermeabilityDarcy =
nmCalculationUtils::milliDarcyToDarcy(
pReservoirData->getPermeability().getValue().toDouble());
p0.Base.k = dVec1(cellCount, dPermeabilityDarcy);
p0.Base.phi = dVec1(cellCount, pReservoirData->getPorosity().getValue().toDouble());
p0.Base.h = dVec1(cellCount, pReservoirData->getThickness().getValue().toDouble());
}

@ -1,4 +1,4 @@
#include "nmCalculationPebiGrid.h"
#include "nmCalculationPebiGrid.h"
#include <Windows.h>
#include <QApplication>
#include <QDebug>
@ -1069,7 +1069,9 @@ bool nmCalculationPebiGrid::generateOutputPara()
scene.Base.Swi = pReservoirData->getSwi().getValue().toDouble();
// 保存参考值用于Python采样
scene.Base.k_ref = pReservoirData->getPermeability().getValue().toDouble();
// 训练场景最终会直接填入PEBI输入结构写文件前转换为求解器使用的D。
scene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(
pReservoirData->getPermeability().getValue().toDouble());
scene.Base.phi_ref = pReservoirData->getPorosity().getValue().toDouble();
scene.Base.h_ref = pReservoirData->getThickness().getValue().toDouble();
}

@ -37,6 +37,12 @@ void setKrigingError(QString* errorMessage, const QString& message)
nmCalculationUtils::nmCalculationUtils() {
}
double nmCalculationUtils::milliDarcyToDarcy(double dPermeabilityMilliDarcy)
{
// PEBI输入结构使用D数据层统一使用mD因此仅在求解器边界换算。
return dPermeabilityMilliDarcy / 1000.0;
}
bool nmCalculationUtils::copyFileToDir(QString filePath, QString destDir) {
// 如果文件不存在,则退出
if(!QFile::exists(filePath)) {

@ -1,4 +1,4 @@
#include "nmDataAttribute.h"
#include "nmDataAttribute.h"
#include <QDebug>
@ -413,7 +413,8 @@ UnitType nmDataAttribute::detectUnitType(const QString& unit) {
}
// 渗透率单位
if (unit == "md" || unit == "Darcy" || unit == "m^2" || unit == "cm^2" || unit == "um^2") {
if (unit == "mD" || unit == "D" || unit == "md" || unit == "Darcy" ||
unit == "m^2" || unit == "cm^2" || unit == "um^2") {
return UNIT_TYPE_PERMEABILITY;
}
@ -578,9 +579,9 @@ double nmDataAttribute::convertPermeabilityUnit(double value, const QString& fro
// 统一转换为Darcy
double valueInDarcy = value;
if (fromUnit == "md") {
valueInDarcy = value / 1000.0; // 1 Darcy = 1000 md
} else if (fromUnit == "Darcy") {
if (fromUnit == "mD" || fromUnit == "md") {
valueInDarcy = value / 1000.0; // 1 D = 1000 mD
} else if (fromUnit == "D" || fromUnit == "Darcy") {
valueInDarcy = value;
} else if (fromUnit == "m^2") {
valueInDarcy = value * 9.869233e-13; // 1 m^2 = 9.869233e-13 Darcy
@ -593,9 +594,9 @@ double nmDataAttribute::convertPermeabilityUnit(double value, const QString& fro
}
// 从Darcy转换为目标单位
if (toUnit == "Darcy") {
if (toUnit == "D" || toUnit == "Darcy") {
return valueInDarcy;
} else if (toUnit == "md") {
} else if (toUnit == "mD" || toUnit == "md") {
return valueInDarcy * 1000.0;
} else if (toUnit == "m^2") {
return valueInDarcy / 9.869233e-13;

@ -15,7 +15,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_fractureHalfLengthSelected = false; // 仅压裂井可用,默认不选中
// 拟合上下界不再使用固定默认值,由自动拟合窗口按数据对象初值和物理边界生成。
m_permeabilityMax = nmDataAttribute("Permeability Max", QVariant(), "Darcy");
m_permeabilityMax = nmDataAttribute("Permeability Max", QVariant(), "mD");
m_skinMax = nmDataAttribute("Skin Max", QVariant(), "");
m_wellboreStorageMax = nmDataAttribute("Wellbore Storage Max", QVariant(), "m^3/MPa");
m_porosityMax = nmDataAttribute("Porosity Max", QVariant(), "");
@ -26,7 +26,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_fractureConductivityMax = nmDataAttribute("Fracture Conductivity Max", QVariant(), "md.m");
m_fractureHalfLengthMax = nmDataAttribute("Fracture Half Length Max", QVariant(), "m");
m_permeabilityMin = nmDataAttribute("Permeability Min", QVariant(), "Darcy");
m_permeabilityMin = nmDataAttribute("Permeability Min", QVariant(), "mD");
m_skinMin = nmDataAttribute("Skin Min", QVariant(), "");
m_wellboreStorageMin = nmDataAttribute("Wellbore Storage Min", QVariant(), "m^3/MPa");
m_porosityMin = nmDataAttribute("Porosity Min", QVariant(), "");
@ -34,7 +34,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_ctMin = nmDataAttribute("Ct Min", QVariant(), "");
m_cfMin = nmDataAttribute("Cf Min", QVariant(), "");
m_swiMin = nmDataAttribute("Swi Min", QVariant(), "");
m_fractureConductivityMin = nmDataAttribute("Fracture Conductivity Min", QVariant(), "md.m");
m_fractureConductivityMin = nmDataAttribute("Fracture Conductivity Min", QVariant(), "mD.m");
m_fractureHalfLengthMin = nmDataAttribute("Fracture Half Length Min", QVariant(), "m");
// 初始化迭代参数
@ -187,6 +187,8 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
// 反序列化参数最大值
if (jsonValue.HasMember("PermeabilityMax") && jsonValue["PermeabilityMax"].IsObject()) {
m_permeabilityMax.FromJsonValue(jsonValue["PermeabilityMax"]);
// 自动拟合上下限统一按数据层基准单位mD解释不兼容旧JSON的D数值。
m_permeabilityMax.setUnit("mD");
}
if (jsonValue.HasMember("SkinMax") && jsonValue["SkinMax"].IsObject()) {
m_skinMax.FromJsonValue(jsonValue["SkinMax"]);
@ -219,6 +221,7 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
// 反序列化参数最小值
if (jsonValue.HasMember("PermeabilityMin") && jsonValue["PermeabilityMin"].IsObject()) {
m_permeabilityMin.FromJsonValue(jsonValue["PermeabilityMin"]);
m_permeabilityMin.setUnit("mD");
}
if (jsonValue.HasMember("SkinMin") && jsonValue["SkinMin"].IsObject()) {
m_skinMin.FromJsonValue(jsonValue["SkinMin"]);

@ -1,4 +1,4 @@
#include "nmDataDiagnostic.h"
#include "nmDataDiagnostic.h"
#include "nmDataWellBase.h"
#include "nmDataReservoir.h"
#include <QDebug>
@ -15,7 +15,7 @@ nmDataDiagnostic::nmDataDiagnostic()
{
m_diagnosticSkin = nmDataAttribute("Skin", 0.0, "");
m_diagnosticWellboreStorage = nmDataAttribute("Wellbore storage", 0.01, "m^3/MPa", UNIT_TYPE_COMPRESSIBILITY, QStringList(), QStringList() << "bbl/psi" << "m^3/bar" << "m^3/kPa" << "m^3/Pa" << "m^3.cm^2/kg" << "m^2" << "m^3/MPa");
m_diagnosticPermeability = nmDataAttribute("Permeability", 0.025, "md", UNIT_TYPE_PERMEABILITY, QStringList(), QStringList() << "md" << "Darcy" << "m^2" << "cm^2" << "um^2");
m_diagnosticPermeability = nmDataAttribute("Permeability", 25.0, "mD", UNIT_TYPE_PERMEABILITY, QStringList(), QStringList() << "mD" << "D" << "m^2" << "cm^2" << "um^2");
m_diagnosticTransmissibility = nmDataAttribute("Transmissibility", 1000.0, "md.m", UNIT_TYPE_CONDUCTIVITY, QStringList(), QStringList()<< "md.ft" << "md.m" << "m^3");
}
@ -258,7 +258,7 @@ void nmDataDiagnostic::calculateTransmissibility(const QVector<QVector<double>>&
DEBUG_OUT(QString("=== Transmissibility Analysis Result ==="));
DEBUG_OUT(QString("Selected method: %1").arg(selectedAnalysis->description));
DEBUG_OUT(QString("Transmissibility (kh): %1 md*m").arg(khValue, 0, 'f', 6));
DEBUG_OUT(QString("Transmissibility (kh): %1 mD*m").arg(khValue, 0, 'f', 6));
DEBUG_OUT(QString("Analysis time: %1 hr").arg(selectedAnalysis->avgTime, 0, 'f', 4));
DEBUG_OUT(QString("Derivative value used: %1 MPa").arg(selectedAnalysis->avgPressure, 0, 'f', 6));
} else {
@ -292,9 +292,9 @@ void nmDataDiagnostic::calculatePermeability(nmDataWellBase* pWell, nmDataReserv
m_diagnosticPermeability.setValue(kValue);
DEBUG_OUT(QString("=== Permeability Analysis Result ==="));
DEBUG_OUT(QString("Transmissibility (kh): %1 md*m").arg(khValue, 0, 'f', 6));
DEBUG_OUT(QString("Transmissibility (kh): %1 mD*m").arg(khValue, 0, 'f', 6));
DEBUG_OUT(QString("Reservoir thickness (h): %1 m").arg(thickness, 0, 'f', 2));
DEBUG_OUT(QString("Permeability (k): %1 md").arg(kValue, 0, 'f', 6));
DEBUG_OUT(QString("Permeability (k): %1 mD").arg(kValue, 0, 'f', 6));
}
//void nmDataDiagnostic::calculateSkin(const QVector<QVector<double>>& rawData,

@ -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");
// 数据层基准单位固定为mD进入PEBI求解器前再统一转换为D。
m_permeability = nmDataAttribute("Permeability", 25.0, "mD");
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());
@ -40,7 +41,7 @@ nmDataReservoir::nmDataReservoir() {
void nmDataReservoir::resetToDefaults()
{
m_permeability.setValue(0.025);
m_permeability.setValue(25.0);
m_transmissibility.setValue(1000.0);
}
@ -147,6 +148,8 @@ void nmDataReservoir::FromJsonValue(const rapidjson::Value& jsonValue)
}
if (jsonValue.HasMember("Permeability") && jsonValue["Permeability"].IsObject()) {
m_permeability.FromJsonValue(jsonValue["Permeability"]);
// JSON中的单位文本不参与兼容换算读取后的数据层单位始终固定为mD。
m_permeability.setUnit("mD");
}
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_Miuo = nmDataAttribute("Miuo", vec[n++].toString(), "mPa.s");
m_permeability = nmDataAttribute("Permeability", vec[n++].toString(), "mD");
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("Miuo", tempValue); m_Miuo = nmDataAttribute("Miuo", tempValue, "mPa.s");
// 序列化格式只保存数值读取时明确按数据层基准单位mD解释。
ser->read("Permeability", tempValue); m_permeability = nmDataAttribute("Permeability", tempValue, "mD");
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"
@ -1979,10 +1980,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()

File diff suppressed because it is too large Load Diff

@ -0,0 +1,487 @@
#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 <QPalette>
#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);
}
/** @brief 根据有效状态生成悬浮提示文字。 */
QString validationToolTip(const QString& sText, bool bValid)
{
if (bValid)
{
return sText;
}
QString sEscaped = sText;
sEscaped.replace("&", "&amp;");
sEscaped.replace("<", "&lt;");
sEscaped.replace(">", "&gt;");
sEscaped.replace("\"", "&quot;");
sEscaped.replace("\n", "<br/>");
return "<span style=\"color:red;\">" +
sEscaped + "</span>";
}
}
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_clrNormalText(),
m_sExternalValidationError(),
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,
int nColumnOffset,
QString& sError)
{
if (pLayout == nullptr)
{
sError = fieldText("The parameter layout is unavailable.");
return false;
}
// 第一步枚举字段不依赖XML数值定义直接创建稳定code下拉框。
if (isEnum())
{
createEnumEditor(pLayout, nRow, nColumnOffset);
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, nColumnOffset, 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,
int nColumnOffset,
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_clrNormalText = m_pLineEdit->palette().color(QPalette::Text);
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, nColumnOffset);
pLayout->addWidget(m_pLineEdit, nRow, nColumnOffset + 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, nColumnOffset + 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, nColumnOffset + 2);
}
updateValidationAppearance(true);
sError.clear();
return true;
}
void nmReservoirParameterField::createEnumEditor(QGridLayout* pLayout,
int nRow,
int nColumnOffset)
{
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, nColumnOffset);
pLayout->addWidget(m_pValueComboBox, nRow, nColumnOffset + 1);
pLayout->addWidget(m_pUnitLabel, nRow, nColumnOffset + 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();
}
void nmReservoirParameterField::setExternalValidationError(
const QString& sError)
{
m_sExternalValidationError = sError;
validate();
}
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);
}
}
bValid = bValid && m_sExternalValidationError.isEmpty();
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)
{
if (m_pLineEdit == nullptr || m_pParameter == nullptr)
{
return;
}
// 只修改文字色避免Qt 4.8样式表绘制覆盖原生输入框边框。
QPalette oPalette = m_pLineEdit->palette();
oPalette.setColor(QPalette::Text,
bValid ? m_clrNormalText : QColor(Qt::red));
m_pLineEdit->setPalette(oPalette);
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);
// 参数别名来自当前语言XML稳定ID便于定位参数定义。
QString sHelp = m_pParameter->m_sAlias;
if (!m_pParameter->m_sName.isEmpty())
{
if (!sHelp.isEmpty())
{
sHelp += "[" + m_pParameter->m_sName + "]";
}
else
{
sHelp = m_pParameter->m_sName;
}
}
if (!sRange.isEmpty())
{
if (!sHelp.isEmpty())
{
sHelp += "\n";
}
sHelp += sRange;
}
if (!m_sExternalValidationError.isEmpty())
{
if (!sHelp.isEmpty())
{
sHelp += "\n";
}
sHelp += m_sExternalValidationError;
}
m_pLineEdit->setToolTip(validationToolTip(sHelp, bValid));
m_pLineEdit->setWhatsThis(sHelp);
if (m_pLabel != nullptr)
{
m_pLabel->setWhatsThis(sHelp);
}
}

@ -0,0 +1,551 @@
#include "nmReservoirParameterPage.h"
#include "nmReservoirParameterField.h"
#include "nmReservoirPropertiesSession.h"
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QSizePolicy>
#include <QVBoxLayout>
#include <QtGlobal>
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_pObjectDisplayGroup(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);
m_pObjectDisplayGroup = new QGroupBox(tr("Object display"), this);
m_pObjectDisplayGroup->setSizePolicy(
QSizePolicy::Expanding, QSizePolicy::Fixed);
QHBoxLayout* pObjectLayout = new QHBoxLayout(m_pObjectDisplayGroup);
pObjectLayout->setContentsMargins(8, 15, 8, 8);
pObjectLayout->setSpacing(9);
m_pObjectLabel = new QLabel(tr("Display:"), m_pObjectDisplayGroup);
m_pObjectLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
m_pObjectComboBox = new QComboBox(m_pObjectDisplayGroup);
m_pObjectComboBox->setMinimumWidth(260);
m_pObjectComboBox->setMaximumWidth(420);
m_pObjectComboBox->setMinimumHeight(27);
pObjectLayout->addStretch(1);
pObjectLayout->addWidget(m_pObjectLabel);
pObjectLayout->addWidget(m_pObjectComboBox);
pObjectLayout->addStretch(1);
m_pMainLayout->addWidget(m_pObjectDisplayGroup);
m_pObjectDisplayGroup->setVisible(
m_eCategory != NM_RESERVOIR_PAGE_RESERVOIR);
// 第二步:分组载体没有边框,仅用于实现原型中的两列网格。
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)));
}
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_pObjectDisplayGroup->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);
int nFieldColumnOffset = 0;
// 字段较少的通栏分组使用独立的左右弹性列,
// 使参数区域保持居中,输入列仍可随窗口宽度适度伸展。
if (oGroupInfo.m_bFieldsCentered)
{
nFieldColumnOffset = 1;
pFieldLayout->setColumnStretch(0, 1);
pFieldLayout->setColumnStretch(1, 0);
pFieldLayout->setColumnStretch(2, 1);
pFieldLayout->setColumnStretch(3, 0);
pFieldLayout->setColumnStretch(4, 1);
}
else
{
// 常规分组由最长标签决定标签列宽,数值列和单位列按比例伸展。
pFieldLayout->setColumnStretch(0, 0);
pFieldLayout->setColumnStretch(1, 8);
pFieldLayout->setColumnStretch(2, 5);
}
pFieldLayout->setColumnMinimumWidth(
nFieldColumnOffset + 1,
nmReservoirParameterField::editorMinimumWidth());
pFieldLayout->setColumnMinimumWidth(
nFieldColumnOffset + 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,
nFieldColumnOffset,
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()
{
nmReservoirParameterField* pSoiField =
m_mapFields.value("Soi", nullptr);
nmReservoirParameterField* pSwiField =
m_mapFields.value("Swi", nullptr);
if (pSoiField != nullptr)
{
pSoiField->setExternalValidationError(QString());
}
if (pSwiField != nullptr)
{
pSwiField->setExternalValidationError(QString());
}
bool bAllValid = true;
for (int nFieldIndex = 0;
nFieldIndex < m_listFields.size();
++nFieldIndex)
{
nmReservoirParameterField* pField =
m_listFields[nFieldIndex];
if (pField != nullptr && pField->isFieldVisible() &&
!pField->validate())
{
bAllValid = false;
}
}
// 油水两相同时显示两个初始饱和度时两者之和必须为1。
if (pSoiField != nullptr && pSwiField != nullptr &&
pSoiField->isFieldVisible() &&
pSwiField->isFieldVisible())
{
bool bSoiValid = false;
bool bSwiValid = false;
QVariant oSoi = pSoiField->baseValue(bSoiValid);
QVariant oSwi = pSwiField->baseValue(bSwiValid);
const double dTolerance = 1.0e-6;
if (bSoiValid && bSwiValid &&
qAbs(oSoi.toDouble() + oSwi.toDouble() - 1.0) >
dTolerance)
{
QString sError = tr(
"Initial oil and water saturations must sum to 1.");
pSoiField->setExternalValidationError(sError);
pSwiField->setExternalValidationError(sError);
bAllValid = false;
}
}
return bAllValid;
}
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,122 @@
#include "nmReservoirPropertiesDataSource.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmWxParameterProperty.h"
nmReservoirPropertiesSnapshot::nmReservoirPropertiesSnapshot()
: m_nSolverModelType(0)
{
}
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_nSolverModelType =
static_cast<int>(m_pDataManager->getSolverModelType());
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;
}

@ -747,7 +747,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("mD")));
// 表皮系数 (Skin)
m_parameterTable->setItem(1, 0, new QTableWidgetItem("2"));

@ -1,4 +1,4 @@
#include "nmWxPropertyInterpolationDlg.h"
#include "nmWxPropertyInterpolationDlg.h"
#include <QAbstractItemView>
#include <QApplication>
@ -47,9 +47,9 @@ namespace
// 表格第0行保留给单位实际测点从第1行开始.
const int POINT_UNIT_ROW = 0;
const int FIRST_POINT_ROW = 1;
// 坐标和厚度以m保存渗透率以D保存但默认使用mD显示.
// 坐标和厚度以m保存渗透率统一以mD保存和显示。
const char* LENGTH_BASE_UNIT = "m";
const char* PERMEABILITY_BASE_UNIT = "D";
const char* PERMEABILITY_BASE_UNIT = "mD";
const char* PERMEABILITY_DISPLAY_UNIT = "mD";
// 使用独立翻译上下文,便于统一维护对话框文本.
@ -92,9 +92,7 @@ QString defaultPropertyDisplayUnit(const QString& property)
QString unitGroupLookupUnit(const QString& unit)
{
// D与时间单位d仅大小写不同使用唯一的mD定位渗透率单位组.
return unit == PERMEABILITY_BASE_UNIT ?
QString(PERMEABILITY_DISPLAY_UNIT) : unit;
return unit;
}
bool convertUnitValue(double sourceValue,

@ -0,0 +1,504 @@
#include "nmWxReservoirPropertiesEditor.h"
#include "nmReservoirPropertiesDataSource.h"
#include "nmReservoirPropertiesSession.h"
#include "nmReservoirParameterPage.h"
#include "iUnitGroup.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>
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, 480);
}
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)
{
// 参数XML已统一使用无歧义的mD作为渗透率基准单位所有字段直接复用框架绑定器。
return bindUnitPairWxs(pLineEdit, pUnitComboBox,
sBaseParameterId, false);
}
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()
{
// 顶部和底部帮助命令统一打开系统帮助文档。
ZxBaseUtil::startHelp();
}
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