From 8e6a96da001c152c082bd40f18958a077fcd8525 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Tue, 30 Sep 2025 20:56:38 +0200 Subject: [PATCH] #12950 Add api for export cell visibility for a given view. --- .../CMakeLists_files.cmake | 2 + .../ProjectDataModelCommands/RimcGridView.cpp | 82 +++++++++++++++++++ .../ProjectDataModelCommands/RimcGridView.h | 42 ++++++++++ GrpcInterface/Python/rips/tests/test_view.py | 64 +++++++++++++++ GrpcInterface/Python/rips/view.py | 34 ++++++++ 5 files changed, 224 insertions(+) create mode 100644 ApplicationLibCode/ProjectDataModelCommands/RimcGridView.cpp create mode 100644 ApplicationLibCode/ProjectDataModelCommands/RimcGridView.h create mode 100644 GrpcInterface/Python/rips/tests/test_view.py diff --git a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake index 0e1c8389c5..20b230684d 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake @@ -26,6 +26,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcIntersection.h ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseCase.h ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseStatisticsCase.h + ${CMAKE_CURRENT_LIST_DIR}/RimcGridView.h ${CMAKE_CURRENT_LIST_DIR}/RimcIdenticalGridCaseGroup.h ${CMAKE_CURRENT_LIST_DIR}/RimcPressureTable.h ${CMAKE_CURRENT_LIST_DIR}/RimcFishbonesCollection.h @@ -64,6 +65,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcIntersection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseCase.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcEclipseStatisticsCase.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimcGridView.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcIdenticalGridCaseGroup.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcPressureTable.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcFishbonesCollection.cpp diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.cpp new file mode 100644 index 0000000000..e42ddad5e6 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.cpp @@ -0,0 +1,82 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimcGridView.h" + +#include "RiaApplication.h" +#include "RiaKeyValueStoreUtil.h" + +#include "RimEclipseView.h" +#include "RimGridView.h" + +#include "cafPdmFieldScriptingCapability.h" + +#include "cvfArray.h" + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimEclipseView, RimcGridView_visibleCellsInternal, "visible_cells_internal" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimcGridView_visibleCellsInternal::RimcGridView_visibleCellsInternal( caf::PdmObjectHandle* self ) + : caf::PdmVoidObjectMethod( self ) +{ + CAF_PDM_InitObject( "Visible Cells Internal", "", "", "Visible Cells Internal" ); + + CAF_PDM_InitScriptableFieldNoDefault( &m_visibilityKey, "VisibilityKey", "" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_timeStep, "TimeStep", "" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected RimcGridView_visibleCellsInternal::execute() +{ + auto gridView = self(); + if ( !gridView ) + { + return std::unexpected( QString( "Grid view is null." ) ); + } + + if ( m_visibilityKey().isEmpty() ) + { + return std::unexpected( QString( "Visibility key is empty." ) ); + } + + // Get the cell visibility array + cvf::ref visibleCells = gridView->currentTotalCellVisibility(); + + if ( visibleCells.isNull() ) + { + return std::unexpected( QString( "Failed to get cell visibility data." ) ); + } + + // Convert cvf::UByteArray to std::vector where 1.0 = visible, 0.0 = invisible + std::vector visibilityValues; + visibilityValues.reserve( visibleCells->size() ); + for ( size_t i = 0; i < visibleCells->size(); ++i ) + { + visibilityValues.push_back( ( *visibleCells )[i] ? 1.0f : 0.0f ); + } + + // Store the visibility data in the key-value store + auto keyValueStore = RiaApplication::instance()->keyValueStore(); + keyValueStore->set( m_visibilityKey().toStdString(), RiaKeyValueStoreUtil::convertToByteVector( visibilityValues ) ); + + return nullptr; +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.h b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.h new file mode 100644 index 0000000000..82c6d4e2e3 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcGridView.h @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafPdmField.h" +#include "cafPdmObjectHandle.h" +#include "cafPdmObjectMethod.h" + +#include + +//================================================================================================== +/// +//================================================================================================== +class RimcGridView_visibleCellsInternal : public caf::PdmVoidObjectMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimcGridView_visibleCellsInternal( caf::PdmObjectHandle* self ); + + std::expected execute() override; + +private: + caf::PdmField m_visibilityKey; + caf::PdmField m_timeStep; +}; diff --git a/GrpcInterface/Python/rips/tests/test_view.py b/GrpcInterface/Python/rips/tests/test_view.py new file mode 100644 index 0000000000..fe11e6441d --- /dev/null +++ b/GrpcInterface/Python/rips/tests/test_view.py @@ -0,0 +1,64 @@ +import sys +import os + +sys.path.insert(1, os.path.join(sys.path[0], "../../")) + +import dataroot + + +def test_visible_cells(rips_instance, initialize_test): + """Test the visible_cells() method on a view""" + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + # Get the first view or create one + views = case.views() + if views: + view = views[0] + else: + view = case.create_view() + + # Test visible_cells at time step 0 + visibility = view.visible_cells(time_step=0) + + # Verify we got a list + assert isinstance(visibility, list) + + # Verify we have data + assert len(visibility) > 0 + + # Verify all values are either 0 or 1 + for v in visibility: + assert v in [0, 1] + + # Count visible and invisible cells + visible_count = sum(visibility) + invisible_count = len(visibility) - visible_count + + # Total cells should match grid size + assert len(visibility) == visible_count + invisible_count + + # The visibility array should have the correct size (can be all visible, all invisible, or mixed) + # This depends on the view settings + assert visible_count + invisible_count > 0 + + +def test_visible_cells_different_timesteps(rips_instance, initialize_test): + """Test visible_cells() at different time steps""" + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + + view = case.views()[0] if case.views() else case.create_view() + + # Test at time step 0 + visibility_ts0 = view.visible_cells(time_step=0) + assert len(visibility_ts0) > 0 + + # Test at time step 1 (if it exists) + visibility_ts1 = view.visible_cells(time_step=1) + assert len(visibility_ts1) > 0 + + # Both should have the same number of cells + assert len(visibility_ts0) == len(visibility_ts1) diff --git a/GrpcInterface/Python/rips/view.py b/GrpcInterface/Python/rips/view.py index 3b9294007e..f95dcbbf6b 100644 --- a/GrpcInterface/Python/rips/view.py +++ b/GrpcInterface/Python/rips/view.py @@ -4,6 +4,9 @@ ResInsight 3d view module import Commands_pb2 as Cmd +import uuid +import rips.project + import rips.case # Circular import of Case, which already imports View. Use full name. from .pdmobject import add_method from .resinsight_classes import ( @@ -211,6 +214,37 @@ def case(self): return None +@add_method(View) +def visible_cells(self, time_step=0): + """Get the visibility status of all cells in the view + + Arguments: + time_step (int): The time step to get visibility for. Defaults to 0. + + Returns: + List[int]: A list where 1 represents a visible cell and 0 represents an invisible cell + """ + + # Generate temporary key for key-value store + visibility_key = f"{uuid.uuid4()}_visibility" + + # Get the project + project = self.ancestor(rips.project.Project) + try: + # Call internal method to store visibility data + self.visible_cells_internal(visibility_key=visibility_key, time_step=time_step) + + # Retrieve visibility data from key-value store + visibility_values = project.key_values(visibility_key) + + # Convert floats to integers (1 for visible, 0 for invisible) + return [int(v) for v in visibility_values] + + finally: + # Clean up temporary key from key-value store + project.remove_key_values(visibility_key) + + @add_method(ViewWindow) def export_snapshot(self, prefix="", export_folder=""): """Export snapshot for the current view