#13313 Python: Add support for setting geometry on an existing case

This commit is contained in:
Kristian Bendiksen
2026-01-05 12:45:32 +01:00
parent 84dd61d4b5
commit 633edd8bba
8 changed files with 379 additions and 1 deletions
@@ -29,6 +29,7 @@
#include "RigCaseCellResultsData.h"
#include "RigEclipseCaseData.h"
#include "RigMainGrid.h"
#include "RimReloadCaseTools.h"
#include "RimEclipseInputProperty.h"
#include "RimReservoirCellResultsStorage.h"
@@ -108,6 +109,40 @@ std::expected<RimCornerPointCase*, QString> RimCornerPointCase::createFromCoordi
return cornerPointCase;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<void, QString> RimCornerPointCase::replaceGridFromCoordinatesArray( RimCornerPointCase& cornerPointCase,
const int nx,
const int ny,
const int nz,
const std::vector<float>& coord,
const std::vector<float>& zcorn,
const std::vector<float>& actnum )
{
RigActiveCellInfo* activeCellInfo = cornerPointCase.eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
CVF_ASSERT( activeCellInfo );
activeCellInfo->clear();
RigActiveCellInfo* fractureActiveCellInfo =
cornerPointCase.eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::FRACTURE_MODEL );
CVF_ASSERT( fractureActiveCellInfo );
fractureActiveCellInfo->clear();
RimReloadCaseTools::clearAllGridData( cornerPointCase.eclipseCaseData() );
// Clear the existing grid geometry before building the new grid
RigMainGrid* mainGrid = cornerPointCase.eclipseCaseData()->mainGrid();
CVF_ASSERT( mainGrid );
mainGrid->reservoirCells().clear();
mainGrid->nodes().clear();
buildGrid( *cornerPointCase.eclipseCaseData(), nx, ny, nz, coord, zcorn, actnum );
cornerPointCase.computeCachedData();
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -50,6 +50,14 @@ public:
const std::vector<float>& zcorn,
const std::vector<float>& actnum );
static std::expected<void, QString> replaceGridFromCoordinatesArray( RimCornerPointCase& cornerPointCase,
const int nx,
const int ny,
const int nz,
const std::vector<float>& coord,
const std::vector<float>& zcorn,
const std::vector<float>& actnum );
protected:
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
@@ -40,10 +40,10 @@ public:
static RimEclipseCase* gridModelFromSummaryCase( const RimSummaryCase* summaryCase );
static RimSummaryCase* findSummaryCaseFromEclipseResultCase( const RimEclipseResultCase* eclResCase );
static bool openOrImportGridModelFromSummaryCase( const RimSummaryCase* summaryCase );
static void clearAllGridData( RigEclipseCaseData* eclipseCaseData );
private:
static void reloadEclipseData( RimEclipseCase* eclipseCase, bool reloadSummaryData );
static void clearAllGridData( RigEclipseCaseData* eclipseCaseData );
static void updateAllPlots();
static bool findGridModelAndActivateFirstView( const RimSummaryCase* summaryCase );
@@ -24,6 +24,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimcFractureTemplate.h
${CMAKE_CURRENT_LIST_DIR}/RimcThermalFractureTemplate.h
${CMAKE_CURRENT_LIST_DIR}/RimcIntersection.h
${CMAKE_CURRENT_LIST_DIR}/RimcCornerPointCase.h
${CMAKE_CURRENT_LIST_DIR}/RimcEclipseCase.h
${CMAKE_CURRENT_LIST_DIR}/RimcEclipseStatisticsCase.h
${CMAKE_CURRENT_LIST_DIR}/RimcGridView.h
@@ -63,6 +64,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimcFractureTemplate.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcThermalFractureTemplate.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcIntersection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcCornerPointCase.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcEclipseCase.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcEclipseStatisticsCase.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcGridView.cpp
@@ -0,0 +1,117 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025- 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 "RimcCornerPointCase.h"
#include "RiaApplication.h"
#include "RiaKeyValueStoreUtil.h"
#include "RiaViewRedrawScheduler.h"
#include "Rim3dView.h"
#include "RimCornerPointCase.h"
#include "RimEclipseView.h"
#include "cafPdmFieldScriptingCapability.h"
#include <expected>
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimCornerPointCase, RimcCornerPointCase_replaceCornerPointGridInternal, "replace_corner_point_grid_internal" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcCornerPointCase_replaceCornerPointGridInternal::RimcCornerPointCase_replaceCornerPointGridInternal( caf::PdmObjectHandle* self )
: caf::PdmVoidObjectMethod( self )
{
CAF_PDM_InitObject( "Replace Corner Point Grid", "", "", "Replace Corner Point Grid" );
CAF_PDM_InitScriptableFieldNoDefault( &m_nx, "Nx", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_ny, "Ny", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_nz, "Nz", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_coordKey, "CoordKey", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_zcornKey, "ZcornKey", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_actnumKey, "ActnumKey", "" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcCornerPointCase_replaceCornerPointGridInternal::execute()
{
auto cornerPointCase = self<RimCornerPointCase>();
if ( !cornerPointCase )
{
return std::unexpected( "Unable to get corner point case." );
}
int nx = m_nx();
int ny = m_ny();
int nz = m_nz();
if ( nx <= 0 || ny <= 0 || nz <= 0 )
{
return std::unexpected( "Invalid grid dimensions. nx, ny and nz must be positive." );
}
auto keyValueStore = RiaApplication::instance()->keyValueStore();
std::vector<float> coord = RiaKeyValueStoreUtil::convertToFloatVector( keyValueStore->get( m_coordKey().toStdString() ) );
std::vector<float> zcorn = RiaKeyValueStoreUtil::convertToFloatVector( keyValueStore->get( m_zcornKey().toStdString() ) );
std::vector<float> actnum = RiaKeyValueStoreUtil::convertToFloatVector( keyValueStore->get( m_actnumKey().toStdString() ) );
if ( coord.empty() || zcorn.empty() || actnum.empty() )
{
return std::unexpected( "Found unexpected empty coord, zcorn or actnum array." );
}
// Block view updates while the grid is being replaced.
RiaViewRedrawScheduler::instance()->clearViewsScheduledForUpdate();
RiaViewRedrawScheduler::instance()->blockUpdate( true );
// Create a new corner point grid from the provided geometry
auto result = RimCornerPointCase::replaceGridFromCoordinatesArray( *cornerPointCase, nx, ny, nz, coord, zcorn, actnum );
if ( !result.has_value() )
{
RiaViewRedrawScheduler::instance()->blockUpdate( false );
return std::unexpected( "Failed to replace grid" );
}
// Update all 3D views to reflect the new grid
// We must completely reinitialize the views because the grid size has changed
std::vector<Rim3dView*> views = cornerPointCase->views();
for ( Rim3dView* view : views )
{
if ( RimEclipseView* eclView = dynamic_cast<RimEclipseView*>( view ) )
{
// Force complete regeneration by calling loadDataAndUpdate
// This will reinitialize all geometry managers with the new grid size
eclView->loadDataAndUpdate();
}
}
RiaViewRedrawScheduler::instance()->blockUpdate( false );
// Update connected editors
cornerPointCase->updateConnectedEditors();
// Clean up key-value store
keyValueStore->remove( m_coordKey().toStdString() );
keyValueStore->remove( m_zcornKey().toStdString() );
keyValueStore->remove( m_actnumKey().toStdString() );
return nullptr;
}
@@ -0,0 +1,47 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025- 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 "cafPdmObjectMethod.h"
#include <QString>
#include <expected>
//==================================================================================================
///
//==================================================================================================
class RimcCornerPointCase_replaceCornerPointGridInternal : public caf::PdmVoidObjectMethod
{
CAF_PDM_HEADER_INIT;
public:
RimcCornerPointCase_replaceCornerPointGridInternal( caf::PdmObjectHandle* self );
std::expected<caf::PdmObjectHandle*, QString> execute() override;
private:
caf::PdmField<int> m_nx;
caf::PdmField<int> m_ny;
caf::PdmField<int> m_nz;
caf::PdmField<QString> m_coordKey;
caf::PdmField<QString> m_zcornKey;
caf::PdmField<QString> m_actnumKey;
};
+53
View File
@@ -1435,3 +1435,56 @@ def export_corner_point_grid(
return (zcorn, coord, actnum, nx, ny, nz)
else:
return ([], [], [], 0, 0, 0)
@add_method(Case)
def replace_corner_point_grid(
self,
nx: int,
ny: int,
nz: int,
coord: List[float],
zcorn: List[float],
actnum: List[int],
):
"""Replace the current case grid with new corner point grid geometry
This method modifies the geometry of an existing case by replacing it with
new grid parameters. All existing properties will be cleared.
Arguments:
nx(int): Number of cells in x direction
ny(int): Number of cells in y direction
nz(int): Number of cells in z direction
coord(list[float]): Coordinate lines as COORD keyword in Eclipse.
Each coordinate line is defined by two points (top and bottom).
Size: (nx+1) * (ny+1) * 2 * 3. Points are ordered as in Eclipse.
zcorn(list[float]): Corner depths as defined by the Eclipse keyword ZCORN.
Size: nx * ny * nz * 8
actnum(list[int]): Active cell info: cells with values > 0 are active.
Size: nx * ny * nz
"""
# Generate unique keys for three arrays
coord_key = "{}_{}".format(uuid.uuid4(), "coord")
zcorn_key = "{}_{}".format(uuid.uuid4(), "zcorn")
actnum_key = "{}_{}".format(uuid.uuid4(), "actnum")
# Get project to access key-value store
project = self.ancestor(rips.project.Project)
if not project:
raise RuntimeError("Unable to get project from case")
# Store arrays in key-value store
project.set_key_values(coord_key, coord)
project.set_key_values(zcorn_key, zcorn)
project.set_key_values(actnum_key, actnum)
# Call internal C++ method to replace the grid
self.replace_corner_point_grid_internal(
nx=nx,
ny=ny,
nz=nz,
coord_key=coord_key,
zcorn_key=zcorn_key,
actnum_key=actnum_key,
)
@@ -172,3 +172,119 @@ def test_export_corner_point_grid_roundtrip(rips_instance, initialize_test):
max_diff = max(diff_x, diff_y, diff_z)
assert max_diff < 1e-3
def test_replace_corner_point_grid_basic(rips_instance, initialize_test):
"""Test the replace_corner_point_grid method by creating a grid and replacing it with a larger one"""
# Create initial grid with 2x2x2 cells
nx_init, ny_init, nz_init = 2, 2, 2
# Generate simple coordinate data for initial grid
coord_init = []
for j in range(ny_init + 1):
for i in range(nx_init + 1):
x = i * 100.0
y = j * 100.0
# Top point
coord_init.extend([x, y, 1000.0])
# Bottom point
coord_init.extend([x, y, 1100.0])
# Generate simple ZCORN data for initial grid
zcorn_init = []
for k in range(nz_init):
for j in range(ny_init):
for i in range(nx_init):
base_depth = 1000.0 + k * 100.0
# 8 corner depths for each cell
zcorn_init.extend(
[
base_depth,
base_depth,
base_depth,
base_depth,
base_depth + 100.0,
base_depth + 100.0,
base_depth + 100.0,
base_depth + 100.0,
]
)
# Generate ACTNUM for initial grid (all cells active)
actnum_init = [1] * (nx_init * ny_init * nz_init)
# Create the initial grid case
case = rips_instance.project.create_corner_point_grid(
"InitialGrid", nx_init, ny_init, nz_init, coord_init, zcorn_init, actnum_init
)
# Verify initial grid dimensions
initial_cell_count = case.cell_count()
assert initial_cell_count.reservoir_cell_count == nx_init * ny_init * nz_init
# Now create replacement grid with more cells (3x3x3)
nx_new, ny_new, nz_new = 3, 3, 3
# Generate coordinate data for new grid
coord_new = []
for j in range(ny_new + 1):
for i in range(nx_new + 1):
x = i * 100.0
y = j * 100.0
# Top point
coord_new.extend([x, y, 1000.0])
# Bottom point
coord_new.extend([x, y, 1200.0])
# Generate ZCORN data for new grid
zcorn_new = []
for k in range(nz_new):
for j in range(ny_new):
for i in range(nx_new):
base_depth = 1000.0 + k * 100.0
# 8 corner depths for each cell
zcorn_new.extend(
[
base_depth,
base_depth,
base_depth,
base_depth,
base_depth + 100.0,
base_depth + 100.0,
base_depth + 100.0,
base_depth + 100.0,
]
)
# Generate ACTNUM for new grid (all cells active)
actnum_new = [1] * (nx_new * ny_new * nz_new)
# Replace the grid geometry
case.replace_corner_point_grid(
nx_new, ny_new, nz_new, coord_new, zcorn_new, actnum_new
)
# Verify that the grid now has the new dimensions
new_cell_count = case.cell_count()
assert new_cell_count.reservoir_cell_count == nx_new * ny_new * nz_new
assert new_cell_count.reservoir_cell_count == 27 # 3x3x3
# Verify the grid has more cells than before
assert new_cell_count.reservoir_cell_count > initial_cell_count.reservoir_cell_count
# Export the replaced grid and verify dimensions
exported_zcorn, exported_coord, exported_actnum, export_nx, export_ny, export_nz = (
case.export_corner_point_grid()
)
# Verify dimensions match
assert export_nx == nx_new
assert export_ny == ny_new
assert export_nz == nz_new
# Verify array sizes
print(exported_actnum)
assert len(exported_actnum) == nx_new * ny_new * nz_new
# All cells should be active
assert sum(1 for x in exported_actnum if x > 0) == len(actnum_new)