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 2 months 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 稳定参数ID用于会话读写,基础参数ID用于读取XML单位和范围。
*/
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 对象映射、稳定ID、页面分组和联动所需的基础ID全部在此集中维护。
*/
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