From 147a60ceec896e43a9743b27439fa4f79b9738a5 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 16 Sep 2026 17:42:27 +0200 Subject: [PATCH] #14772 Python: Add Project.import_formation_names and Case.set_formation_names as object methods and forward importFormationNames to them The legacy command combined import and assignment with an applyToCaseId. This is split into RimProject_importFormationNames (returns the FormationNames object) and RimCase_setFormationNames (takes the object). RimFormationNames is made scriptable so it can be returned and passed. RicfImportFormationNames forwards to both. Project.import_formation_names() in Python keeps its signature, now returns the FormationNames object, and gains an apply_to_all_cases flag (default True, matching the old behavior). Case.import_formation_names() is kept and emits a DeprecationWarning. --- .../RicfImportFormationNames.cpp | 80 ++++++++----------- .../Formations/RimFormationNames.cpp | 11 ++- .../ProjectDataModelCommands/RimcCase.cpp | 45 +++++++++++ .../ProjectDataModelCommands/RimcCase.h | 20 +++++ .../ProjectDataModelCommands/RimcProject.cpp | 69 ++++++++++++++++ .../ProjectDataModelCommands/RimcProject.h | 21 +++++ GrpcInterface/Python/rips/case.py | 21 ++--- GrpcInterface/Python/rips/project.py | 19 +++-- .../Python/rips/tests/test_project.py | 27 +++++++ 9 files changed, 249 insertions(+), 64 deletions(-) diff --git a/ApplicationLibCode/CommandFileInterface/RicfImportFormationNames.cpp b/ApplicationLibCode/CommandFileInterface/RicfImportFormationNames.cpp index 87976f94a8..95ac2bec58 100644 --- a/ApplicationLibCode/CommandFileInterface/RicfImportFormationNames.cpp +++ b/ApplicationLibCode/CommandFileInterface/RicfImportFormationNames.cpp @@ -17,16 +17,16 @@ ///////////////////////////////////////////////////////////////////////////////// #include "RicfImportFormationNames.h" -#include "RicImportFormationNamesFeature.h" +#include "RicfCommandForwarding.h" #include "Formations/RimFormationNames.h" #include "RimCase.h" #include "RimProject.h" +#include "RimcCase.h" +#include "RimcProject.h" #include "cafPdmFieldScriptingCapability.h" -#include - CAF_PDM_SOURCE_INIT( RicfImportFormationNames, "importFormationNames" ); //-------------------------------------------------------------------------------------------------- @@ -43,57 +43,43 @@ RicfImportFormationNames::RicfImportFormationNames() //-------------------------------------------------------------------------------------------------- caf::PdmScriptResponse RicfImportFormationNames::execute() { - QStringList errorMessages, warningMessages; + const QString commandName = classKeyword(); - if ( !m_formationFiles().empty() ) + RimProject_importFormationNames importMethod( RimProject::current() ); + importMethod.setFormationFiles( m_formationFiles() ); + + auto importResult = importMethod.execute(); + if ( !importResult ) return RicfForwarding::errorResponse( importResult.error(), commandName ); + + auto* formationNames = dynamic_cast( importResult.value() ); + if ( !formationNames ) return RicfForwarding::errorResponse( "Imported object is not a formation names object", commandName ); + + // Apply to the given case, or to all grid cases when applyToCaseId is -1 + std::vector cases; + if ( m_applyToCaseId() == -1 ) { - QStringList formationFileList; - for ( QString formationFile : m_formationFiles() ) - { - if ( QFileInfo::exists( formationFile ) ) - { - formationFileList.push_back( formationFile ); - } - else - { - errorMessages.push_back( QString( "%1 does not exist" ).arg( formationFile ) ); - } - } - - RimFormationNames* formationNames = RicImportFormationNamesFeature::importFormationFiles( formationFileList ); - if ( formationNames ) - { - bool foundCase = false; - std::vector cases = RimProject::current()->allGridCases(); - for ( RimCase* rimCase : cases ) - { - if ( m_applyToCaseId() == -1 || ( rimCase->caseId() == m_applyToCaseId() ) ) - { - rimCase->setFormationNames( formationNames ); - rimCase->updateFormationNamesData(); - rimCase->updateConnectedEditors(); - foundCase = true; - } - } - if ( m_applyToCaseId() != -1 && !foundCase ) - { - warningMessages << "Could not find the case to apply the formations to"; - } - } + cases = RimProject::current()->allGridCases(); } else { - errorMessages << "No formation files provided"; + auto rimCase = RicfForwarding::findCase( m_applyToCaseId() ); + if ( !rimCase ) + { + caf::PdmScriptResponse response; + response.updateStatus( caf::PdmScriptResponse::COMMAND_WARNING, "Could not find the case to apply the formations to" ); + return response; + } + cases.push_back( rimCase.value() ); } - caf::PdmScriptResponse response; - for ( QString warningMessage : warningMessages ) + for ( RimCase* rimCase : cases ) { - response.updateStatus( caf::PdmScriptResponse::COMMAND_WARNING, warningMessage ); + RimCase_setFormationNames setMethod( rimCase ); + setMethod.setFormationNames( formationNames ); + + auto result = setMethod.execute(); + if ( !result ) return RicfForwarding::errorResponse( result.error(), commandName ); } - for ( QString errorMessage : errorMessages ) - { - response.updateStatus( caf::PdmScriptResponse::COMMAND_ERROR, errorMessage ); - } - return response; + + return caf::PdmScriptResponse(); } diff --git a/ApplicationLibCode/ProjectDataModel/Formations/RimFormationNames.cpp b/ApplicationLibCode/ProjectDataModel/Formations/RimFormationNames.cpp index d6ac3e1477..0091fa399e 100644 --- a/ApplicationLibCode/ProjectDataModel/Formations/RimFormationNames.cpp +++ b/ApplicationLibCode/ProjectDataModel/Formations/RimFormationNames.cpp @@ -31,6 +31,8 @@ #include "RiuPlotMainWindowTools.h" #include "cafAssert.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" #include "cafPdmUiFilePathEditor.h" #include @@ -43,9 +45,14 @@ CAF_PDM_SOURCE_INIT( RimFormationNames, "FormationNames" ); //-------------------------------------------------------------------------------------------------- RimFormationNames::RimFormationNames() { - CAF_PDM_InitObject( "Formation Names", ":/Formations16x16.png" ); + CAF_PDM_InitScriptableObjectWithNameAndComment( "Formation Names", + ":/Formations16x16.png", + "", + "", + "FormationNames", + "Formation names imported from a .lyr or similar file" ); - CAF_PDM_InitFieldNoDefault( &m_formationNamesFileName, "FormationNamesFileName", "File Name" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_formationNamesFileName, "FormationNamesFileName", "File Name" ); m_formationNamesFileName.uiCapability()->setUiEditorTypeName( caf::PdmUiFilePathEditor::uiEditorTypeName() ); diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcCase.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcCase.cpp index 81a2d4ec64..a04b9db076 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcCase.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcCase.cpp @@ -21,6 +21,7 @@ #include "RiaApplication.h" #include "RiaProjectModifier.h" +#include "Formations/RimFormationNames.h" #include "Rim3dView.h" #include "RimCase.h" #include "RimEclipseCase.h" @@ -167,3 +168,47 @@ QString RimCase_createView::classKeywordReturnedType() const { return Rim3dView::classKeywordStatic(); } + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimCase, RimCase_setFormationNames, "setFormationNames" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCase_setFormationNames::RimCase_setFormationNames( caf::PdmObjectHandle* self ) + : caf::PdmVoidObjectMethod( self ) +{ + CAF_PDM_InitObject( "Set Formation Names", "", "", "Set the active formation names of the case" ); + + CAF_PDM_InitScriptableFieldNoDefault( &m_formationNames, + "FormationNames", + "Formation Names", + "", + "", + "Formation names object, e.g. from Project.import_formation_names()" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCase_setFormationNames::setFormationNames( RimFormationNames* formationNames ) +{ + m_formationNames = formationNames; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimCase_setFormationNames::execute() +{ + auto* rimCase = self(); + if ( !rimCase ) return std::unexpected( "No case is available." ); + + RimFormationNames* formationNames = m_formationNames(); + if ( !formationNames ) return std::unexpected( "No formation names specified." ); + + rimCase->setFormationNames( formationNames ); + rimCase->updateFormationNamesData(); + rimCase->updateConnectedEditors(); + + return nullptr; +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcCase.h b/ApplicationLibCode/ProjectDataModelCommands/RimcCase.h index 9110f85ef3..6928b6d898 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcCase.h +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcCase.h @@ -21,10 +21,12 @@ #include "cafPdmField.h" #include "cafPdmObjectHandle.h" #include "cafPdmObjectMethod.h" +#include "cafPdmPtrField.h" #include class Rim3dView; +class RimFormationNames; //================================================================================================== /// Replace the grid file of a case (Eclipse result case or GeoMech case) and reload the project. @@ -63,3 +65,21 @@ public: std::expected execute() override; QString classKeywordReturnedType() const override; }; + +//================================================================================================== +/// Set the active formation names of the case. +//================================================================================================== +class RimCase_setFormationNames : public caf::PdmVoidObjectMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimCase_setFormationNames( caf::PdmObjectHandle* self ); + + void setFormationNames( RimFormationNames* formationNames ); + + std::expected execute() override; + +private: + caf::PdmPtrField m_formationNames; +}; diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp index 6627f4fd1d..15bb66f116 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp @@ -32,6 +32,7 @@ #include "ExportCommands/RicSnapshotAllPlotsToFileFeature.h" #include "ExportCommands/RicSnapshotAllViewsToFileFeature.h" +#include "RicImportFormationNamesFeature.h" #include "RicImportGeneralDataFeature.h" #include "RicImportSummaryCasesFeature.h" #include "ViewLink/RicLinkVisibleViewsFeature.h" @@ -39,6 +40,7 @@ #include "RifReaderSettings.h" +#include "Formations/RimFormationNames.h" #include "Rim3dView.h" #include "RimCase.h" #include "RimCornerPointCase.h" @@ -740,3 +742,70 @@ QString RimProject_createGridCaseGroup::classKeywordReturnedType() const { return RimIdenticalGridCaseGroup::classKeywordStatic(); } + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_importFormationNames, "importFormationNames" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimProject_importFormationNames::RimProject_importFormationNames( caf::PdmObjectHandle* self ) + : caf::PdmObjectCreationMethod( self ) +{ + CAF_PDM_InitObject( "Import Formation Names", "", "", "Import formation names from files and return the formation names object" ); + + CAF_PDM_InitScriptableField( &m_formationFiles, + "FormationFiles", + std::vector(), + "Formation Files", + "", + "", + "Formation files to import (.lyr, .fmu, ...)" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimProject_importFormationNames::setFormationFiles( const std::vector& formationFiles ) +{ + m_formationFiles = formationFiles; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimProject_importFormationNames::execute() +{ + auto* project = self(); + if ( !project ) return std::unexpected( "No project is available." ); + + if ( m_formationFiles().empty() ) return std::unexpected( "No formation files provided" ); + + QStringList formationFileList; + QStringList missingFiles; + for ( const QString& formationFile : m_formationFiles() ) + { + if ( QFileInfo::exists( formationFile ) ) + { + formationFileList.push_back( formationFile ); + } + else + { + missingFiles.push_back( formationFile ); + } + } + + if ( !missingFiles.empty() ) return std::unexpected( QString( "%1 does not exist" ).arg( missingFiles.join( ", " ) ) ); + + RimFormationNames* formationNames = RicImportFormationNamesFeature::importFormationFiles( formationFileList ); + if ( !formationNames ) return std::unexpected( "Could not import formation names" ); + + return formationNames; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimProject_importFormationNames::classKeywordReturnedType() const +{ + return RimFormationNames::classKeywordStatic(); +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcProject.h b/ApplicationLibCode/ProjectDataModelCommands/RimcProject.h index 13fc6724fc..94a0a02ece 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcProject.h +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcProject.h @@ -32,6 +32,7 @@ class Rim3dView; class RimIdenticalGridCaseGroup; +class RimFormationNames; //================================================================================================== /// @@ -247,3 +248,23 @@ public: private: caf::PdmField> m_casePaths; }; + +//================================================================================================== +/// Import formation names from one or more files (.lyr, .fmu, ...). Returns the created formation names +/// object, which can be assigned to cases with Case.set_formation_names(). +//================================================================================================== +class RimProject_importFormationNames : public caf::PdmObjectCreationMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimProject_importFormationNames( caf::PdmObjectHandle* self ); + + void setFormationFiles( const std::vector& formationFiles ); + + std::expected execute() override; + QString classKeywordReturnedType() const override; + +private: + caf::PdmField> m_formationFiles; +}; diff --git a/GrpcInterface/Python/rips/case.py b/GrpcInterface/Python/rips/case.py index 0649876ba0..e1ca492554 100644 --- a/GrpcInterface/Python/rips/case.py +++ b/GrpcInterface/Python/rips/case.py @@ -932,20 +932,23 @@ def import_formation_names( ) -> None: """Import formation names into project and apply it to the current case + Deprecated: use Project.import_formation_names(apply_to_all_cases=False) followed by + Case.set_formation_names() instead. + Arguments: formation_files(list): list of files to import """ - if formation_files is None: - formation_files = [] - elif isinstance(formation_files, str): - formation_files = [formation_files] - - self._execute_command( - importFormationNames=Cmd.ImportFormationNamesRequest( - formationFiles=formation_files, applyToCaseId=self.id - ) + warnings.warn( + "Case.import_formation_names() is deprecated, use Project.import_formation_names() and Case.set_formation_names() instead", + DeprecationWarning, + stacklevel=3, ) + project = self.ancestor(rips.project.Project) + formation_names = project.import_formation_names( + formation_files=formation_files, apply_to_all_cases=False + ) + self.set_formation_names(formation_names=formation_names) @add_method(Case) diff --git a/GrpcInterface/Python/rips/project.py b/GrpcInterface/Python/rips/project.py index b76b449cca..d43d031ae9 100644 --- a/GrpcInterface/Python/rips/project.py +++ b/GrpcInterface/Python/rips/project.py @@ -23,6 +23,7 @@ import KeyValueStore_pb2 from .exception import RipsError from .resinsight_classes import ( ColorLegendCollection, + FormationNames, FractureTemplate, PlotWindow, Project, @@ -458,23 +459,29 @@ def import_well_log_files(self, well_log_files=None, well_log_folder=""): @add_method(Project) -def import_formation_names(self, formation_files=None): - """Import formation names into project +def import_formation_names(self, formation_files=None, apply_to_all_cases=True): + """Import formation names into project and apply it to all grid cases in the project Arguments: formation_files(list): list of files to import + apply_to_all_cases(bool): assign the imported formation names to all grid cases. + Use Case.set_formation_names() for finer control. + Returns: + :class:`rips.generated.generated_classes.FormationNames` """ if formation_files is None: formation_files = [] elif isinstance(formation_files, str): formation_files = [formation_files] - self._execute_command( - importFormationNames=Commands_pb2.ImportFormationNamesRequest( - formationFiles=formation_files, applyToCaseId=-1 - ) + formation_names = self._call_pdm_method_return_value( + "importFormationNames", FormationNames, formation_files=formation_files ) + if apply_to_all_cases: + for case in self.descendants(Case): + case.set_formation_names(formation_names=formation_names) + return formation_names @add_method(Project) diff --git a/GrpcInterface/Python/rips/tests/test_project.py b/GrpcInterface/Python/rips/tests/test_project.py index 90653c8abc..63b2ce9c97 100644 --- a/GrpcInterface/Python/rips/tests/test_project.py +++ b/GrpcInterface/Python/rips/tests/test_project.py @@ -311,6 +311,33 @@ def test_import_formation_names(rips_instance, initialize_test): assert "Active Formation Names" in available +def test_import_and_set_formation_names_object_methods(rips_instance, initialize_test): + case_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(case_path) + + formation_names = rips_instance.project.import_formation_names( + formation_files=[dataroot.PATH + "/20Layers.lyr"], apply_to_all_cases=False + ) + assert isinstance(formation_names, rips.FormationNames) + assert formation_names.formation_names_file_name.endswith("20Layers.lyr") + # No formation names assigned to the case yet + with pytest.raises(rips.RipsError): + case.available_properties(rips.PropertyType.FORMATION_NAMES) + + case.set_formation_names(formation_names=formation_names) + assert "Active Formation Names" in case.available_properties( + rips.PropertyType.FORMATION_NAMES + ) + + with pytest.raises(rips.RipsError): + rips_instance.project.import_formation_names(formation_files=["/no/such.lyr"]) + with pytest.raises(rips.RipsError): + case.set_formation_names() + + with pytest.warns(DeprecationWarning): + case.import_formation_names(formation_files=dataroot.PATH + "/20Layers.lyr") + + _MINIMAL_LAS = """~Version Information VERS. 2.0 : CWLS log ASCII Standard - VERSION 2.0 WRAP. NO : One line per depth step