diff --git a/ApplicationLibCode/CommandFileInterface/RicfCreateWellBoreStabilityPlotFeature.cpp b/ApplicationLibCode/CommandFileInterface/RicfCreateWellBoreStabilityPlotFeature.cpp index 68b0d7f791..7887861a34 100644 --- a/ApplicationLibCode/CommandFileInterface/RicfCreateWellBoreStabilityPlotFeature.cpp +++ b/ApplicationLibCode/CommandFileInterface/RicfCreateWellBoreStabilityPlotFeature.cpp @@ -17,21 +17,17 @@ ///////////////////////////////////////////////////////////////////////////////// #include "RicfCreateWellBoreStabilityPlotFeature.h" -#include "RiaLogging.h" - -#include "WellLogCommands/RicNewWellBoreStabilityPlotFeature.h" +#include "RicfCommandForwarding.h" #include "RimGeoMechCase.h" -#include "RimGeoMechView.h" #include "RimProject.h" #include "RimWbsParameters.h" #include "RimWellBoreStabilityPlot.h" #include "RimWellPath.h" +#include "RimcGeoMechCase.h" #include "cafPdmFieldScriptingCapability.h" -#include - CAF_PDM_SOURCE_INIT( RicfCreateWbsPlotResult, "createWbsPlotResult" ); //-------------------------------------------------------------------------------------------------- @@ -62,48 +58,32 @@ RicfCreateWellBoreStabilityPlotFeature::RicfCreateWellBoreStabilityPlotFeature() //-------------------------------------------------------------------------------------------------- caf::PdmScriptResponse RicfCreateWellBoreStabilityPlotFeature::execute() { - RimProject* project = RimProject::current(); + const QString commandName = classKeyword(); - std::vector geoMechCases = project->descendantsIncludingThisOfType(); + auto rimCase = RicfForwarding::findCase( m_caseId() ); + if ( !rimCase ) return RicfForwarding::errorResponse( rimCase.error(), commandName ); - RimGeoMechCase* chosenCase = nullptr; - for ( RimGeoMechCase* geoMechCase : geoMechCases ) + auto* geoMechCase = dynamic_cast( rimCase.value() ); + if ( !geoMechCase ) { - if ( geoMechCase->caseId() == m_caseId() ) - { - chosenCase = geoMechCase; - break; - } + return RicfForwarding::errorResponse( QString( "Could not find GeoMech case with id %1" ).arg( m_caseId() ), commandName ); } - RimWellPath* chosenWellPath = nullptr; - for ( RimWellPath* wellPath : project->allWellPaths() ) - { - if ( wellPath->name() == m_wellPath() ) - { - chosenWellPath = wellPath; - break; - } - } + RimWellPath* wellPath = RimProject::current()->wellPathByName( m_wellPath() ); + if ( !wellPath ) return RicfForwarding::errorResponse( QString( "Could not find well path '%1'" ).arg( m_wellPath() ), commandName ); - if ( chosenCase && chosenWellPath && m_timeStep() >= 0 ) - { - if ( !chosenWellPath->wellPathGeometry() ) - { - QString error = - QString( "The well path %1 has no geometry. Cannot create a Well Bore Stability Plot" ).arg( chosenWellPath->name() ); - RiaLogging::error( error.toStdString() ); - return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, error ); - } + RimGeoMechCase_createWellBoreStabilityPlot method( geoMechCase ); + method.setWellPath( wellPath ); + method.setTimeStep( m_timeStep() ); + method.setParameters( m_wbsParameters() ); - RimWellBoreStabilityPlot* wbsPlot = - RicNewWellBoreStabilityPlotFeature::createPlot( chosenCase, chosenWellPath, m_timeStep(), m_wbsParameters() ); - caf::PdmScriptResponse response; - response.setResult( new RicfCreateWbsPlotResult( wbsPlot->id() ) ); - return response; - } + auto result = method.execute(); + if ( !result ) return RicfForwarding::errorResponse( result.error(), commandName ); - QString error = QString( "createWellBoreStabilityPlot: Could not find GeoMech case with id %1" ).arg( m_caseId() ); - RiaLogging::error( error.toStdString() ); - return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, error ); + auto* plot = dynamic_cast( result.value() ); + if ( !plot ) return RicfForwarding::errorResponse( "Created object is not a Well Bore Stability plot", commandName ); + + caf::PdmScriptResponse response; + response.setResult( new RicfCreateWbsPlotResult( plot->id() ) ); + return response; } diff --git a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake index 5e92988d2b..bf4ced8a56 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake @@ -47,6 +47,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcNestedCollectionBase.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcCase.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcViewWindow.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimcGeoMechCase.cpp ) list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES}) diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcGeoMechCase.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcGeoMechCase.cpp new file mode 100644 index 0000000000..979836768c --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcGeoMechCase.cpp @@ -0,0 +1,99 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RimcGeoMechCase.h" + +#include "WellLogCommands/RicNewWellBoreStabilityPlotFeature.h" + +#include "RimGeoMechCase.h" +#include "RimWbsParameters.h" +#include "RimWellBoreStabilityPlot.h" +#include "RimWellPath.h" + +#include "cafPdmFieldScriptingCapability.h" + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimGeoMechCase, RimGeoMechCase_createWellBoreStabilityPlot, "createWellBoreStabilityPlot" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGeoMechCase_createWellBoreStabilityPlot::RimGeoMechCase_createWellBoreStabilityPlot( caf::PdmObjectHandle* self ) + : caf::PdmObjectCreationMethod( self ) +{ + CAF_PDM_InitObject( "Create Well Bore Stability Plot", "", "", "Create a Well Bore Stability plot for a well path in this case" ); + + CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "Well Path", "", "", "Well path to create the plot for" ); + CAF_PDM_InitScriptableField( &m_timeStep, "TimeStep", 0, "Time Step", "", "", "Zero-based time step index" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGeoMechCase_createWellBoreStabilityPlot::setWellPath( RimWellPath* wellPath ) +{ + m_wellPath = wellPath; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGeoMechCase_createWellBoreStabilityPlot::setTimeStep( int timeStep ) +{ + m_timeStep = timeStep; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGeoMechCase_createWellBoreStabilityPlot::setParameters( const RimWbsParameters* parameters ) +{ + m_parameters = parameters; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimGeoMechCase_createWellBoreStabilityPlot::execute() +{ + auto* geoMechCase = self(); + if ( !geoMechCase ) return std::unexpected( "No GeoMech case is available." ); + + RimWellPath* wellPath = m_wellPath(); + if ( !wellPath ) return std::unexpected( "No well path specified." ); + + if ( !wellPath->wellPathGeometry() ) + { + return std::unexpected( + QString( "The well path %1 has no geometry. Cannot create a Well Bore Stability Plot" ).arg( wellPath->name() ) ); + } + + if ( m_timeStep() < 0 ) return std::unexpected( "Time step must be non-negative." ); + + RimWellBoreStabilityPlot* plot = RicNewWellBoreStabilityPlotFeature::createPlot( geoMechCase, wellPath, m_timeStep(), m_parameters ); + if ( !plot ) return std::unexpected( "Could not create Well Bore Stability plot." ); + + return plot; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimGeoMechCase_createWellBoreStabilityPlot::classKeywordReturnedType() const +{ + return RimWellBoreStabilityPlot::classKeywordStatic(); +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcGeoMechCase.h b/ApplicationLibCode/ProjectDataModelCommands/RimcGeoMechCase.h new file mode 100644 index 0000000000..54c6f034ac --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcGeoMechCase.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "cafPdmPtrField.h" + +#include + +class RimWellPath; +class RimWbsParameters; + +//================================================================================================== +/// Create a Well Bore Stability plot for a well path in this GeoMech case. +/// +/// The plot is created with default parameters. Adjust them through the returned plot's Parameters +/// child object, or pass a RimWbsParameters object from C++ via setParameters(). +//================================================================================================== +class RimGeoMechCase_createWellBoreStabilityPlot : public caf::PdmObjectCreationMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimGeoMechCase_createWellBoreStabilityPlot( caf::PdmObjectHandle* self ); + + void setWellPath( RimWellPath* wellPath ); + void setTimeStep( int timeStep ); + void setParameters( const RimWbsParameters* parameters ); + + std::expected execute() override; + QString classKeywordReturnedType() const override; + +private: + caf::PdmPtrField m_wellPath; + caf::PdmField m_timeStep; + + const RimWbsParameters* m_parameters = nullptr; +}; diff --git a/GrpcInterface/Python/rips/case.py b/GrpcInterface/Python/rips/case.py index e1ca492554..cfa9340262 100644 --- a/GrpcInterface/Python/rips/case.py +++ b/GrpcInterface/Python/rips/case.py @@ -59,6 +59,7 @@ from .resinsight_classes import ( PropertyType as PropertyType, Reservoir as Reservoir, WellBoreStabilityPlot as WellBoreStabilityPlot, + WellPath as WellPath, WbsParameters as WbsParameters, ) @@ -892,37 +893,55 @@ def set_grid_property( raise IndexError -@add_method(Case) +@add_method(GeoMechCase) def create_well_bore_stability_plot( self, - well_path: str, + well_path: Union[str, WellPath], time_step: int, parameters: Optional[WbsParameters] = None, -) -> Optional[WellBoreStabilityPlot]: +) -> WellBoreStabilityPlot: """Create a new well bore stability plot Arguments: - well_path(str): well path name + well_path(WellPath or str): well path object or well path name time_step(int): time step + parameters(WbsParameters): optional parameters copied into the created plot Returns: :class:`rips.generated.generated_classes.WellBoreStabilityPlot` """ - pb2_parameters = None + if isinstance(well_path, str): + warnings.warn( + "GeoMechCase.create_well_bore_stability_plot(well_path=) is deprecated, pass a WellPath object instead", + DeprecationWarning, + stacklevel=3, + ) + project = self.ancestor(rips.project.Project) + well_path_object = project.well_path_by_name(well_path) + if well_path_object is None: + raise RipsError(f"Could not find well path '{well_path}'") + well_path = well_path_object + + plot = self._call_pdm_method_return_value( + "createWellBoreStabilityPlot", + WellBoreStabilityPlot, + well_path=well_path, + time_step=time_step, + ) + if parameters is not None: assert isinstance(parameters, WbsParameters) - pb2_parameters = parameters.pb2_object() + plot_parameters = plot.parameters() + if plot_parameters is not None: + # Copy the public field values only; private members hold the gRPC connection + for attribute in dir(parameters): + if attribute.startswith("_"): + continue + value = getattr(parameters, attribute) + if not callable(value): + setattr(plot_parameters, attribute, value) + plot_parameters.update() - plot_result = self._execute_command( - createWellBoreStabilityPlot=Cmd.CreateWbsPlotRequest( - caseId=self.id, - wellPath=well_path, - timeStep=time_step, - wbsParameters=pb2_parameters, - ) - ) - project = self.ancestor(rips.project.Project) - plot = project.plot(view_id=plot_result.createWbsPlotResult.viewId) return plot diff --git a/GrpcInterface/Python/rips/tests/test_cases.py b/GrpcInterface/Python/rips/tests/test_cases.py index b558829f91..de49e32f71 100644 --- a/GrpcInterface/Python/rips/tests/test_cases.py +++ b/GrpcInterface/Python/rips/tests/test_cases.py @@ -459,3 +459,13 @@ def test_create_saturation_pressure_plots_requires_data(rips_instance, initializ case = rips_instance.project.load_case(case_path) with pytest.raises(rips.RipsError): case.create_saturation_pressure_plots() + + +def test_create_well_bore_stability_plot_is_geomech_only( + rips_instance, initialize_test +): + # No GeoMech test data is available; verify the method is exposed on the right class + case_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(case_path) + assert not hasattr(case, "create_well_bore_stability_plot") + assert hasattr(rips.GeoMechCase, "create_well_bore_stability_plot")