From b78ef0d0a1494cc0c33c5709741896e12b74f055 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 27 May 2026 12:59:19 +0200 Subject: [PATCH] #13890 Python: Add scriptable API for selected-fault FAULTDIST Expose the new RimFaultDistanceResult facility to Python by marking the relevant PDM classes and fields scriptable. RimEclipseView's m_faultCollection, RimFaultInViewCollection, RimFaultInView, RimFaultDistanceResult, and RimFaultDistanceResultCollection are now scriptable, so faults can be enumerated from Python via view.fault_collection().faults(). A new Rimc method add_fault_distance_result on RimFaultInViewCollection takes a name and a list of fault objects, creates the result (auto-numbered FAULTDIST when the name is empty), and returns the new RimFaultDistanceResult. An empty fault list falls back to every fault in the view, matching the static-native FAULTDIST behaviour. Add a Python test exercising both a subset call and a parity check that asserts the all-faults result matches the existing static-native FAULTDIST cell-for-cell. --- .../Faults/RimFaultDistanceResult.cpp | 13 +++- .../RimFaultDistanceResultCollection.cpp | 11 ++- .../Faults/RimFaultInView.cpp | 12 ++- .../Faults/RimFaultInViewCollection.cpp | 13 +++- .../ProjectDataModel/RimEclipseView.cpp | 2 +- .../CMakeLists_files.cmake | 1 + .../RimcFaultInViewCollection.cpp | 76 +++++++++++++++++++ .../RimcFaultInViewCollection.h | 46 +++++++++++ .../rips/tests/test_fault_distance_result.py | 55 ++++++++++++++ 9 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.cpp create mode 100644 ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.h create mode 100644 GrpcInterface/Python/rips/tests/test_fault_distance_result.py diff --git a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResult.cpp b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResult.cpp index 1ce953fef2..257e066fc7 100644 --- a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResult.cpp +++ b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResult.cpp @@ -31,6 +31,8 @@ #include "RimFaultInView.h" #include "RimFaultInViewCollection.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" #include "cafPdmUiTreeSelectionEditor.h" CAF_PDM_SOURCE_INIT( RimFaultDistanceResult, "RimFaultDistanceResult" ); @@ -40,11 +42,16 @@ CAF_PDM_SOURCE_INIT( RimFaultDistanceResult, "RimFaultDistanceResult" ); //-------------------------------------------------------------------------------------------------- RimFaultDistanceResult::RimFaultDistanceResult() { - CAF_PDM_InitObject( "Fault Distance Result", ":/draw_style_faults_24x24.png" ); + CAF_PDM_InitScriptableObjectWithNameAndComment( "Fault Distance Result", + ":/draw_style_faults_24x24.png", + "", + "", + "FaultDistanceResult", + "Per-cell distance to a selected subset of faults" ); - CAF_PDM_InitFieldNoDefault( &m_resultName, "ResultName", "Name" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_resultName, "ResultName", "Name" ); - CAF_PDM_InitFieldNoDefault( &m_faults, "SelectedFaults", "Faults" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_faults, "SelectedFaults", "Faults" ); m_faults.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); } diff --git a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResultCollection.cpp b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResultCollection.cpp index 3167afc7e3..c79d0eb28b 100644 --- a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResultCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultDistanceResultCollection.cpp @@ -20,6 +20,8 @@ #include "RimFaultDistanceResult.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" #include "cafPdmUiTreeOrdering.h" #include @@ -31,9 +33,14 @@ CAF_PDM_SOURCE_INIT( RimFaultDistanceResultCollection, "RimFaultDistanceResultCo //-------------------------------------------------------------------------------------------------- RimFaultDistanceResultCollection::RimFaultDistanceResultCollection() { - CAF_PDM_InitObject( "Fault Distance Results", ":/draw_style_faults_24x24.png" ); + CAF_PDM_InitScriptableObjectWithNameAndComment( "Fault Distance Results", + ":/draw_style_faults_24x24.png", + "", + "", + "FaultDistanceResultCollection", + "Collection of named, subset-based FAULTDIST results" ); - CAF_PDM_InitFieldNoDefault( &m_items, "FaultDistanceResults", "" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_items, "FaultDistanceResults", "" ); } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInView.cpp b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInView.cpp index a99d51fe27..86be01b15a 100644 --- a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInView.cpp +++ b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInView.cpp @@ -24,6 +24,9 @@ #include "RimEclipseView.h" #include "RimIntersectionCollection.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" + CAF_PDM_SOURCE_INIT( RimFaultInView, "Fault" ); //-------------------------------------------------------------------------------------------------- @@ -31,9 +34,14 @@ CAF_PDM_SOURCE_INIT( RimFaultInView, "Fault" ); //-------------------------------------------------------------------------------------------------- RimFaultInView::RimFaultInView() { - CAF_PDM_InitObject( "RimFault", ":/draw_style_faults_24x24.png" ); + CAF_PDM_InitScriptableObjectWithNameAndComment( "RimFault", + ":/draw_style_faults_24x24.png", + "", + "", + "FaultInView", + "A fault belonging to a view's fault collection" ); - CAF_PDM_InitFieldNoDefault( &name, "FaultName", "Name" ); + CAF_PDM_InitScriptableFieldNoDefault( &name, "FaultName", "Name" ); name.uiCapability()->setUiHidden( true ); name.uiCapability()->setUiReadOnly( true ); diff --git a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInViewCollection.cpp b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInViewCollection.cpp index 5d9242647b..636e9f0724 100644 --- a/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInViewCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Faults/RimFaultInViewCollection.cpp @@ -36,6 +36,8 @@ #include "cafAppEnum.h" #include "cafPdmFieldCvfColor.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" #include "cafPdmUiCheckBoxEditor.h" #include "cafPdmUiLineEditor.h" #include "cafPdmUiTreeOrdering.h" @@ -59,7 +61,12 @@ CAF_PDM_SOURCE_INIT( RimFaultInViewCollection, "Faults" ); //-------------------------------------------------------------------------------------------------- RimFaultInViewCollection::RimFaultInViewCollection() { - CAF_PDM_InitObject( "Faults", ":/draw_style_faults_24x24.png" ); + CAF_PDM_InitScriptableObjectWithNameAndComment( "Faults", + ":/draw_style_faults_24x24.png", + "", + "", + "FaultInViewCollection", + "Per-view fault collection" ); CAF_PDM_InitField( &m_showFaultCollection, "Active", true, "Active" ); m_showFaultCollection.uiCapability()->setUiHidden( true ); @@ -97,9 +104,9 @@ RimFaultInViewCollection::RimFaultInViewCollection() "Hide NNC Geometry if No NNC Result is Available" ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_hideNNCsWhenNoResultIsAvailable ); - CAF_PDM_InitFieldNoDefault( &m_faults, "Faults", "Faults" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_faults, "Faults", "Faults" ); - CAF_PDM_InitFieldNoDefault( &m_distanceResults, "FaultDistanceResults", "" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_distanceResults, "FaultDistanceResults", "" ); m_distanceResults = new RimFaultDistanceResultCollection; CAF_PDM_InitField( &m_showFaultsOutsideFilters_obsolete, "ShowFaultsOutsideFilters", true, "Show Faults Outside Filters" ); diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp index b9ecdef87c..370bf71663 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp @@ -198,7 +198,7 @@ RimEclipseView::RimEclipseView() CAF_PDM_InitFieldNoDefault( &m_wellCollection, "WellCollection", "Simulation Wells" ); m_wellCollection = new RimSimWellInViewCollection; - CAF_PDM_InitFieldNoDefault( &m_faultCollection, "FaultCollection", "Faults" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_faultCollection, "FaultCollection", "Faults" ); m_faultCollection = new RimFaultInViewCollection; CAF_PDM_InitFieldNoDefault( &m_faultReactivationModelCollection, "FaultReactivationModelCollection", "Fault Reactivation Models" ); diff --git a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake index 5080c3b43c..86060f573c 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake @@ -31,6 +31,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcGridView.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcIdenticalGridCaseGroup.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcPressureTable.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimcFaultInViewCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcFishbonesCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcPolygonCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcDataFilterCollection.cpp diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.cpp new file mode 100644 index 0000000000..f643064a90 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.cpp @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimcFaultInViewCollection.h" + +#include "RimFaultDistanceResult.h" +#include "RimFaultDistanceResultCollection.h" +#include "RimFaultInView.h" +#include "RimFaultInViewCollection.h" + +#include "cafPdmAbstractFieldScriptingCapability.h" +#include "cafPdmFieldScriptingCapability.h" + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimFaultInViewCollection, RimcFaultInViewCollection_addFaultDistanceResult, "add_fault_distance_result" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimcFaultInViewCollection_addFaultDistanceResult::RimcFaultInViewCollection_addFaultDistanceResult( caf::PdmObjectHandle* self ) + : caf::PdmObjectCreationMethod( self ) +{ + CAF_PDM_InitObject( "Add Fault Distance Result", "", "", "Create a FAULTDIST cell result for a chosen subset of faults" ); + + CAF_PDM_InitScriptableFieldNoDefault( &m_resultName, "Name", "Name (default FAULTDIST if empty)" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_faults, "Faults", "Faults to include (empty = all)" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimcFaultInViewCollection_addFaultDistanceResult::execute() +{ + auto* hostCollection = self(); + if ( !hostCollection ) return std::unexpected( QString( "No fault collection" ) ); + + auto* distanceCollection = hostCollection->faultDistanceResults(); + if ( !distanceCollection ) return std::unexpected( QString( "No fault distance results collection" ) ); + + auto* newResult = distanceCollection->addResult(); + if ( !newResult ) return std::unexpected( QString( "Failed to create fault distance result" ) ); + + if ( !m_resultName().isEmpty() ) newResult->setResultName( m_resultName() ); + + std::vector selected = m_faults.ptrReferencedObjectsByType(); + if ( selected.empty() ) selected = hostCollection->faults(); + + newResult->setSelectedFaults( selected ); + + hostCollection->updateConnectedEditors(); + distanceCollection->updateConnectedEditors(); + + return newResult; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimcFaultInViewCollection_addFaultDistanceResult::classKeywordReturnedType() const +{ + return RimFaultDistanceResult::classKeywordStatic(); +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.h b/ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.h new file mode 100644 index 0000000000..5599d70bb9 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcFaultInViewCollection.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafPdmField.h" +#include "cafPdmObjectHandle.h" +#include "cafPdmObjectMethod.h" +#include "cafPdmPtrArrayField.h" + +#include + +class RimFaultInView; + +//================================================================================================== +/// +//================================================================================================== +class RimcFaultInViewCollection_addFaultDistanceResult : public caf::PdmObjectCreationMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimcFaultInViewCollection_addFaultDistanceResult( caf::PdmObjectHandle* self ); + + std::expected execute() override; + QString classKeywordReturnedType() const override; + +private: + caf::PdmField m_resultName; + caf::PdmPtrArrayField m_faults; +}; diff --git a/GrpcInterface/Python/rips/tests/test_fault_distance_result.py b/GrpcInterface/Python/rips/tests/test_fault_distance_result.py new file mode 100644 index 0000000000..9a291bb0fe --- /dev/null +++ b/GrpcInterface/Python/rips/tests/test_fault_distance_result.py @@ -0,0 +1,55 @@ +import math +import os +import sys + +sys.path.insert(1, os.path.join(sys.path[0], "../../")) + +import dataroot +import rips + + +GRID_PATH = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" + + +def _get_view(case): + views = case.views() + if views: + return views[0] + return case.create_view() + + +def test_add_fault_distance_result_subset(rips_instance, initialize_test): + case = rips_instance.project.load_case(GRID_PATH) + view = _get_view(case) + fault_collection = view.fault_collection() + faults = fault_collection.faults() + assert len(faults) > 0 + + result = fault_collection.add_fault_distance_result("PY_FAULTDIST", faults[:1]) + assert result is not None + assert result.result_name == "PY_FAULTDIST" + + +def test_add_fault_distance_result_all_matches_static(rips_instance, initialize_test): + """Passing every fault into add_fault_distance_result must reproduce the + static-native FAULTDIST values cell-for-cell.""" + case = rips_instance.project.load_case(GRID_PATH) + view = _get_view(case) + fault_collection = view.fault_collection() + all_faults = fault_collection.faults() + assert len(all_faults) > 0 + + static_values = case.active_cell_property( + rips.PropertyType.STATIC_NATIVE, "FAULTDIST", 0 + ) + + fault_collection.add_fault_distance_result("PY_FAULTDIST_ALL", all_faults) + + generated_values = case.active_cell_property( + rips.PropertyType.GENERATED, "PY_FAULTDIST_ALL", 0 + ) + + assert len(static_values) == len(generated_values) + assert len(static_values) > 0 + for s, g in zip(static_values, generated_values): + assert math.isclose(s, g, abs_tol=1e-6)