From 3ce81c4eca166496c6390ed46f0b84a43dfe10bd Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 16 Sep 2026 17:22:02 +0200 Subject: [PATCH] #14772 Python: Add GridCaseGroup.replace_source_cases as object method and forward replaceSourceCases to it RimIdenticalGridCaseGroup_replaceSourceCases takes the grid file paths directly (instead of a list file) and an optional ProjectFile, following the same design as Case.replace_grid. RicfReplaceSourceCases reads the list file and the executor's last project path and forwards to it. Project.replace_source_cases() in Python is kept and emits a DeprecationWarning. --- .../RicfReplaceSourceCases.cpp | 48 +++++-------- .../RimcIdenticalGridCaseGroup.cpp | 72 +++++++++++++++++++ .../RimcIdenticalGridCaseGroup.h | 23 ++++++ GrpcInterface/Python/rips/project.py | 8 +++ .../Python/rips/tests/test_project.py | 35 +++++++++ 5 files changed, 155 insertions(+), 31 deletions(-) diff --git a/ApplicationLibCode/CommandFileInterface/RicfReplaceSourceCases.cpp b/ApplicationLibCode/CommandFileInterface/RicfReplaceSourceCases.cpp index a3ca981659..dc0e4a0946 100644 --- a/ApplicationLibCode/CommandFileInterface/RicfReplaceSourceCases.cpp +++ b/ApplicationLibCode/CommandFileInterface/RicfReplaceSourceCases.cpp @@ -19,10 +19,12 @@ #include "RicfReplaceSourceCases.h" #include "RicfCommandFileExecutor.h" +#include "RicfCommandForwarding.h" #include "RiaApplication.h" -#include "RiaLogging.h" -#include "RiaProjectModifier.h" + +#include "RimIdenticalGridCaseGroup.h" +#include "RimcIdenticalGridCaseGroup.h" #include "cafPdmFieldScriptingCapability.h" @@ -42,41 +44,25 @@ RicfReplaceSourceCases::RicfReplaceSourceCases() //-------------------------------------------------------------------------------------------------- caf::PdmScriptResponse RicfReplaceSourceCases::execute() { - if ( m_gridListFile().isNull() ) - { - QString error( "replaceSourceCases: Required parameter gridListFile." ); - RiaLogging::error( error.toStdString() ); - return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, error ); - } + const QString commandName = classKeyword(); + if ( m_gridListFile().isNull() ) return RicfForwarding::errorResponse( "Required parameter gridListFile.", commandName ); + + // The legacy command reloads the project opened by 'openProject', which is not necessarily saved. QString lastProjectPath = RicfCommandFileExecutor::instance()->getLastProjectPath(); if ( lastProjectPath.isNull() ) { - QString error( "replaceSourceCases: 'openProject' must be called before 'replaceSourceCases' to specify " - "project file to replace cases in." ); - RiaLogging::error( error.toStdString() ); - return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, error ); + return RicfForwarding::errorResponse( "'openProject' must be called before 'replaceSourceCases' to specify project file to replace " + "cases in.", + commandName ); } - cvf::ref projectModifier = new RiaProjectModifier; + auto caseGroup = RicfForwarding::findCaseGroup( m_caseGroupId() ); + if ( !caseGroup ) return RicfForwarding::errorResponse( caseGroup.error(), commandName ); - std::vector listFileNames = RiaApplication::readFileListFromTextFile( m_gridListFile() ); - if ( m_caseGroupId() == -1 ) - { - projectModifier->setReplaceSourceCasesFirstOccurrence( listFileNames ); - } - else - { - projectModifier->setReplaceSourceCasesById( m_caseGroupId(), listFileNames ); - } + RimIdenticalGridCaseGroup_replaceSourceCases method( caseGroup.value() ); + method.setGridFiles( RiaApplication::readFileListFromTextFile( m_gridListFile() ) ); + method.setProjectFile( lastProjectPath ); - if ( !RiaApplication::instance()->loadProject( lastProjectPath, - RiaApplication::ProjectLoadAction::PLA_CALCULATE_STATISTICS, - projectModifier.p() ) ) - { - QString error( "Could not reload project" ); - RiaLogging::error( error.toStdString() ); - return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, error ); - } - return caf::PdmScriptResponse(); + return RicfForwarding::toScriptResponse( method.execute(), commandName ); } diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.cpp index 3d40428c46..c13e4b64f7 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.cpp @@ -18,6 +18,9 @@ #include "RimcIdenticalGridCaseGroup.h" +#include "RiaApplication.h" +#include "RiaProjectModifier.h" + #include "RimEclipseStatisticsCase.h" #include "RimIdenticalGridCaseGroup.h" #include "RimProject.h" @@ -78,3 +81,72 @@ QString RimcIdenticalGridCaseGroup_createStatisticsCase::classKeywordReturnedTyp { return RimEclipseStatisticsCase::classKeywordStatic(); } + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimIdenticalGridCaseGroup, RimIdenticalGridCaseGroup_replaceSourceCases, "replaceSourceCases" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimIdenticalGridCaseGroup_replaceSourceCases::RimIdenticalGridCaseGroup_replaceSourceCases( caf::PdmObjectHandle* self ) + : caf::PdmVoidObjectMethod( self ) +{ + CAF_PDM_InitObject( "Replace Source Cases", "", "", "Replace all source cases of the group with the given grid files and reload the project" ); + + CAF_PDM_InitScriptableField( &m_gridFiles, "GridFiles", std::vector(), "Grid Files", "", "", "Paths to the new grid files" ); + CAF_PDM_InitScriptableField( &m_projectFile, + "ProjectFile", + QString(), + "Project File", + "", + "", + "Optional project file to reload. Defaults to the current project file." ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIdenticalGridCaseGroup_replaceSourceCases::setGridFiles( const std::vector& gridFiles ) +{ + m_gridFiles = gridFiles; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIdenticalGridCaseGroup_replaceSourceCases::setProjectFile( const QString& projectFile ) +{ + m_projectFile = projectFile; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimIdenticalGridCaseGroup_replaceSourceCases::execute() +{ + auto* caseGroup = self(); + if ( !caseGroup ) return std::unexpected( "No grid case group is available." ); + + if ( m_gridFiles().empty() ) return std::unexpected( "No grid files specified." ); + + QString projectPath = m_projectFile(); + if ( projectPath.isEmpty() ) + { + RimProject* project = RimProject::current(); + if ( project ) projectPath = project->fileName(); + } + + if ( projectPath.isEmpty() ) + { + return std::unexpected( "The project must be saved as a file before replacing the source cases of a group." ); + } + + cvf::ref projectModifier = cvf::make_ref(); + projectModifier->setReplaceSourceCasesById( caseGroup->groupId(), m_gridFiles() ); + + if ( !RiaApplication::instance()->loadProject( projectPath, RiaApplication::ProjectLoadAction::PLA_CALCULATE_STATISTICS, projectModifier.p() ) ) + { + return std::unexpected( "Could not reload project" ); + } + + return nullptr; +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.h b/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.h index aa95cd5712..ce9307321e 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.h +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcIdenticalGridCaseGroup.h @@ -44,3 +44,26 @@ public: private: caf::PdmField m_populateResultSelection; }; + +//================================================================================================== +/// Replace all source cases of the grid case group with the given grid files and reload the project. +/// +/// The project must be saved to file, as the replacement is performed by reloading the project +/// through a RiaProjectModifier. Statistics are recomputed after the reload. +//================================================================================================== +class RimIdenticalGridCaseGroup_replaceSourceCases : public caf::PdmVoidObjectMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimIdenticalGridCaseGroup_replaceSourceCases( caf::PdmObjectHandle* self ); + + void setGridFiles( const std::vector& gridFiles ); + void setProjectFile( const QString& projectFile ); + + std::expected execute() override; + +private: + caf::PdmField> m_gridFiles; + caf::PdmField m_projectFile; +}; diff --git a/GrpcInterface/Python/rips/project.py b/GrpcInterface/Python/rips/project.py index 2ef1e1b9a3..89a52a9211 100644 --- a/GrpcInterface/Python/rips/project.py +++ b/GrpcInterface/Python/rips/project.py @@ -142,10 +142,18 @@ def case(self: Project, case_id: int) -> Optional[Reservoir]: def replace_source_cases(self, grid_list_file, case_group_id=0): """Replace all source grid cases within a case group + Deprecated: use GridCaseGroup.replace_source_cases(grid_files=[...]) on the group object instead, + which takes the grid file paths directly. + Arguments: grid_list_file (str): path to file containing a list of cases case_group_id (int): id of the case group to replace """ + warnings.warn( + "Project.replace_source_cases() is deprecated, use GridCaseGroup.replace_source_cases(grid_files=[...]) instead", + DeprecationWarning, + stacklevel=3, + ) return self._execute_command( replaceSourceCases=Commands_pb2.ReplaceSourceCasesRequest( gridListFile=grid_list_file, caseGroupId=case_group_id diff --git a/GrpcInterface/Python/rips/tests/test_project.py b/GrpcInterface/Python/rips/tests/test_project.py index f22eb84992..e2e6a7dc9e 100644 --- a/GrpcInterface/Python/rips/tests/test_project.py +++ b/GrpcInterface/Python/rips/tests/test_project.py @@ -101,6 +101,41 @@ def test_statistics_case_object_methods(rips_instance, initialize_test): grid_case_group.compute_statistics(case_ids=[stat_case.id]) +def test_replace_source_cases(rips_instance, initialize_test): + case_paths = [ + dataroot.PATH + "/Case_with_10_timesteps/Real0/BRUGGE_0000.EGRID", + dataroot.PATH + "/Case_with_10_timesteps/Real10/BRUGGE_0010.EGRID", + ] + project = rips_instance.project + grid_case_group = project.create_grid_case_group(case_paths=case_paths) + group_id = grid_case_group.group_id + assert len(grid_case_group.descendants(rips.EclipseCase)) == 2 + + # Reload requires a saved project + with pytest.raises(rips.RipsError): + grid_case_group.replace_source_cases(grid_files=case_paths[:1]) + + with tempfile.TemporaryDirectory(prefix="rips") as tmpdirname: + project.save(os.path.join(tmpdirname, "group.rsp")) + project = rips_instance.project + grid_case_group = project.grid_case_group(group_id) + + new_paths = [ + dataroot.PATH + "/Case_with_10_timesteps/Real30/BRUGGE_0030.EGRID", + dataroot.PATH + "/Case_with_10_timesteps/Real40/BRUGGE_0040.EGRID", + dataroot.PATH + "/Case_with_10_timesteps/Real0/BRUGGE_0000.EGRID", + ] + grid_case_group.replace_source_cases(grid_files=new_paths) + + # The project is reloaded, so retrieve the group again + project = rips_instance.project + grid_case_group = project.grid_case_group(group_id) + assert grid_case_group is not None + source_cases = grid_case_group.descendants(rips.EclipseCase) + names = sorted(c.name for c in source_cases) + assert names == ["BRUGGE_0000", "BRUGGE_0030", "BRUGGE_0040"] + + def test_save_project_round_trip(rips_instance, initialize_test): case_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" project = rips_instance.project