mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
#14772 Python: Add Case.replace_grid and forward replaceCase to it
First step in modernizing the command file interface: move the implementation of legacy Ricf commands into object-oriented Rimc methods, and let the Ricf command forward to them. - Add RimCase_replaceGrid (PdmVoidObjectMethod on RimCase) with NewGridFile and optional ProjectFile parameters. Works for both Eclipse result cases and GeoMech cases. Generated in Python as Case.replace_grid(). - Add RicfCommandForwarding helpers for resolving ids to objects and converting std::expected results to PdmScriptResponse. - RicfSingleCaseReplace::execute() now resolves the case and the executor's last project path, then forwards to RimCase_replaceGrid. Command file syntax and Commands.proto are unchanged. - Case.replace() in Python is kept as a thin wrapper over replace_grid() and emits a DeprecationWarning.
This commit is contained in:
1 parent
3437cf9b94
commit
3eb63c0b8e
9 files changed
+355
-32
No files matched your search
@@ -36,6 +36,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RicfImportWellLogFiles.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RicfImportFormationNames.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RicfExportWellLogPlotData.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RicfCommandForwarding.cpp
|
||||
)
|
||||
|
||||
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
|
||||
@@ -0,0 +1,90 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "RicfCommandForwarding.h"
|
||||
|
||||
#include "RiaLogging.h"
|
||||
|
||||
#include "Rim3dView.h"
|
||||
#include "RimCase.h"
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimProject.h"
|
||||
|
||||
#include "cafPdmObjectHandle.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<RimCase*, QString> RicfForwarding::findCase( int caseId )
|
||||
{
|
||||
RimProject* project = RimProject::current();
|
||||
if ( !project ) return std::unexpected( "No project is available." );
|
||||
|
||||
std::vector<RimCase*> allCases = project->allGridCases();
|
||||
if ( caseId < 0 )
|
||||
{
|
||||
// Matches RiaProjectModifier::firstCaseId(): the first occurrence is the first Eclipse result case
|
||||
for ( RimCase* rimCase : allCases )
|
||||
{
|
||||
if ( dynamic_cast<RimEclipseResultCase*>( rimCase ) ) return rimCase;
|
||||
}
|
||||
return std::unexpected( "No Eclipse result cases found in project." );
|
||||
}
|
||||
|
||||
for ( RimCase* rimCase : allCases )
|
||||
{
|
||||
if ( rimCase && rimCase->caseId() == caseId ) return rimCase;
|
||||
}
|
||||
|
||||
return std::unexpected( QString( "Could not find case with ID %1" ).arg( caseId ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<Rim3dView*, QString> RicfForwarding::findView( RimCase* rimCase, int viewId )
|
||||
{
|
||||
if ( !rimCase ) return std::unexpected( "No case given when looking up view." );
|
||||
|
||||
for ( Rim3dView* view : rimCase->views() )
|
||||
{
|
||||
if ( view && view->id() == viewId ) return view;
|
||||
}
|
||||
|
||||
return std::unexpected( QString( "Could not find view with ID %1 in case %2" ).arg( viewId ).arg( rimCase->caseId() ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
caf::PdmScriptResponse RicfForwarding::toScriptResponse( const std::expected<caf::PdmObjectHandle*, QString>& result, const QString& commandName )
|
||||
{
|
||||
if ( !result.has_value() ) return errorResponse( result.error(), commandName );
|
||||
|
||||
return caf::PdmScriptResponse();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
caf::PdmScriptResponse RicfForwarding::errorResponse( const QString& message, const QString& commandName )
|
||||
{
|
||||
QString fullMessage = QString( "%1: %2" ).arg( commandName ).arg( message );
|
||||
RiaLogging::error( fullMessage.toStdString() );
|
||||
return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, fullMessage );
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cafPdmScriptResponse.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <expected>
|
||||
|
||||
class RimCase;
|
||||
class Rim3dView;
|
||||
|
||||
namespace caf
|
||||
{
|
||||
class PdmObjectHandle;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
/// Helpers used by the legacy command file interface (Ricf*) to forward to the object-oriented
|
||||
/// command methods (Rimc*). The legacy interface identifies objects by integer ids, while the
|
||||
/// object methods operate directly on objects. These helpers translate between the two.
|
||||
//==================================================================================================
|
||||
namespace RicfForwarding
|
||||
{
|
||||
/// Find a grid case by id. A negative id resolves to the first Eclipse result case in the project.
|
||||
std::expected<RimCase*, QString> findCase( int caseId );
|
||||
|
||||
/// Find a view by id in the given case. A negative id is not accepted.
|
||||
std::expected<Rim3dView*, QString> findView( RimCase* rimCase, int viewId );
|
||||
|
||||
/// Convert the result of a Rimc method execution to a script response, logging errors with the command name.
|
||||
caf::PdmScriptResponse toScriptResponse( const std::expected<caf::PdmObjectHandle*, QString>& result, const QString& commandName );
|
||||
|
||||
/// Create an error response and log the message with the command name.
|
||||
caf::PdmScriptResponse errorResponse( const QString& message, const QString& commandName );
|
||||
|
||||
} // namespace RicfForwarding
|
||||
@@ -23,8 +23,11 @@
|
||||
#include "RiaProjectModifier.h"
|
||||
|
||||
#include "RicfCommandFileExecutor.h"
|
||||
#include "RicfCommandForwarding.h"
|
||||
|
||||
#include "RimCase.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimcCase.h"
|
||||
|
||||
#include "cafPdmFieldScriptingCapability.h"
|
||||
|
||||
@@ -63,46 +66,29 @@ QString RicfSingleCaseReplace::filePath() const
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
caf::PdmScriptResponse RicfSingleCaseReplace::execute()
|
||||
{
|
||||
const QString commandName = classKeyword();
|
||||
|
||||
// The legacy command file interface allows replacing a case in a project that has been opened by
|
||||
// 'openProject' but is not saved. Resolve the project path here and pass it on explicitly.
|
||||
QString projectPath = RimProject::current()->fileName();
|
||||
if ( projectPath.isEmpty() )
|
||||
{
|
||||
QString lastProjectPath = RicfCommandFileExecutor::instance()->getLastProjectPath();
|
||||
if ( lastProjectPath.isNull() )
|
||||
{
|
||||
QString errMsg( "replaceCase: The project must be saved as a file before calling 'replaceCase'." );
|
||||
RiaLogging::error( errMsg.toStdString() );
|
||||
return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, errMsg );
|
||||
return RicfForwarding::errorResponse( "The project must be saved as a file before calling 'replaceCase'.", commandName );
|
||||
}
|
||||
projectPath = lastProjectPath;
|
||||
}
|
||||
|
||||
cvf::ref<RiaProjectModifier> projectModifier = cvf::make_ref<RiaProjectModifier>();
|
||||
auto rimCase = RicfForwarding::findCase( m_caseId() );
|
||||
if ( !rimCase ) return RicfForwarding::errorResponse( rimCase.error(), commandName );
|
||||
|
||||
QString filePath = m_newGridFile();
|
||||
QFileInfo casePathInfo( filePath );
|
||||
if ( !casePathInfo.exists() )
|
||||
{
|
||||
QDir startDir( RiaApplication::instance()->startDir() );
|
||||
filePath = startDir.absoluteFilePath( m_newGridFile() );
|
||||
}
|
||||
RimCase_replaceGrid method( rimCase.value() );
|
||||
method.setNewGridFile( m_newGridFile() );
|
||||
method.setProjectFile( projectPath );
|
||||
|
||||
if ( m_caseId() < 0 )
|
||||
{
|
||||
projectModifier->setReplaceCaseFirstOccurrence( filePath );
|
||||
}
|
||||
else
|
||||
{
|
||||
projectModifier->setReplaceCase( m_caseId(), filePath );
|
||||
}
|
||||
|
||||
if ( !RiaApplication::instance()->loadProject( projectPath, RiaApplication::ProjectLoadAction::PLA_NONE, projectModifier.p() ) )
|
||||
{
|
||||
QString errMsg( "Could not reload project" );
|
||||
RiaLogging::error( errMsg.toStdString() );
|
||||
return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, errMsg );
|
||||
}
|
||||
|
||||
return caf::PdmScriptResponse();
|
||||
return RicfForwarding::toScriptResponse( method.execute(), commandName );
|
||||
}
|
||||
|
||||
CAF_PDM_SOURCE_INIT( RicfMultiCaseReplace, "replaceMultipleCases" );
|
||||
|
||||
@@ -45,6 +45,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimcWellEventPerf.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimcColorLegend.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimcNestedCollectionBase.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimcCase.cpp
|
||||
)
|
||||
|
||||
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
|
||||
@@ -0,0 +1,106 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "RimcCase.h"
|
||||
|
||||
#include "RiaApplication.h"
|
||||
#include "RiaProjectModifier.h"
|
||||
|
||||
#include "RimCase.h"
|
||||
#include "RimProject.h"
|
||||
|
||||
#include "cafPdmFieldScriptingCapability.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimCase, RimCase_replaceGrid, "replaceGrid" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimCase_replaceGrid::RimCase_replaceGrid( caf::PdmObjectHandle* self )
|
||||
: caf::PdmVoidObjectMethod( self )
|
||||
{
|
||||
CAF_PDM_InitObject( "Replace Grid", "", "", "Replace the grid file of the case and reload the project" );
|
||||
|
||||
CAF_PDM_InitScriptableField( &m_newGridFile, "NewGridFile", QString(), "New Grid File", "", "Path to the new grid file (EGRID, GRID, GRDECL or ODB)" );
|
||||
CAF_PDM_InitScriptableField( &m_projectFile,
|
||||
"ProjectFile",
|
||||
QString(),
|
||||
"Project File",
|
||||
"",
|
||||
"Optional project file to reload. Defaults to the current project file." );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimCase_replaceGrid::setNewGridFile( const QString& newGridFile )
|
||||
{
|
||||
m_newGridFile = newGridFile;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimCase_replaceGrid::setProjectFile( const QString& projectFile )
|
||||
{
|
||||
m_projectFile = projectFile;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<caf::PdmObjectHandle*, QString> RimCase_replaceGrid::execute()
|
||||
{
|
||||
auto* rimCase = self<RimCase>();
|
||||
if ( !rimCase ) return std::unexpected( "No case is available." );
|
||||
|
||||
if ( m_newGridFile().isEmpty() ) return std::unexpected( "No new grid file 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 grid of a case." );
|
||||
}
|
||||
|
||||
QString filePath = m_newGridFile();
|
||||
QFileInfo casePathInfo( filePath );
|
||||
if ( !casePathInfo.exists() )
|
||||
{
|
||||
QDir startDir( RiaApplication::instance()->startDir() );
|
||||
filePath = startDir.absoluteFilePath( m_newGridFile() );
|
||||
}
|
||||
|
||||
cvf::ref<RiaProjectModifier> projectModifier = cvf::make_ref<RiaProjectModifier>();
|
||||
projectModifier->setReplaceCase( rimCase->caseId(), filePath );
|
||||
|
||||
if ( !RiaApplication::instance()->loadProject( projectPath, RiaApplication::ProjectLoadAction::PLA_NONE, projectModifier.p() ) )
|
||||
{
|
||||
return std::unexpected( "Could not reload project" );
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmObjectHandle.h"
|
||||
#include "cafPdmObjectMethod.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
//==================================================================================================
|
||||
/// Replace the grid file of a case (Eclipse result case or GeoMech case) and reload the project.
|
||||
///
|
||||
/// The project must be saved to file, as the replacement is performed by reloading the project
|
||||
/// through a RiaProjectModifier. An explicit project file can be given to override the current
|
||||
/// project file name (used by the legacy command file interface).
|
||||
//==================================================================================================
|
||||
class RimCase_replaceGrid : public caf::PdmVoidObjectMethod
|
||||
{
|
||||
CAF_PDM_HEADER_INIT;
|
||||
|
||||
public:
|
||||
RimCase_replaceGrid( caf::PdmObjectHandle* self );
|
||||
|
||||
void setNewGridFile( const QString& newGridFile );
|
||||
void setProjectFile( const QString& projectFile );
|
||||
|
||||
std::expected<caf::PdmObjectHandle*, QString> execute() override;
|
||||
|
||||
private:
|
||||
caf::PdmField<QString> m_newGridFile;
|
||||
caf::PdmField<QString> m_projectFile;
|
||||
};
|
||||
@@ -37,6 +37,7 @@ result
|
||||
|
||||
import grpc
|
||||
import uuid
|
||||
import warnings
|
||||
from typing import Any, Iterable, Iterator, List, Optional, Tuple, Union
|
||||
|
||||
import Case_pb2
|
||||
@@ -170,13 +171,18 @@ def grids(self) -> List[Grid]:
|
||||
def replace(self, new_grid_file: str) -> None:
|
||||
"""Replace the current case grid with a new grid loaded from file
|
||||
|
||||
Deprecated: use replace_grid(new_grid_file) instead.
|
||||
|
||||
Arguments:
|
||||
new_egrid_file (str): Path to EGRID file
|
||||
new_grid_file (str): Path to EGRID file
|
||||
"""
|
||||
project = self.ancestor(rips.project.Project)
|
||||
self._execute_command(
|
||||
replaceCase=Cmd.ReplaceCaseRequest(newGridFile=new_grid_file, caseId=self.id)
|
||||
warnings.warn(
|
||||
"Case.replace() is deprecated, use Case.replace_grid() instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
project = self.ancestor(rips.project.Project)
|
||||
self.replace_grid(new_grid_file=new_grid_file)
|
||||
new_case = project.case(self.id)
|
||||
self.copy_from(new_case)
|
||||
|
||||
|
||||
@@ -177,6 +177,36 @@ def test_replaceCase(rips_instance, initialize_test):
|
||||
assert case.id == 0
|
||||
|
||||
|
||||
def test_replace_grid(rips_instance, initialize_test):
|
||||
project = rips_instance.project.open(
|
||||
dataroot.PATH + "/TEST10K_FLT_LGR_NNC/10KWithWellLog.rsp"
|
||||
)
|
||||
case_path = dataroot.PATH + "/Case_with_10_timesteps/Real0/BRUGGE_0000.EGRID"
|
||||
case = project.case(case_id=0)
|
||||
assert case is not None
|
||||
assert case.name == "TEST10K_FLT_LGR_NNC"
|
||||
|
||||
case.replace_grid(new_grid_file=case_path)
|
||||
|
||||
# The project is reloaded, so retrieve the case object again
|
||||
cases = rips_instance.project.cases()
|
||||
assert len(cases) == 1
|
||||
case = rips_instance.project.case(case_id=0)
|
||||
assert case.name == "BRUGGE_0000"
|
||||
assert case.id == 0
|
||||
|
||||
|
||||
def test_replace_deprecated(rips_instance, initialize_test):
|
||||
project = rips_instance.project.open(
|
||||
dataroot.PATH + "/TEST10K_FLT_LGR_NNC/10KWithWellLog.rsp"
|
||||
)
|
||||
case_path = dataroot.PATH + "/Case_with_10_timesteps/Real0/BRUGGE_0000.EGRID"
|
||||
case = project.case(case_id=0)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
case.replace(new_grid_file=case_path)
|
||||
assert case.name == "BRUGGE_0000"
|
||||
|
||||
|
||||
def test_loadNonExistingCase(rips_instance, initialize_test):
|
||||
case_path = "Nonsense/Nonsense/Nonsense"
|
||||
with pytest.raises(rips.RipsError):
|
||||
|
||||
Reference in new issue
Block a user