From cc1f09b6650da6a2f7a1202106c60ee5f19021d2 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 16 Sep 2026 17:24:23 +0200 Subject: [PATCH] #14772 Python: Add EclipseCase.create_saturation_pressure_plots as object method and forward createSaturationPressurePlots to it RimEclipseResultCase_createSaturationPressurePlots exposes the time step (the legacy command hardcodes 0) and returns an error when no plots could be created. It only shows the plot window when a GUI is running; the legacy code dereferenced RiaGuiApplication::instance() unconditionally. RicfCreateSaturationPressurePlots resolves the cases and loops, treating per-case failures as warnings as before. The hand-written Case.create_saturation_pressure_plots() Python wrapper is removed since the generated EclipseCase method shadows it. --- .../RicfCreateSaturationPressurePlots.cpp | 84 +++++++++---------- .../RimcEclipseCase.cpp | 55 ++++++++++++ .../RimcEclipseCase.h | 18 ++++ GrpcInterface/Python/rips/case.py | 11 --- GrpcInterface/Python/rips/tests/test_cases.py | 8 ++ 5 files changed, 119 insertions(+), 57 deletions(-) diff --git a/ApplicationLibCode/CommandFileInterface/RicfCreateSaturationPressurePlots.cpp b/ApplicationLibCode/CommandFileInterface/RicfCreateSaturationPressurePlots.cpp index 5b0a67fa41..18cbdbb0fe 100644 --- a/ApplicationLibCode/CommandFileInterface/RicfCreateSaturationPressurePlots.cpp +++ b/ApplicationLibCode/CommandFileInterface/RicfCreateSaturationPressurePlots.cpp @@ -19,15 +19,13 @@ #include "RicfCreateSaturationPressurePlots.h" -#include "GridCrossPlotCommands/RicCreateSaturationPressurePlotsFeature.h" - -#include "RiaGuiApplication.h" #include "RiaLogging.h" +#include "RicfCommandForwarding.h" + #include "RimEclipseResultCase.h" -#include "RimMainPlotCollection.h" #include "RimProject.h" -#include "RimSaturationPressurePlotCollection.h" +#include "RimcEclipseCase.h" #include "cafPdmFieldScriptingCapability.h" @@ -46,54 +44,48 @@ RicfCreateSaturationPressurePlots::RicfCreateSaturationPressurePlots() //-------------------------------------------------------------------------------------------------- caf::PdmScriptResponse RicfCreateSaturationPressurePlots::execute() { - std::vector caseIds = m_caseIds(); - if ( caseIds.empty() ) - { - RimProject* project = RimProject::current(); - if ( project ) - { - auto eclipeCases = project->eclipseCases(); - for ( auto c : eclipeCases ) - { - caseIds.push_back( c->caseId() ); - } - } - } - - if ( caseIds.empty() ) - { - QString error( "createSaturationPressurePlots: No cases found" ); - RiaLogging::error( error.toStdString() ); - return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, error ); - } + const QString commandName = classKeyword(); RimProject* project = RimProject::current(); - if ( !project ) - { - QString error( "No project loaded" ); - RiaLogging::error( error.toStdString() ); - return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, error ); - } + if ( !project ) return RicfForwarding::errorResponse( "No project loaded", commandName ); - auto eclipeCases = project->eclipseCases(); - for ( auto c : eclipeCases ) + // Collect the result cases. No ids means all Eclipse result cases in the project. + std::vector resultCases; + if ( m_caseIds().empty() ) { - auto eclipseResultCase = dynamic_cast( c ); - if ( !eclipseResultCase ) continue; - - for ( auto caseId : caseIds ) + for ( RimEclipseCase* c : project->eclipseCases() ) { - if ( c->caseId() == caseId ) - { - int timeStep = 0; - RicCreateSaturationPressurePlotsFeature::createPlots( eclipseResultCase, timeStep ); - } + if ( auto* resultCase = dynamic_cast( c ) ) resultCases.push_back( resultCase ); + } + } + else + { + for ( int caseId : m_caseIds() ) + { + auto rimCase = RicfForwarding::findCase( caseId ); + if ( !rimCase ) return RicfForwarding::errorResponse( rimCase.error(), commandName ); + + if ( auto* resultCase = dynamic_cast( rimCase.value() ) ) resultCases.push_back( resultCase ); } } - RimSaturationPressurePlotCollection* collection = RimMainPlotCollection::current()->saturationPressurePlotCollection(); - collection->updateAllRequiredEditors(); - RiaGuiApplication::instance()->getOrCreateAndShowMainPlotWindow(); + if ( resultCases.empty() ) return RicfForwarding::errorResponse( "No cases found", commandName ); - return caf::PdmScriptResponse(); + caf::PdmScriptResponse response; + for ( RimEclipseResultCase* resultCase : resultCases ) + { + RimEclipseResultCase_createSaturationPressurePlots method( resultCase ); + method.setTimeStep( 0 ); + + auto result = method.execute(); + if ( !result ) + { + // Legacy behavior: a case without the required data is skipped, not an error + QString warning = QString( "%1: %2" ).arg( commandName ).arg( result.error() ); + RiaLogging::warning( warning.toStdString() ); + response.updateStatus( caf::PdmScriptResponse::COMMAND_WARNING, warning ); + } + } + + return response; } diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.cpp index 3935142d35..8b1f88e974 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.cpp @@ -29,6 +29,7 @@ #include "ExportCommands/RicExportLgrFeature.h" #include "FractureCommands/RicCreateMultipleFracturesOptionItemUi.h" #include "FractureCommands/RicCreateMultipleFracturesUi.h" +#include "GridCrossPlotCommands/RicCreateSaturationPressurePlotsFeature.h" #include "RicCreateTemporaryLgrFeature.h" #include "RicDeleteTemporaryLgrsFeature.h" @@ -59,6 +60,7 @@ #include "RimOilField.h" #include "RimProject.h" #include "RimRoffCase.h" +#include "RimSaturationPressurePlotCollection.h" #include "RimWellPath.h" #include "RimWellPathCollection.h" @@ -1450,3 +1452,56 @@ std::expected RimEclipseResultCase_exportFlowCha return nullptr; } + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimEclipseResultCase, RimEclipseResultCase_createSaturationPressurePlots, "createSaturationPressurePlots" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseResultCase_createSaturationPressurePlots::RimEclipseResultCase_createSaturationPressurePlots( caf::PdmObjectHandle* self ) + : caf::PdmVoidObjectMethod( self ) +{ + CAF_PDM_InitObject( "Create Saturation Pressure Plots", + "", + "", + "Create saturation pressure plots (PRESSURE vs PBUB/PDEW per EQUIL region) for the case" ); + + CAF_PDM_InitScriptableField( &m_timeStep, "TimeStep", 0, "Time Step", "", "", "Zero-based time step index" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseResultCase_createSaturationPressurePlots::setTimeStep( int timeStep ) +{ + m_timeStep = timeStep; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimEclipseResultCase_createSaturationPressurePlots::execute() +{ + auto* eclipseCase = self(); + if ( !eclipseCase ) return std::unexpected( "No case is available." ); + + auto plots = RicCreateSaturationPressurePlotsFeature::createPlots( eclipseCase, m_timeStep() ); + if ( plots.empty() ) + { + return std::unexpected( QString( "No saturation pressure plots created for case '%1'. The case must have EQUIL data and the " + "PRESSURE, PBUB and PDEW results." ) + .arg( eclipseCase->caseUserDescription() ) ); + } + + if ( auto* collection = RimMainPlotCollection::current()->saturationPressurePlotCollection() ) + { + collection->updateAllRequiredEditors(); + } + + if ( RiaGuiApplication::isRunning() ) + { + RiaGuiApplication::instance()->getOrCreateAndShowMainPlotWindow(); + } + + return nullptr; +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.h b/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.h index 7ac9d5d159..0ad506c1bc 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.h +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcEclipseCase.h @@ -404,3 +404,21 @@ private: caf::PdmField m_minimumCommunication; caf::PdmField m_aquiferCellThreshold; }; + +//================================================================================================== +/// Create saturation pressure plots (PRESSURE vs PBUB/PDEW per EQUIL region) for the case. +//================================================================================================== +class RimEclipseResultCase_createSaturationPressurePlots : public caf::PdmVoidObjectMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimEclipseResultCase_createSaturationPressurePlots( caf::PdmObjectHandle* self ); + + void setTimeStep( int timeStep ); + + std::expected execute() override; + +private: + caf::PdmField m_timeStep; +}; diff --git a/GrpcInterface/Python/rips/case.py b/GrpcInterface/Python/rips/case.py index 7eeafc1e94..0649876ba0 100644 --- a/GrpcInterface/Python/rips/case.py +++ b/GrpcInterface/Python/rips/case.py @@ -561,17 +561,6 @@ def create_lgr_for_completion( ) -@add_method(Case) -def create_saturation_pressure_plots(self) -> Any: - """ - Create saturation pressure plots for the current case - """ - case_ids = [self.id] - return self._execute_command( - createSaturationPressurePlots=Cmd.CreateSatPressPlotRequest(caseIds=case_ids) - ) - - @add_method(Case) def available_properties( self, diff --git a/GrpcInterface/Python/rips/tests/test_cases.py b/GrpcInterface/Python/rips/tests/test_cases.py index 2c51648a97..b558829f91 100644 --- a/GrpcInterface/Python/rips/tests/test_cases.py +++ b/GrpcInterface/Python/rips/tests/test_cases.py @@ -451,3 +451,11 @@ def test_10k_result_alias(rips_instance, initialize_test): with pytest.raises(rips.RipsError, match="No such result"): case.grid_property(rips.PropertyType.DYNAMIC_NATIVE, "SPOIL", 3) + + +def test_create_saturation_pressure_plots_requires_data(rips_instance, initialize_test): + # None of the test models carry EQUIL/PBUB/PDEW data, so the method must report a clear error + case_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(case_path) + with pytest.raises(rips.RipsError): + case.create_saturation_pressure_plots()