From 9d04f0b122d812dbda1d71a4317aeb11e4d078c4 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Fri, 17 Apr 2026 18:01:33 +0200 Subject: [PATCH] Sector export: Add refinement regions Promote sector-export refinement from inline wizard fields to first-class PDM objects so users can create, edit, and preview refinement regions in the 3D view before opening the export dialog. --- .../Commands/CMakeLists_files.cmake | 2 + .../ExportCommands/RicExportSectorModelUi.cpp | 102 ++++- .../ExportCommands/RicExportSectorModelUi.h | 9 +- .../ExportCommands/RicRefinementSettings.cpp | 336 ++++---------- .../ExportCommands/RicRefinementSettings.h | 24 +- .../RicNewRefinementRegionFeature.cpp | 88 ++++ .../Commands/RicNewRefinementRegionFeature.h | 31 ++ .../ModelVisualization/CMakeLists_files.cmake | 2 + .../RivRefinementRegionPartMgr.cpp | 427 ++++++++++++++++++ .../RivRefinementRegionPartMgr.h | 93 ++++ .../ProjectDataModel/CMakeLists_files.cmake | 4 + .../RimContextCommandBuilder.cpp | 13 + .../ProjectDataModel/RimEclipseView.cpp | 42 +- .../ProjectDataModel/RimEclipseView.h | 10 +- .../ProjectDataModel/RimRefinementRegion.cpp | 327 ++++++++++++++ .../ProjectDataModel/RimRefinementRegion.h | 104 +++++ .../RimRefinementRegionCollection.cpp | 246 ++++++++++ .../RimRefinementRegionCollection.h | 73 +++ .../ReservoirDataModel/RigGridExportAdapter.h | 16 +- 19 files changed, 1637 insertions(+), 312 deletions(-) create mode 100644 ApplicationLibCode/Commands/RicNewRefinementRegionFeature.cpp create mode 100644 ApplicationLibCode/Commands/RicNewRefinementRegionFeature.h create mode 100644 ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.cpp create mode 100644 ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.h create mode 100644 ApplicationLibCode/ProjectDataModel/RimRefinementRegion.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/RimRefinementRegion.h create mode 100644 ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.h diff --git a/ApplicationLibCode/Commands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/CMakeLists_files.cmake index 3c8f3d4c58..5a97472fea 100644 --- a/ApplicationLibCode/Commands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/CMakeLists_files.cmake @@ -53,6 +53,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicResampleDialog.h ${CMAKE_CURRENT_LIST_DIR}/RicCreateTemporaryLgrFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicDeleteTemporaryLgrsFeature.h + ${CMAKE_CURRENT_LIST_DIR}/RicNewRefinementRegionFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicExportContourMapToTextFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicExportContourMapToTextUi.h ${CMAKE_CURRENT_LIST_DIR}/RicExportStimPlanModelToFileFeature.h @@ -161,6 +162,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicResampleDialog.cpp ${CMAKE_CURRENT_LIST_DIR}/RicCreateTemporaryLgrFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicDeleteTemporaryLgrsFeature.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicNewRefinementRegionFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicExportContourMapToTextFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicExportContourMapToTextUi.cpp ${CMAKE_CURRENT_LIST_DIR}/RicExportStimPlanModelToFileFeature.cpp diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp index 740e6d5620..97fb4ad2d8 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.cpp @@ -32,9 +32,13 @@ #include "Jobs/RimKeywordBcprop.h" #include "RimEclipseCase.h" #include "RimEclipseView.h" +#include "RimRefinementRegion.h" +#include "RimRefinementRegionCollection.h" #include "RimTools.h" #include "Tools/RimEclipseViewTools.h" +#include "RigNoRefinement.h" + #include "cafPdmUiCheckBoxEditor.h" #include "cafPdmUiFilePathEditor.h" #include "cafPdmUiGroup.h" @@ -42,6 +46,7 @@ #include "cafPdmUiListEditor.h" #include "cafPdmUiRadioButtonEditor.h" #include "cafPdmUiTableViewEditor.h" +#include "cafPdmUiTreeSelectionEditor.h" #include @@ -95,6 +100,9 @@ RicExportSectorModelUi::RicExportSectorModelUi() CAF_PDM_InitFieldNoDefault( &m_refinementSettings, "RefinementSettings", "" ); m_refinementSettings = new RicRefinementSettings(); + CAF_PDM_InitFieldNoDefault( &m_selectedRegions, "SelectedRefinementRegions", "Refinement Regions" ); + m_selectedRegions.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); + CAF_PDM_InitFieldNoDefault( &m_bcpropKeywords, "BcpropKeywords", "BCPROP Keywords" ); m_bcpropKeywords.uiCapability()->setUiEditorTypeName( caf::PdmUiTableViewEditor::uiEditorTypeName() ); m_bcpropKeywords.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN ); @@ -234,8 +242,12 @@ void RicExportSectorModelUi::defineUiOrdering( QString uiConfigName, caf::PdmUiO } else if ( uiConfigName == m_pageNames[WizardPageEnum::GridRefinement] ) { - m_refinementSettings->setSectorBounds( min(), max() ); - m_refinementSettings->addToUiOrdering( uiOrdering ); + uiOrdering.addNewLabel( "Refinement regions are managed in the project tree under the 3D view." ); + uiOrdering.addNewLabel( "Right-click the view and choose 'New Refinement Region' to create one." ); + uiOrdering.addNewLabel( "" ); + + auto* group = uiOrdering.addNewGroup( "Regions to Include in Export" ); + group->add( &m_selectedRegions ); } else if ( uiConfigName == m_pageNames[WizardPageEnum::BoundaryConditions] ) { @@ -363,6 +375,16 @@ void RicExportSectorModelUi::setEclipseView( RimEclipseView* view ) // Get default input deck file name from eclipse case QFileInfo fi( view->eclipseCase()->gridFileName() ); m_inputDeckName = fi.absolutePath() + "/" + fi.completeBaseName() + ".DATA"; + + // Preselect any existing refinement regions so the user doesn't have to tick them manually. + m_selectedRegions.clearWithoutDelete(); + if ( auto* collection = view->refinementRegionCollection() ) + { + for ( auto* region : collection->regions() ) + { + if ( region != nullptr ) m_selectedRegions.push_back( region ); + } + } } //-------------------------------------------------------------------------------------------------- @@ -395,6 +417,14 @@ void RicExportSectorModelUi::defineEditorAttribute( const caf::PdmFieldHandle* f attrib->heightHint = 200; } } + else if ( field == &m_selectedRegions ) + { + auto attrib = dynamic_cast( attribute ); + if ( attrib ) + { + attrib->heightHint = 100; + } + } else if ( field == &m_inputDeckName ) { caf::PdmUiFilePathEditorAttribute* myAttr = dynamic_cast( attribute ); @@ -462,17 +492,21 @@ RicRefinementSettings* RicExportSectorModelUi::refinementSettings() const //-------------------------------------------------------------------------------------------------- std::unique_ptr RicExportSectorModelUi::effectiveRefinement() const { - m_refinementSettings->setSectorBounds( min(), max() ); - return m_refinementSettings->effectiveRefinement(); -} + std::vector regions; + for ( const auto& r : m_selectedRegions ) + { + if ( r ) regions.push_back( r ); + } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -cvf::Vec3st RicExportSectorModelUi::refinement() const -{ - m_refinementSettings->setSectorBounds( min(), max() ); - return m_refinementSettings->refinement(); + auto combined = RimRefinementRegionCollection::combineRefinements( min(), max(), regions ); + if ( combined.has_value() ) + { + return std::move( *combined ); + } + + // Error: fall back to identity. The validate() step should have caught this already. + cvf::Vec3st sectorSize( max().x() - min().x() + 1, max().y() - min().y() + 1, max().z() - min().z() + 1 ); + return std::make_unique( sectorSize ); } //-------------------------------------------------------------------------------------------------- @@ -480,7 +514,10 @@ cvf::Vec3st RicExportSectorModelUi::refinement() const //-------------------------------------------------------------------------------------------------- bool RicExportSectorModelUi::hasNonUniformRefinement() const { - return m_refinementSettings->hasNonUniformRefinement(); + // With the new region-based model, any selected region producing non-identity refinement is + // treated as non-uniform (even if each region is internally uniform) because the combined + // RigRefinement is built as RigNonUniformRefinement. + return !m_selectedRegions.empty(); } //-------------------------------------------------------------------------------------------------- @@ -555,6 +592,31 @@ int RicExportSectorModelUi::wellPadding() const return m_visibleWellsPadding(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QList RicExportSectorModelUi::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) +{ + QList options; + + if ( fieldNeedingOptions == &m_selectedRegions ) + { + if ( m_eclipseView() ) + { + if ( auto* collection = m_eclipseView()->refinementRegionCollection() ) + { + for ( auto* region : collection->regions() ) + { + if ( !region ) continue; + options.push_back( caf::PdmOptionItemInfo( region->regionName(), region ) ); + } + } + } + } + + return options; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -782,9 +844,17 @@ std::map RicExportSectorModelUi::validate( const QString& conf } else if ( configName == m_pageNames[WizardPageEnum::GridRefinement] ) { - m_refinementSettings->setSectorBounds( min(), max() ); - auto refinementErrors = m_refinementSettings->validateSettings(); - fieldErrors.insert( refinementErrors.begin(), refinementErrors.end() ); + std::vector regions; + for ( const auto& r : m_selectedRegions ) + { + if ( r ) regions.push_back( r ); + } + + auto combined = RimRefinementRegionCollection::combineRefinements( min(), max(), regions ); + if ( !combined.has_value() ) + { + fieldErrors[m_selectedRegions.keyword()] = combined.error(); + } } else if ( configName == m_pageNames[WizardPageEnum::BoundaryConditions] ) { diff --git a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h index 11be82474c..01698b2e2b 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h +++ b/ApplicationLibCode/Commands/ExportCommands/RicExportSectorModelUi.h @@ -22,6 +22,7 @@ #include "cafPdmChildField.h" #include "cafPdmField.h" #include "cafPdmObject.h" +#include "cafPdmPtrArrayField.h" #include "cafPdmPtrField.h" #include "cafVecIjk.h" @@ -39,6 +40,7 @@ class RimKeywordBcprop; class RimEclipseCase; class RimEclipseView; class RicRefinementSettings; +class RimRefinementRegion; //================================================================================================== /// @@ -68,7 +70,6 @@ public: RicRefinementSettings* refinementSettings() const; std::unique_ptr effectiveRefinement() const; - cvf::Vec3st refinement() const; bool hasNonUniformRefinement() const; std::vector keywordsToRemove() const; @@ -94,7 +95,8 @@ protected: void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; - std::map validate( const QString& configName ) const override; + QList calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override; + std::map validate( const QString& configName ) const override; void setDefaultKeywordsToRemove(); @@ -129,7 +131,8 @@ private: caf::PdmField m_minK; caf::PdmField m_maxK; - caf::PdmChildField m_refinementSettings; + caf::PdmChildField m_refinementSettings; + caf::PdmPtrArrayField m_selectedRegions; caf::PdmField m_boundaryCondition; caf::PdmChildArrayField m_bcpropKeywords; diff --git a/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.cpp b/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.cpp index 259fa7a748..f94dc931cd 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.cpp @@ -20,31 +20,22 @@ #include "RiaLogging.h" -#include "RigNoRefinement.h" #include "RigNonUniformRefinement.h" -#include "RigUniformRefinement.h" + +#include "Rim3dView.h" #include "cafPdmUiCheckBoxEditor.h" #include "cafPdmUiGroup.h" -#include "cafPdmUiRadioButtonEditor.h" namespace caf { template <> -void AppEnum::setUp() -{ - addItem( RicRefinementSettings::NONE, "NONE", "None" ); - addItem( RicRefinementSettings::UNIFORM, "UNIFORM", "Uniform" ); - addItem( RicRefinementSettings::NON_UNIFORM, "NON_UNIFORM", "Non-Uniform" ); - setDefault( RicRefinementSettings::NONE ); -} -template <> void AppEnum::setUp() { - addItem( RicRefinementSettings::CUSTOM_WIDTHS, "CUSTOM_WIDTHS", "Custom Widths" ); addItem( RicRefinementSettings::LINEAR_EQUAL_SPLIT, "LINEAR_EQUAL_SPLIT", "Linear (Equal Split)" ); + addItem( RicRefinementSettings::CUSTOM_WIDTHS, "CUSTOM_WIDTHS", "Custom Widths" ); addItem( RicRefinementSettings::LOGARITHMIC_CENTER, "LOGARITHMIC_CENTER", "Logarithmic (Towards Center)" ); - setDefault( RicRefinementSettings::CUSTOM_WIDTHS ); + setDefault( RicRefinementSettings::LINEAR_EQUAL_SPLIT ); } } // namespace caf @@ -63,42 +54,19 @@ RicRefinementSettings::RicRefinementSettings() { CAF_PDM_InitObject( "Refinement Settings" ); - CAF_PDM_InitField( &m_refinementCountI, "RefinementCountI", 1, "Cell Count I, J, K" ); - CAF_PDM_InitField( &m_refinementCountJ, "RefinementCountJ", 1, "" ); - CAF_PDM_InitField( &m_refinementCountK, "RefinementCountK", 1, "" ); - - m_refinementCountI.setRange( 1, 10 ); - m_refinementCountJ.setRange( 1, 10 ); - m_refinementCountK.setRange( 1, 10 ); - - CAF_PDM_InitFieldNoDefault( &m_refinementMode, "RefinementMode", "Refinement Mode" ); - m_refinementMode.uiCapability()->setUiEditorTypeName( caf::PdmUiRadioButtonEditor::uiEditorTypeName() ); - - CAF_PDM_InitField( &m_nonUniformEnableI, "NonUniformEnableI", false, "Enable I Refinement" ); + CAF_PDM_InitField( &m_nonUniformEnableI, "NonUniformEnableI", true, "Enable I Refinement" ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_nonUniformEnableI ); - CAF_PDM_InitField( &m_nonUniformRangeStartI, "NonUniformRangeStartI", 1, "Grid Cell Start" ); - m_nonUniformRangeStartI.setMinValue( 1 ); - CAF_PDM_InitField( &m_nonUniformRangeEndI, "NonUniformRangeEndI", 1, "Grid Cell End" ); - m_nonUniformRangeEndI.setMinValue( 1 ); CAF_PDM_InitField( &m_nonUniformIntervalsI, "NonUniformIntervalsI", QString( "0.5, 0.5" ), "Fractional Widths" ); - CAF_PDM_InitField( &m_nonUniformEnableJ, "NonUniformEnableJ", false, "Enable J Refinement" ); + CAF_PDM_InitField( &m_nonUniformEnableJ, "NonUniformEnableJ", true, "Enable J Refinement" ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_nonUniformEnableJ ); - CAF_PDM_InitField( &m_nonUniformRangeStartJ, "NonUniformRangeStartJ", 1, "Grid Cell Start" ); - m_nonUniformRangeStartJ.setMinValue( 1 ); - CAF_PDM_InitField( &m_nonUniformRangeEndJ, "NonUniformRangeEndJ", 1, "Grid Cell End" ); - m_nonUniformRangeEndJ.setMinValue( 1 ); CAF_PDM_InitField( &m_nonUniformIntervalsJ, "NonUniformIntervalsJ", QString( "0.5, 0.5" ), "Fractional Widths" ); - CAF_PDM_InitField( &m_nonUniformEnableK, "NonUniformEnableK", false, "Enable K Refinement" ); + CAF_PDM_InitField( &m_nonUniformEnableK, "NonUniformEnableK", true, "Enable K Refinement" ); caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_nonUniformEnableK ); - CAF_PDM_InitField( &m_nonUniformRangeStartK, "NonUniformRangeStartK", 1, "Grid Cell Start" ); - m_nonUniformRangeStartK.setMinValue( 1 ); - CAF_PDM_InitField( &m_nonUniformRangeEndK, "NonUniformRangeEndK", 1, "Grid Cell End" ); - m_nonUniformRangeEndK.setMinValue( 1 ); CAF_PDM_InitField( &m_nonUniformIntervalsK, "NonUniformIntervalsK", QString( "0.5, 0.5" ), "Fractional Widths" ); - CAF_PDM_InitFieldNoDefault( &m_nonUniformSubMode, "NonUniformSubMode", "Non-Uniform Mode" ); + CAF_PDM_InitFieldNoDefault( &m_nonUniformSubMode, "NonUniformSubMode", "Refinement Mode" ); CAF_PDM_InitField( &m_nonUniformSubcellCountI, "NonUniformSubcellCountI", 2, "Subcells per Cell" ); m_nonUniformSubcellCountI.setRange( 2, 100 ); @@ -133,43 +101,9 @@ void RicRefinementSettings::setSectorBounds( const caf::VecIjk0& min, const caf: //-------------------------------------------------------------------------------------------------- std::unique_ptr RicRefinementSettings::effectiveRefinement() const { - size_t sectorSizeI = m_sectorMaxI - m_sectorMinI + 1; - size_t sectorSizeJ = m_sectorMaxJ - m_sectorMinJ + 1; - size_t sectorSizeK = m_sectorMaxK - m_sectorMinK + 1; - - cvf::Vec3st sectorSize( sectorSizeI, sectorSizeJ, sectorSizeK ); - - if ( m_refinementMode() == NONE ) return std::make_unique( sectorSize ); - - if ( m_refinementMode() == UNIFORM ) - { - return std::make_unique( cvf::Vec3st( m_refinementCountI(), m_refinementCountJ(), m_refinementCountK() ), - sectorSize ); - } - return nonUniformRefinement(); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -cvf::Vec3st RicRefinementSettings::refinement() const -{ - if ( m_refinementMode() != UNIFORM ) - { - return cvf::Vec3st( 1, 1, 1 ); - } - return cvf::Vec3st( m_refinementCountI(), m_refinementCountJ(), m_refinementCountK() ); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RicRefinementSettings::RefinementMode RicRefinementSettings::refinementMode() const -{ - return m_refinementMode(); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -183,13 +117,9 @@ std::unique_ptr RicRefinementSettings::nonUniformRefine auto result = std::make_unique( sectorSize ); - if ( m_refinementMode() != NON_UNIFORM ) return result; - struct DimensionConfig { bool enabled; - int rangeStart; - int rangeEnd; QString intervals; int subcellCount; int totalCells; @@ -197,64 +127,27 @@ std::unique_ptr RicRefinementSettings::nonUniformRefine }; std::vector dims = { - { m_nonUniformEnableI(), - m_nonUniformRangeStartI(), - m_nonUniformRangeEndI(), - m_nonUniformIntervalsI(), - m_nonUniformSubcellCountI(), - m_nonUniformTotalCellsI(), - RigNonUniformRefinement::DimI }, - { m_nonUniformEnableJ(), - m_nonUniformRangeStartJ(), - m_nonUniformRangeEndJ(), - m_nonUniformIntervalsJ(), - m_nonUniformSubcellCountJ(), - m_nonUniformTotalCellsJ(), - RigNonUniformRefinement::DimJ }, - { m_nonUniformEnableK(), - m_nonUniformRangeStartK(), - m_nonUniformRangeEndK(), - m_nonUniformIntervalsK(), - m_nonUniformSubcellCountK(), - m_nonUniformTotalCellsK(), - RigNonUniformRefinement::DimK }, + { m_nonUniformEnableI(), m_nonUniformIntervalsI(), m_nonUniformSubcellCountI(), m_nonUniformTotalCellsI(), RigNonUniformRefinement::DimI }, + { m_nonUniformEnableJ(), m_nonUniformIntervalsJ(), m_nonUniformSubcellCountJ(), m_nonUniformTotalCellsJ(), RigNonUniformRefinement::DimJ }, + { m_nonUniformEnableK(), m_nonUniformIntervalsK(), m_nonUniformSubcellCountK(), m_nonUniformTotalCellsK(), RigNonUniformRefinement::DimK }, }; - // Sector min indices (1-based) for converting original grid coordinates to sector-relative - int sectorMin[3] = { m_sectorMinI, m_sectorMinJ, m_sectorMinK }; - const QString dimLabels[3] = { "I", "J", "K" }; auto subMode = m_nonUniformSubMode(); - RiaLogging::info( QString( "Non-uniform refinement: sector size [%1, %2, %3], sector min [%4, %5, %6]" ) - .arg( sectorSize.x() ) - .arg( sectorSize.y() ) - .arg( sectorSize.z() ) - .arg( sectorMin[0] ) - .arg( sectorMin[1] ) - .arg( sectorMin[2] ) ); + RiaLogging::info( + QString( "Non-uniform refinement: sector size [%1, %2, %3]" ).arg( sectorSize.x() ).arg( sectorSize.y() ).arg( sectorSize.z() ) ); for ( const auto& dc : dims ) { if ( !dc.enabled ) continue; - // Convert from original grid coordinates (1-based) to sector-relative (0-based) - int sectorStart = dc.rangeStart - sectorMin[static_cast( dc.dim )]; - int sectorEnd = dc.rangeEnd - sectorMin[static_cast( dc.dim )]; - - // Clamp to sector bounds - int sectorMaxIdx = static_cast( result->sectorSize( dc.dim ) ) - 1; - sectorStart = std::max( 0, sectorStart ); - sectorEnd = std::min( sectorMaxIdx, sectorEnd ); - if ( sectorStart > sectorEnd ) - { - RiaLogging::warning( QString( "Non-uniform refinement %1: range [%2, %3] is outside sector after clamping, skipping" ) - .arg( dimLabels[static_cast( dc.dim )] ) - .arg( sectorStart ) - .arg( sectorEnd ) ); - continue; - } + // Non-uniform refinement spans the full region in the enabled direction; the region's + // IJK bounds already define the range, so there is no sub-range to apply. + const int sectorStart = 0; + const int sectorEnd = static_cast( result->sectorSize( dc.dim ) ) - 1; + if ( sectorEnd < sectorStart ) continue; if ( subMode == CUSTOM_WIDTHS ) { @@ -267,15 +160,12 @@ std::unique_ptr RicRefinementSettings::nonUniformRefine continue; } - RiaLogging::info( - QString( "Non-uniform refinement %1: grid range [%2, %3] -> sector range [%4, %5] (sector size %6, %7 widths)" ) - .arg( dimLabels[static_cast( dc.dim )] ) - .arg( dc.rangeStart ) - .arg( dc.rangeEnd ) - .arg( sectorStart ) - .arg( sectorEnd ) - .arg( result->sectorSize( dc.dim ) ) - .arg( widths.size() ) ); + RiaLogging::info( QString( "Non-uniform refinement %1: sector range [%2, %3] (sector size %4, %5 widths)" ) + .arg( dimLabels[static_cast( dc.dim )] ) + .arg( sectorStart ) + .arg( sectorEnd ) + .arg( result->sectorSize( dc.dim ) ) + .arg( widths.size() ) ); result->distributeWidthsAcrossCells( dc.dim, sectorStart, sectorEnd, widths ); } @@ -322,7 +212,7 @@ std::unique_ptr RicRefinementSettings::nonUniformRefine //-------------------------------------------------------------------------------------------------- bool RicRefinementSettings::hasNonUniformRefinement() const { - return m_refinementMode() == NON_UNIFORM && ( m_nonUniformEnableI() || m_nonUniformEnableJ() || m_nonUniformEnableK() ); + return m_nonUniformEnableI() || m_nonUniformEnableJ() || m_nonUniformEnableK(); } //-------------------------------------------------------------------------------------------------- @@ -330,79 +220,43 @@ bool RicRefinementSettings::hasNonUniformRefinement() const //-------------------------------------------------------------------------------------------------- void RicRefinementSettings::addToUiOrdering( caf::PdmUiOrdering& uiOrdering ) { - uiOrdering.add( &m_refinementMode ); + uiOrdering.add( &m_nonUniformSubMode ); uiOrdering.addNewLabel( "" ); - if ( m_refinementMode() == UNIFORM ) - { - uiOrdering.add( &m_refinementCountI, { .newRow = true, .totalColumnSpan = 2, .leftLabelColumnSpan = 1 } ); - uiOrdering.appendToRow( &m_refinementCountJ ); - uiOrdering.appendToRow( &m_refinementCountK ); - } - else if ( m_refinementMode() == NON_UNIFORM ) - { - uiOrdering.add( &m_nonUniformSubMode ); - uiOrdering.addNewLabel( "" ); + auto subMode = m_nonUniformSubMode(); - auto subMode = m_nonUniformSubMode(); + auto addDimensionGroup = [&]( const QString& label, + caf::PdmField& enableField, + caf::PdmField& intervalsField, + caf::PdmField& subcellCountField, + caf::PdmField& totalCellsField ) + { + auto* grp = uiOrdering.addNewGroup( label ); + grp->setCollapsedByDefault(); + grp->add( &enableField ); - auto addDimensionGroup = [&]( const QString& label, - caf::PdmField& enableField, - caf::PdmField& rangeStartField, - caf::PdmField& rangeEndField, - caf::PdmField& intervalsField, - caf::PdmField& subcellCountField, - caf::PdmField& totalCellsField ) + if ( subMode == CUSTOM_WIDTHS ) { - auto* grp = uiOrdering.addNewGroup( label ); - grp->setCollapsedByDefault(); - grp->add( &enableField ); - grp->add( &rangeStartField ); - grp->add( &rangeEndField ); + grp->add( &intervalsField ); + } + else if ( subMode == LINEAR_EQUAL_SPLIT ) + { + grp->add( &subcellCountField ); + } + else if ( subMode == LOGARITHMIC_CENTER ) + { + grp->add( &totalCellsField ); + } - if ( subMode == CUSTOM_WIDTHS ) - { - grp->add( &intervalsField ); - } - else if ( subMode == LINEAR_EQUAL_SPLIT ) - { - grp->add( &subcellCountField ); - } - else if ( subMode == LOGARITHMIC_CENTER ) - { - grp->add( &totalCellsField ); - } + bool dimEnabled = enableField(); + intervalsField.uiCapability()->setUiReadOnly( !dimEnabled ); + subcellCountField.uiCapability()->setUiReadOnly( !dimEnabled ); + totalCellsField.uiCapability()->setUiReadOnly( !dimEnabled ); + }; - bool dimEnabled = enableField(); - rangeStartField.uiCapability()->setUiReadOnly( !dimEnabled ); - rangeEndField.uiCapability()->setUiReadOnly( !dimEnabled ); - intervalsField.uiCapability()->setUiReadOnly( !dimEnabled ); - subcellCountField.uiCapability()->setUiReadOnly( !dimEnabled ); - totalCellsField.uiCapability()->setUiReadOnly( !dimEnabled ); - }; - - addDimensionGroup( "I Direction", - m_nonUniformEnableI, - m_nonUniformRangeStartI, - m_nonUniformRangeEndI, - m_nonUniformIntervalsI, - m_nonUniformSubcellCountI, - m_nonUniformTotalCellsI ); - addDimensionGroup( "J Direction", - m_nonUniformEnableJ, - m_nonUniformRangeStartJ, - m_nonUniformRangeEndJ, - m_nonUniformIntervalsJ, - m_nonUniformSubcellCountJ, - m_nonUniformTotalCellsJ ); - addDimensionGroup( "K Direction", - m_nonUniformEnableK, - m_nonUniformRangeStartK, - m_nonUniformRangeEndK, - m_nonUniformIntervalsK, - m_nonUniformSubcellCountK, - m_nonUniformTotalCellsK ); - } + addDimensionGroup( "I Direction", m_nonUniformEnableI, m_nonUniformIntervalsI, m_nonUniformSubcellCountI, m_nonUniformTotalCellsI ); + addDimensionGroup( "J Direction", m_nonUniformEnableJ, m_nonUniformIntervalsJ, m_nonUniformSubcellCountJ, m_nonUniformTotalCellsJ ); + addDimensionGroup( "K Direction", m_nonUniformEnableK, m_nonUniformIntervalsK, m_nonUniformSubcellCountK, m_nonUniformTotalCellsK ); } //-------------------------------------------------------------------------------------------------- @@ -412,62 +266,27 @@ std::map RicRefinementSettings::validateSettings() const { std::map fieldErrors; - if ( m_refinementMode() == NONE ) return fieldErrors; - - if ( m_refinementMode() == UNIFORM ) + struct DimValidation { - for ( auto& field : { &m_refinementCountI, &m_refinementCountJ, &m_refinementCountK } ) - { - auto errStr = field->validate(); - if ( !errStr.isEmpty() ) - { - fieldErrors[field->keyword()] = errStr; - } - } - } - else + const caf::PdmField& enable; + const caf::PdmField& intervals; + QString label; + }; + + std::vector dims = { + { m_nonUniformEnableI, m_nonUniformIntervalsI, "I" }, + { m_nonUniformEnableJ, m_nonUniformIntervalsJ, "J" }, + { m_nonUniformEnableK, m_nonUniformIntervalsK, "K" }, + }; + + for ( const auto& dv : dims ) { - struct DimValidation + if ( !dv.enable() ) continue; + + if ( m_nonUniformSubMode() == CUSTOM_WIDTHS && parseWidths( dv.intervals() ).empty() ) { - const caf::PdmField& enable; - const caf::PdmField& rangeStart; - const caf::PdmField& rangeEnd; - const caf::PdmField& intervals; - int sectorMin; - int sectorMax; - QString label; - }; - - std::vector dims = { - { m_nonUniformEnableI, m_nonUniformRangeStartI, m_nonUniformRangeEndI, m_nonUniformIntervalsI, m_sectorMinI, m_sectorMaxI, "I" }, - { m_nonUniformEnableJ, m_nonUniformRangeStartJ, m_nonUniformRangeEndJ, m_nonUniformIntervalsJ, m_sectorMinJ, m_sectorMaxJ, "J" }, - { m_nonUniformEnableK, m_nonUniformRangeStartK, m_nonUniformRangeEndK, m_nonUniformIntervalsK, m_sectorMinK, m_sectorMaxK, "K" }, - }; - - for ( const auto& dv : dims ) - { - if ( !dv.enable() ) continue; - - if ( dv.rangeStart() > dv.rangeEnd() ) - { - fieldErrors[dv.rangeStart.keyword()] = - QString( "%1 direction: Grid Cell Start cannot be larger than Grid Cell End." ).arg( dv.label ); - } - if ( dv.rangeStart() < dv.sectorMin || dv.rangeEnd() > dv.sectorMax ) - { - fieldErrors[dv.rangeEnd.keyword()] = - QString( "%1 direction: Refinement range [%2, %3] is outside the sector model range [%4, %5]." ) - .arg( dv.label ) - .arg( dv.rangeStart() ) - .arg( dv.rangeEnd() ) - .arg( dv.sectorMin ) - .arg( dv.sectorMax ); - } - if ( m_nonUniformSubMode() == CUSTOM_WIDTHS && parseWidths( dv.intervals() ).empty() ) - { - fieldErrors[dv.intervals.keyword()] = - QString( "%1 direction: Fractional widths must contain at least one positive value." ).arg( dv.label ); - } + fieldErrors[dv.intervals.keyword()] = + QString( "%1 direction: Fractional widths must contain at least one positive value." ).arg( dv.label ); } } @@ -488,11 +307,16 @@ void RicRefinementSettings::defineUiOrdering( QString uiConfigName, caf::PdmUiOr //-------------------------------------------------------------------------------------------------- void RicRefinementSettings::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { - if ( ( changedField == &m_refinementMode ) || ( changedField == &m_nonUniformEnableI ) || ( changedField == &m_nonUniformEnableJ ) || - ( changedField == &m_nonUniformEnableK ) || ( changedField == &m_nonUniformSubMode ) ) + if ( ( changedField == &m_nonUniformEnableI ) || ( changedField == &m_nonUniformEnableJ ) || ( changedField == &m_nonUniformEnableK ) || + ( changedField == &m_nonUniformSubMode ) ) { updateConnectedEditors(); } + + if ( auto view = firstAncestorOrThisOfType() ) + { + view->scheduleCreateDisplayModelAndRedraw(); + } } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.h b/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.h index d83f7f7536..8072c5a3d1 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.h +++ b/ApplicationLibCode/Commands/ExportCommands/RicRefinementSettings.h @@ -33,7 +33,7 @@ class RigNonUniformRefinement; //================================================================================================== /// -/// Settings for grid refinement (both uniform and non-uniform) +/// Settings for non-uniform grid refinement of a RimRefinementRegion. /// //================================================================================================== class RicRefinementSettings : public caf::PdmObject @@ -41,13 +41,6 @@ class RicRefinementSettings : public caf::PdmObject CAF_PDM_HEADER_INIT; public: - enum RefinementMode - { - NONE, - UNIFORM, - NON_UNIFORM - }; - enum NonUniformSubMode { CUSTOM_WIDTHS, @@ -55,7 +48,6 @@ public: LOGARITHMIC_CENTER }; - using RefinementModeEnum = caf::AppEnum; using NonUniformSubModeEnum = caf::AppEnum; RicRefinementSettings(); @@ -64,8 +56,6 @@ public: // Data access std::unique_ptr effectiveRefinement() const; - cvf::Vec3st refinement() const; - RefinementMode refinementMode() const; std::unique_ptr nonUniformRefinement() const; bool hasNonUniformRefinement() const; @@ -81,25 +71,13 @@ private: static std::vector parseWidths( const QString& text ); private: - caf::PdmField m_refinementCountI; - caf::PdmField m_refinementCountJ; - caf::PdmField m_refinementCountK; - - caf::PdmField m_refinementMode; - caf::PdmField m_nonUniformEnableI; - caf::PdmField m_nonUniformRangeStartI; - caf::PdmField m_nonUniformRangeEndI; caf::PdmField m_nonUniformIntervalsI; caf::PdmField m_nonUniformEnableJ; - caf::PdmField m_nonUniformRangeStartJ; - caf::PdmField m_nonUniformRangeEndJ; caf::PdmField m_nonUniformIntervalsJ; caf::PdmField m_nonUniformEnableK; - caf::PdmField m_nonUniformRangeStartK; - caf::PdmField m_nonUniformRangeEndK; caf::PdmField m_nonUniformIntervalsK; caf::PdmField m_nonUniformSubMode; diff --git a/ApplicationLibCode/Commands/RicNewRefinementRegionFeature.cpp b/ApplicationLibCode/Commands/RicNewRefinementRegionFeature.cpp new file mode 100644 index 0000000000..f46f32c1a0 --- /dev/null +++ b/ApplicationLibCode/Commands/RicNewRefinementRegionFeature.cpp @@ -0,0 +1,88 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RicNewRefinementRegionFeature.h" + +#include "RimEclipseCase.h" +#include "RimEclipseView.h" +#include "RimRefinementRegion.h" +#include "RimRefinementRegionCollection.h" + +#include "Riu3DMainWindowTools.h" + +#include "cafSelectionManagerTools.h" + +#include + +CAF_CMD_SOURCE_INIT( RicNewRefinementRegionFeature, "RicNewRefinementRegionFeature" ); + +namespace +{ +RimRefinementRegionCollection* selectedCollection() +{ + auto collections = caf::selectedObjectsByTypeStrict(); + if ( !collections.empty() ) return collections.front(); + + auto views = caf::selectedObjectsByTypeStrict(); + if ( !views.empty() && views.front() ) return views.front()->refinementRegionCollection(); + + auto regions = caf::selectedObjectsByTypeStrict(); + if ( !regions.empty() && regions.front() ) + { + return regions.front()->firstAncestorOrThisOfType(); + } + + return nullptr; +} +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicNewRefinementRegionFeature::isCommandEnabled() const +{ + return selectedCollection() != nullptr; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicNewRefinementRegionFeature::onActionTriggered( bool isChecked ) +{ + auto collection = selectedCollection(); + if ( !collection ) return; + + auto* view = collection->firstAncestorOrThisOfType(); + auto* eclipseCase = view ? view->eclipseCase() : nullptr; + + auto* region = collection->addNewRegion( eclipseCase ); + + if ( view ) view->scheduleCreateDisplayModelAndRedraw(); + + collection->updateConnectedEditors(); + Riu3DMainWindowTools::selectAsCurrentItem( region ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicNewRefinementRegionFeature::setupActionLook( QAction* actionToSetup ) +{ + actionToSetup->setText( "New Refinement Region" ); + actionToSetup->setIcon( QIcon( ":/CellFilter_Range.png" ) ); +} diff --git a/ApplicationLibCode/Commands/RicNewRefinementRegionFeature.h b/ApplicationLibCode/Commands/RicNewRefinementRegionFeature.h new file mode 100644 index 0000000000..abd29fa6b8 --- /dev/null +++ b/ApplicationLibCode/Commands/RicNewRefinementRegionFeature.h @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "cafCmdFeature.h" + +class RicNewRefinementRegionFeature : public caf::CmdFeature +{ + CAF_CMD_HEADER_INIT; + +protected: + bool isCommandEnabled() const override; + void onActionTriggered( bool isChecked ) override; + void setupActionLook( QAction* actionToSetup ) override; +}; diff --git a/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake b/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake index 75fd7514b2..dc3e278e66 100644 --- a/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake +++ b/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake @@ -56,6 +56,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RivPolylinePartMgr.h ${CMAKE_CURRENT_LIST_DIR}/RivDrawableSpheres.h ${CMAKE_CURRENT_LIST_DIR}/RivBoxGeometryGenerator.h + ${CMAKE_CURRENT_LIST_DIR}/RivRefinementRegionPartMgr.h ${CMAKE_CURRENT_LIST_DIR}/RivAnnotationTools.h ${CMAKE_CURRENT_LIST_DIR}/RivAnnotationSourceInfo.h ) @@ -113,6 +114,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RivPolylinePartMgr.cpp ${CMAKE_CURRENT_LIST_DIR}/RivDrawableSpheres.cpp ${CMAKE_CURRENT_LIST_DIR}/RivBoxGeometryGenerator.cpp + ${CMAKE_CURRENT_LIST_DIR}/RivRefinementRegionPartMgr.cpp ${CMAKE_CURRENT_LIST_DIR}/RivAnnotationTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RivAnnotationSourceInfo.cpp ) diff --git a/ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.cpp b/ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.cpp new file mode 100644 index 0000000000..a7bdbfad29 --- /dev/null +++ b/ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.cpp @@ -0,0 +1,427 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RivRefinementRegionPartMgr.h" + +#include "RiaPreferences.h" + +#include "RivBoxGeometryGenerator.h" +#include "RivPartPriority.h" +#include "RivScalarMapperUtils.h" + +#include "ResultAccessors/RigResultAccessor.h" +#include "ResultAccessors/RigResultAccessorFactory.h" +#include "RigEclipseCaseData.h" +#include "RigGridExportAdapter.h" +#include "RigMainGrid.h" +#include "RigRefinement.h" + +#include "Rim3dView.h" +#include "RimEclipseCase.h" +#include "RimEclipseCellColors.h" +#include "RimEclipseView.h" +#include "RimRefinementRegion.h" +#include "RimRefinementRegionCollection.h" +#include "RimRegularLegendConfig.h" + +#include "cafDisplayCoordTransform.h" +#include "cafEffectGenerator.h" + +#include "cvfDrawableGeo.h" +#include "cvfMatrix4.h" +#include "cvfModelBasicList.h" +#include "cvfPart.h" +#include "cvfPrimitiveSetIndexedUInt.h" +#include "cvfScalarMapper.h" +#include "cvfStructGrid.h" +#include "cvfStructGridGeometryGenerator.h" + +#include +#include + +namespace +{ +// Above this refined-cell count, the per-cell solid + wireframe rendering is skipped in favour of the outer-box wireframe. +constexpr size_t MAX_CELLS_FOR_PER_CELL_GEOMETRY = 200000; + +constexpr int VERTICES_PER_SUB_CELL = 24; // 6 faces x 4 corners +constexpr int TRI_INDICES_PER_SUB_CELL = 36; // 6 faces x 2 triangles x 3 indices +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivRefinementRegionPartMgr::RivRefinementRegionPartMgr() = default; + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivRefinementRegionPartMgr::~RivRefinementRegionPartMgr() = default; + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivRefinementRegionPartMgr::clearGeometryCache() +{ + m_regionCaches.clear(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivRefinementRegionPartMgr::buildGeometry( const RimRefinementRegionCollection* collection, + RimEclipseCase* eclipseCase, + const caf::DisplayCoordTransform* coordTransform ) +{ + clearGeometryCache(); + + if ( !collection || !eclipseCase || !coordTransform ) return; + if ( !collection->isActive() ) return; + + for ( auto* region : collection->regions() ) + { + if ( !region || !region->isActive() ) continue; + buildRegionCache( region, eclipseCase, coordTransform ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivRefinementRegionPartMgr::appendStaticPartsToModel( cvf::ModelBasicList* model ) +{ + if ( !model ) return; + + for ( auto& cache : m_regionCaches ) + { + if ( cache.facePart.notNull() ) model->addPart( cache.facePart.p() ); + if ( cache.meshPart.notNull() ) model->addPart( cache.meshPart.p() ); + if ( cache.outerBoxPart.notNull() ) model->addPart( cache.outerBoxPart.p() ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// Build the cache entry for a single region: per-sub-cell vertex array, triangle index list for +/// the solid faces, line index list for the mesh overlay, and parent-cell index list used by +/// updateCellResultColor() to look up the result value of the original (un-refined) cell. +//-------------------------------------------------------------------------------------------------- +void RivRefinementRegionPartMgr::buildRegionCache( const RimRefinementRegion* region, + RimEclipseCase* eclipseCase, + const caf::DisplayCoordTransform* coordTransform ) +{ + auto* caseData = eclipseCase->eclipseCaseData(); + if ( !caseData ) return; + auto* mainGrid = caseData->mainGrid(); + if ( !mainGrid ) return; + + const size_t gridI = mainGrid->cellCountI(); + const size_t gridJ = mainGrid->cellCountJ(); + const size_t gridK = mainGrid->cellCountK(); + if ( gridI == 0 || gridJ == 0 || gridK == 0 ) return; + + const auto rmin = region->ijkMin(); + const auto rmax = region->ijkMax(); + const size_t minI = std::min( rmin.x(), gridI - 1 ); + const size_t minJ = std::min( rmin.y(), gridJ - 1 ); + const size_t minK = std::min( rmin.z(), gridK - 1 ); + const size_t maxI = std::min( rmax.x(), gridI - 1 ); + const size_t maxJ = std::min( rmax.y(), gridJ - 1 ); + const size_t maxK = std::min( rmax.z(), gridK - 1 ); + + auto refinement = region->effectiveRefinement(); + if ( !refinement ) + { + RegionCache cache; + cache.fallbackColor = region->previewColor(); + cache.gridIndex = 0; + cache.outerBoxPart = createOuterBoxPart( region, eclipseCase, coordTransform ); + if ( cache.outerBoxPart.notNull() ) m_regionCaches.push_back( cache ); + return; + } + + cvf::Vec3st sectorMin( minI, minJ, minK ); + cvf::Vec3st sectorMax( maxI, maxJ, maxK ); + + RigGridExportAdapter adapter( caseData, sectorMin, sectorMax, *refinement ); + + const size_t refinedI = adapter.cellCountI(); + const size_t refinedJ = adapter.cellCountJ(); + const size_t refinedK = adapter.cellCountK(); + const size_t totalRefined = refinedI * refinedJ * refinedK; + if ( totalRefined == 0 ) return; + + if ( totalRefined > MAX_CELLS_FOR_PER_CELL_GEOMETRY ) + { + RegionCache cache; + cache.fallbackColor = region->previewColor(); + cache.gridIndex = 0; + cache.outerBoxPart = createOuterBoxPart( region, eclipseCase, coordTransform ); + if ( cache.outerBoxPart.notNull() ) m_regionCaches.push_back( cache ); + return; + } + + // RigGridExportAdapter applies MAPAXES to its output. The 3D view works in reservoir-native + // (pre-MAPAXES) coords, so undo it for the preview if active. + cvf::Mat4d invMapAxes = cvf::Mat4d::IDENTITY; + bool applyInverse = adapter.useMapAxes(); + if ( applyInverse ) invMapAxes = adapter.mapAxisTransform().getInverted(); + + std::vector vertices; + vertices.reserve( totalRefined * VERTICES_PER_SUB_CELL ); + + std::vector parentCellIdx; + parentCellIdx.reserve( totalRefined ); + + for ( size_t k = 0; k < refinedK; ++k ) + { + for ( size_t j = 0; j < refinedJ; ++j ) + { + for ( size_t i = 0; i < refinedI; ++i ) + { + auto corners = adapter.getCellCorners( i, j, k ); + if ( applyInverse ) + { + for ( auto& c : corners ) + c.transformPoint( invMapAxes ); + } + + for ( int faceEnum = cvf::StructGridInterface::POS_I; faceEnum < cvf::StructGridInterface::NO_FACE; ++faceEnum ) + { + auto face = static_cast( faceEnum ); + cvf::ubyte faceConn[4]; + cvf::StructGridInterface::cellFaceVertexIndices( face, faceConn ); + + for ( int n = 0; n < 4; ++n ) + { + auto display = coordTransform->transformToDisplayCoord( corners[faceConn[n]] ); + vertices.push_back( cvf::Vec3f( display ) ); + } + } + + auto mapping = adapter.mapRefinedToOriginal( i, j, k ); + parentCellIdx.push_back( mainGrid->cellIndexFromIJK( mapping.originalI, mapping.originalJ, mapping.originalK ) ); + } + } + } + + if ( vertices.empty() ) return; + + cvf::ref vertexArray = new cvf::Vec3fArray; + vertexArray->assign( vertices ); + + // Triangle indices: each face quad [a,b,c,d] becomes two triangles [a,b,c] and [a,c,d]. + cvf::ref triIndices = new cvf::UIntArray; + triIndices->resize( totalRefined * TRI_INDICES_PER_SUB_CELL ); + for ( size_t cell = 0; cell < totalRefined; ++cell ) + { + const cvf::uint baseVertex = static_cast( cell * VERTICES_PER_SUB_CELL ); + const size_t baseIndex = cell * TRI_INDICES_PER_SUB_CELL; + for ( int face = 0; face < 6; ++face ) + { + const cvf::uint q = baseVertex + face * 4; + const size_t t = baseIndex + face * 6; + triIndices->set( t + 0, q + 0 ); + triIndices->set( t + 1, q + 1 ); + triIndices->set( t + 2, q + 2 ); + triIndices->set( t + 3, q + 0 ); + triIndices->set( t + 4, q + 2 ); + triIndices->set( t + 5, q + 3 ); + } + } + + cvf::ref faceGeo = new cvf::DrawableGeo; + faceGeo->setVertexArray( vertexArray.p() ); + + cvf::ref trianglePrim = new cvf::PrimitiveSetIndexedUInt( cvf::PT_TRIANGLES ); + trianglePrim->setIndices( triIndices.p() ); + faceGeo->addPrimitiveSet( trianglePrim.p() ); + faceGeo->computeNormals(); + + cvf::ref facePart = new cvf::Part; + facePart->setName( "RivRefinementRegionPartMgr - refined cell faces" ); + facePart->setDrawable( faceGeo.p() ); + facePart->setPriority( RivPartPriority::PartType::BaseLevel ); + + // Mesh wireframe overlay. + cvf::ref lineIndices = cvf::StructGridGeometryGenerator::lineIndicesFromQuadVertexArray( vertexArray.p() ); + cvf::ref meshGeo = new cvf::DrawableGeo; + meshGeo->setVertexArray( vertexArray.p() ); + cvf::ref linePrim = new cvf::PrimitiveSetIndexedUInt( cvf::PT_LINES ); + linePrim->setIndices( lineIndices.p() ); + meshGeo->addPrimitiveSet( linePrim.p() ); + + cvf::ref meshPart = new cvf::Part; + meshPart->setName( "RivRefinementRegionPartMgr - refined cell mesh" ); + meshPart->setDrawable( meshGeo.p() ); + meshPart->setPriority( RivPartPriority::PartType::MeshLines ); + + caf::MeshEffectGenerator meshEffGen( RiaPreferences::current()->defaultGridLineColors() ); + meshPart->setEffect( meshEffGen.generateCachedEffect().p() ); + + RegionCache cache; + cache.facePart = facePart; + cache.meshPart = meshPart; + cache.parentGlobalCellIdx = std::move( parentCellIdx ); + cache.fallbackColor = region->previewColor(); + cache.gridIndex = 0; + cache.textureCoords = new cvf::Vec2fArray; + cache.textureCoords->resize( vertices.size() ); + + // Apply a flat fallback color so the part renders sensibly before updateCellResultColor() runs. + applyFlatColorToFacePart( cache ); + + m_regionCaches.push_back( std::move( cache ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivRefinementRegionPartMgr::applyFlatColorToFacePart( RegionCache& cache ) +{ + if ( cache.facePart.isNull() ) return; + + caf::SurfaceEffectGenerator surfEff( cvf::Color4f( cache.fallbackColor, 1.0f ), caf::PO_1 ); + cache.facePart->setEffect( surfEff.generateCachedEffect().p() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivRefinementRegionPartMgr::updateCellResultColor( size_t timeStepIndex, RimEclipseCellColors* cellResultColors ) +{ + if ( m_regionCaches.empty() ) return; + if ( !cellResultColors ) return; + + auto* view = cellResultColors->reservoirView(); + if ( !view ) return; + auto* eclipseCase = view->eclipseCase(); + if ( !eclipseCase || !eclipseCase->eclipseCaseData() ) return; + + const bool noResult = !cellResultColors->hasResult(); + const cvf::ScalarMapper* mapper = noResult ? nullptr : cellResultColors->legendConfig()->scalarMapper(); + const bool ternary = cellResultColors->isTernarySaturationSelected(); + const bool lighting = view->isLightingDisabled(); + + // Ternary saturations are not supported for the preview; fall back to a flat color so the + // user still sees the region clearly. + if ( noResult || !mapper || ternary ) + { + for ( auto& cache : m_regionCaches ) + applyFlatColorToFacePart( cache ); + return; + } + + cvf::ref accessor = + RigResultAccessorFactory::createFromResultDefinition( eclipseCase->eclipseCaseData(), 0, timeStepIndex, cellResultColors ); + if ( accessor.isNull() ) + { + for ( auto& cache : m_regionCaches ) + applyFlatColorToFacePart( cache ); + return; + } + + for ( auto& cache : m_regionCaches ) + { + if ( cache.facePart.isNull() || cache.textureCoords.isNull() ) continue; + if ( cache.parentGlobalCellIdx.empty() ) continue; + + const size_t numSubCells = cache.parentGlobalCellIdx.size(); + cvf::Vec2f* rawTexCoord = cache.textureCoords->ptr(); + + for ( size_t s = 0; s < numSubCells; ++s ) + { + double value = accessor->cellScalar( cache.parentGlobalCellIdx[s] ); + cvf::Vec2f texCoord; + if ( value == HUGE_VAL || std::isnan( value ) || std::isinf( value ) ) + { + // y=1.0 targets the "undefined" texel row of the legend texture, rendered in undefColor. + texCoord = cvf::Vec2f( 0.0f, 1.0f ); + } + else + { + texCoord = mapper->mapToTextureCoord( value ); + } + + const size_t base = s * VERTICES_PER_SUB_CELL; + for ( int v = 0; v < VERTICES_PER_SUB_CELL; ++v ) + { + rawTexCoord[base + v] = texCoord; + } + } + + RivScalarMapperUtils::applyTextureResultsToPart( cache.facePart.p(), cache.textureCoords.p(), mapper, 1.0f, caf::FC_NONE, lighting ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// Fallback for very large regions: an 8-vertex wireframe box at the region's extremes. +//-------------------------------------------------------------------------------------------------- +cvf::ref RivRefinementRegionPartMgr::createOuterBoxPart( const RimRefinementRegion* region, + RimEclipseCase* eclipseCase, + const caf::DisplayCoordTransform* coordTransform ) +{ + auto* caseData = eclipseCase->eclipseCaseData(); + if ( !caseData ) return nullptr; + auto* mainGrid = caseData->mainGrid(); + if ( !mainGrid ) return nullptr; + + const size_t gridI = mainGrid->cellCountI(); + const size_t gridJ = mainGrid->cellCountJ(); + const size_t gridK = mainGrid->cellCountK(); + + const auto rmin = region->ijkMin(); + const auto rmax = region->ijkMax(); + const size_t minI = std::min( rmin.x(), gridI - 1 ); + const size_t minJ = std::min( rmin.y(), gridJ - 1 ); + const size_t minK = std::min( rmin.z(), gridK - 1 ); + const size_t maxI = std::min( rmax.x(), gridI - 1 ); + const size_t maxJ = std::min( rmax.y(), gridJ - 1 ); + const size_t maxK = std::min( rmax.z(), gridK - 1 ); + + struct CornerPick + { + size_t i; + size_t j; + size_t k; + size_t localCorner; + }; + + const std::array picks = { { + { minI, minJ, minK, 0 }, + { maxI, minJ, minK, 1 }, + { maxI, maxJ, minK, 2 }, + { minI, maxJ, minK, 3 }, + { minI, minJ, maxK, 4 }, + { maxI, minJ, maxK, 5 }, + { maxI, maxJ, maxK, 6 }, + { minI, maxJ, maxK, 7 }, + } }; + + std::vector vertices; + vertices.reserve( 8 ); + for ( const auto& p : picks ) + { + size_t cellIdx = mainGrid->cellIndexFromIJK( p.i, p.j, p.k ); + auto corners = mainGrid->cellCornerVertices( cellIdx ); + auto domain = corners[p.localCorner]; + auto display = coordTransform->transformToDisplayCoord( domain ); + vertices.push_back( cvf::Vec3f( display ) ); + } + + return RivBoxGeometryGenerator::createBoxFromVertices( vertices, region->previewColor() ); +} diff --git a/ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.h b/ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.h new file mode 100644 index 0000000000..bc7f68dfb5 --- /dev/null +++ b/ApplicationLibCode/ModelVisualization/RivRefinementRegionPartMgr.h @@ -0,0 +1,93 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "cvfArray.h" +#include "cvfColor3.h" +#include "cvfObject.h" + +#include + +namespace cvf +{ +class ModelBasicList; +class Part; +} // namespace cvf + +namespace caf +{ +class DisplayCoordTransform; +} + +class RimEclipseCase; +class RimEclipseCellColors; +class RimRefinementRegion; +class RimRefinementRegionCollection; + +//================================================================================================== +/// +/// Builds parts for previewing refinement regions in the 3D view. Each region is rendered as +/// solid polyhedrons (one per refined sub-cell) coloured by the parent cell's value of the view's +/// currently selected cell result, with mesh lines drawn on top. +/// +/// Geometry and the per-sub-cell parent-cell index list are built once in buildGeometry() and +/// cached. Per-time-step recolouring updates only texture coordinates via updateCellResultColor(). +/// +//================================================================================================== +class RivRefinementRegionPartMgr : public cvf::Object +{ +public: + RivRefinementRegionPartMgr(); + ~RivRefinementRegionPartMgr() override; + + void clearGeometryCache(); + + // Rebuild geometry + parent-cell index list for every active region in the collection. + void buildGeometry( const RimRefinementRegionCollection* collection, + RimEclipseCase* eclipseCase, + const caf::DisplayCoordTransform* coordTransform ); + + // Append all cached parts (face + mesh, or outer-box fallback) to the given scene model. + void appendStaticPartsToModel( cvf::ModelBasicList* model ); + + // Recompute texture coordinates from the current result accessor and apply to cached face parts. + // No-op for regions that fell back to the outer-box wireframe. + void updateCellResultColor( size_t timeStepIndex, RimEclipseCellColors* cellResultColors ); + +private: + struct RegionCache + { + cvf::ref facePart; // PT_TRIANGLES, null when outer-box fallback or no geometry + cvf::ref meshPart; // PT_LINES, null when outer-box fallback + cvf::ref outerBoxPart; // 8-vertex wireframe box, used when refined cell count exceeds limit + cvf::ref textureCoords; // Reused per time step; sized to vertex count + std::vector parentGlobalCellIdx; // One entry per sub-cell, row-major (i,j,k) + cvf::Color3f fallbackColor; // Region preview color (used when no result is selected) + size_t gridIndex; // Source grid index (always 0 for main grid today) + }; + + static cvf::ref + createOuterBoxPart( const RimRefinementRegion* region, RimEclipseCase* eclipseCase, const caf::DisplayCoordTransform* coordTransform ); + + void buildRegionCache( const RimRefinementRegion* region, RimEclipseCase* eclipseCase, const caf::DisplayCoordTransform* coordTransform ); + + void applyFlatColorToFacePart( RegionCache& cache ); + + std::vector m_regionCaches; +}; diff --git a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake index fb60eb9a3f..ee7d38de71 100644 --- a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake @@ -61,6 +61,8 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimViewLinkerCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimContextCommandBuilder.h ${CMAKE_CURRENT_LIST_DIR}/RimGridCollection.h + ${CMAKE_CURRENT_LIST_DIR}/RimRefinementRegion.h + ${CMAKE_CURRENT_LIST_DIR}/RimRefinementRegionCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimPlotCurve.h ${CMAKE_CURRENT_LIST_DIR}/RimPlotCurveAppearance.h ${CMAKE_CURRENT_LIST_DIR}/RimStackablePlotCurve.h @@ -194,6 +196,8 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimViewLinkerCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimContextCommandBuilder.cpp ${CMAKE_CURRENT_LIST_DIR}/RimGridCollection.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimRefinementRegion.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimRefinementRegionCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimPlotCurve.cpp ${CMAKE_CURRENT_LIST_DIR}/RimPlotCurveAppearance.cpp ${CMAKE_CURRENT_LIST_DIR}/RimStackablePlotCurve.cpp diff --git a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp index da8cecde28..89a277c4b5 100644 --- a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -107,6 +107,8 @@ #include "RimPressureDepthData.h" #include "RimPressureTable.h" #include "RimProject.h" +#include "RimRefinementRegion.h" +#include "RimRefinementRegionCollection.h" #include "RimRftCase.h" #include "RimRftPlotCollection.h" #include "RimSaturationPressurePlotCollection.h" @@ -224,6 +226,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() menuBuilder << "RicNewContourMapViewFeature"; menuBuilder << "RicCreateGridCrossPlotFeature"; menuBuilder << "RicCreateSaturationPressurePlotsFeature"; + menuBuilder << "RicNewRefinementRegionFeature"; menuBuilder << "Separator"; menuBuilder << "RicCopyReferencesToClipboardFeature"; menuBuilder.subMenuStart( "Export" ); @@ -286,6 +289,16 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() menuBuilder << "RicExportCompletionsForTemporaryLgrsFeature"; menuBuilder << "RicDeleteTemporaryLgrsFeature"; } + else if ( dynamic_cast( firstUiItem ) ) + { + menuBuilder << "RicNewRefinementRegionFeature"; + } + else if ( dynamic_cast( firstUiItem ) ) + { + menuBuilder << "RicNewRefinementRegionFeature"; + menuBuilder << "Separator"; + menuBuilder << "RicDeleteItemFeature"; + } else if ( dynamic_cast( firstUiItem ) ) { menuBuilder << "RicPasteGeoMechViewsFeature"; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp index 8381b702c1..7e6fc312f3 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp @@ -77,6 +77,8 @@ #include "RimMultipleEclipseResults.h" #include "RimOilField.h" #include "RimProject.h" +#include "RimRefinementRegion.h" +#include "RimRefinementRegionCollection.h" #include "RimRegularLegendConfig.h" #include "RimReservoirCellResultsStorage.h" #include "RimSeismicSection.h" @@ -101,6 +103,7 @@ #include "RiuTools.h" #include "RiuViewer.h" +#include "RivRefinementRegionPartMgr.h" #include "RivReservoirSimWellsPartMgr.h" #include "RivReservoirViewPartMgr.h" #include "RivSingleCellPartGenerator.h" @@ -215,10 +218,11 @@ RimEclipseView::RimEclipseView() faultResultSettings()->setReservoirView( this ); - m_reservoirGridPartManager = new RivReservoirViewPartMgr( this ); - m_simWellsPartManager = new RivReservoirSimWellsPartMgr( this ); - m_streamlinesPartManager = new RivStreamlinesPartMgr( this ); - m_eclipseCase = nullptr; + m_reservoirGridPartManager = new RivReservoirViewPartMgr( this ); + m_simWellsPartManager = new RivReservoirSimWellsPartMgr( this ); + m_streamlinesPartManager = new RivStreamlinesPartMgr( this ); + m_refinementRegionPartManager = new RivRefinementRegionPartMgr; + m_eclipseCase = nullptr; nameConfig()->setCustomName( "3D View" ); nameConfig()->hideCaseNameField( false ); @@ -234,12 +238,18 @@ RimEclipseView::RimEclipseView() CAF_PDM_InitFieldNoDefault( &m_cameraPositions, "CameraPositions", "Camera Positions for Cases" ); + CAF_PDM_InitFieldNoDefault( &m_refinementRegions, "RefinementRegions", "Refinement Regions" ); + m_refinementRegions = new RimRefinementRegionCollection(); + setDeletable( true ); updateAnimations.connect( this, &RimEclipseView::onAnimationsUpdate ); m_faultReactVizModel = new cvf::ModelBasicList; m_faultReactVizModel->setName( "FaultReactModel" ); + + m_refinementRegionsVizModel = new cvf::ModelBasicList; + m_refinementRegionsVizModel->setName( "RefinementRegionsModel" ); } //-------------------------------------------------------------------------------------------------- @@ -334,6 +344,14 @@ RimFaultReactivationModelCollection* RimEclipseView::faultReactivationModelColle return m_faultReactivationModelCollection; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimRefinementRegionCollection* RimEclipseView::refinementRegionCollection() const +{ + return m_refinementRegions(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -742,6 +760,14 @@ void RimEclipseView::onCreateDisplayModel() m_faultReactivationModelCollection->appendPartsToModel( this, m_faultReactVizModel.p(), transform.p(), ownerCase()->allCellsBoundingBox() ); nativeOrOverrideViewer()->addStaticModelOnce( m_faultReactVizModel.p(), isUsingOverrideViewer() ); + // Refinement region preview boxes + m_refinementRegionsVizModel->removeAllParts(); + m_refinementRegionPartManager->buildGeometry( refinementRegionCollection(), eclipseCase(), transform.p() ); + m_refinementRegionPartManager->appendStaticPartsToModel( m_refinementRegionsVizModel.p() ); + m_refinementRegionPartManager->updateCellResultColor( m_currentTimeStep, cellResult() ); + m_refinementRegionsVizModel->updateBoundingBoxesRecursive(); + nativeOrOverrideViewer()->addStaticModelOnce( m_refinementRegionsVizModel.p(), isUsingOverrideViewer() ); + // Surfaces m_surfaceVizModel->removeAllParts(); if ( surfaceInViewCollection() ) @@ -892,6 +918,8 @@ void RimEclipseView::onUpdateDisplayModelForCurrentTimeStep() updateVisibleCellColors(); + m_refinementRegionPartManager->updateCellResultColor( m_currentTimeStep, cellResult() ); + wellCollection()->scaleWellDisks(); appendWellsAndFracturesToModel(); @@ -1250,6 +1278,11 @@ void RimEclipseView::initAfterRead() cellResult()->setReservoirView( this ); cellEdgeResult()->setReservoirView( this ); + if ( !m_refinementRegions() ) + { + m_refinementRegions = new RimRefinementRegionCollection(); + } + updateUiIconFromToggleField(); } @@ -2030,6 +2063,7 @@ void RimEclipseView::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrderin { uiTreeOrdering.add( m_overlayInfoConfig() ); uiTreeOrdering.add( m_gridCollection() ); + if ( m_refinementRegions() ) uiTreeOrdering.add( m_refinementRegions() ); uiTreeOrdering.add( cellResult() ); uiTreeOrdering.add( cellEdgeResult() ); uiTreeOrdering.add( cellFilterCollection() ); diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseView.h b/ApplicationLibCode/ProjectDataModel/RimEclipseView.h index b01f747bf8..8ba14cdbb6 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseView.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseView.h @@ -63,6 +63,7 @@ class RivReservoirSimWellsPartMgr; class RivExtrudedCurveIntersectionPartMgr; class RivReservoirViewPartMgr; class RivStreamlinesPartMgr; +class RivRefinementRegionPartMgr; class RimRegularLegendConfig; class RimTernaryLegendConfig; class RimEclipseResultDefinition; @@ -72,6 +73,7 @@ class RimMultipleEclipseResults; class RigEclipseResultAddress; class RimFaultReactivationModelCollection; class RimCameraPosition; +class RimRefinementRegionCollection; namespace cvf { @@ -113,6 +115,7 @@ public: RimVirtualPerforationResults* virtualPerforationResult() const; RimStreamlineInViewCollection* streamlineCollection() const; RimFaultReactivationModelCollection* faultReactivationModelCollection() const; + RimRefinementRegionCollection* refinementRegionCollection() const; bool showInvalidCells() const; bool showInactiveCells() const; @@ -240,6 +243,7 @@ private: protected: cvf::ref m_faultReactVizModel; + cvf::ref m_refinementRegionsVizModel; caf::PdmPtrField m_eclipseCase; caf::PdmField> m_caseChangeBehaviour; @@ -268,11 +272,13 @@ private: cvf::ref m_reservoirGridPartManager; cvf::ref m_simWellsPartManager; cvf::ref m_streamlinesPartManager; + cvf::ref m_refinementRegionPartManager; std::vector m_visibleGridParts; - caf::PdmChildField m_additionalResultsForResultInfo; - caf::PdmChildArrayField m_cameraPositions; + caf::PdmChildField m_additionalResultsForResultInfo; + caf::PdmChildArrayField m_cameraPositions; + caf::PdmChildField m_refinementRegions; // Callback for providing available Eclipse cases (used by view collections to filter cases) std::function()> m_eclipseCaseProvider; diff --git a/ApplicationLibCode/ProjectDataModel/RimRefinementRegion.cpp b/ApplicationLibCode/ProjectDataModel/RimRefinementRegion.cpp new file mode 100644 index 0000000000..1745eb71cd --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimRefinementRegion.cpp @@ -0,0 +1,327 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RimRefinementRegion.h" + +#include "ExportCommands/RicRefinementSettings.h" + +#include "RigMainGrid.h" + +#include "Rim3dView.h" +#include "RimEclipseCase.h" +#include "RimEclipseView.h" + +#include "cafPdmFieldCvfColor.h" +#include "cafPdmUiSliderEditor.h" + +CAF_PDM_SOURCE_INIT( RimRefinementRegion, "RefinementRegion" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimRefinementRegion::RimRefinementRegion() +{ + CAF_PDM_InitObject( "Refinement Region", ":/CellFilter_Range.png" ); + + CAF_PDM_InitField( &m_isActive, "IsActive", true, "Show in 3D View" ); + CAF_PDM_InitField( &m_regionName, "Name", QString( "Refinement Region" ), "Name" ); + + CAF_PDM_InitField( &m_startI, "StartI", 1, "I Start" ); + m_startI.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); + CAF_PDM_InitField( &m_cellCountI, "CellCountI", 1, "I Width" ); + m_cellCountI.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); + + CAF_PDM_InitField( &m_startJ, "StartJ", 1, "J Start" ); + m_startJ.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); + CAF_PDM_InitField( &m_cellCountJ, "CellCountJ", 1, "J Width" ); + m_cellCountJ.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); + + CAF_PDM_InitField( &m_startK, "StartK", 1, "K Start" ); + m_startK.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); + CAF_PDM_InitField( &m_cellCountK, "CellCountK", 1, "K Width" ); + m_cellCountK.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() ); + + CAF_PDM_InitField( &m_previewColor, "PreviewColor", cvf::Color3f( 1.0f, 0.5f, 0.0f ), "Preview Color" ); + + CAF_PDM_InitFieldNoDefault( &m_refinementSettings, "RefinementSettings", "Refinement" ); + m_refinementSettings = new RicRefinementSettings(); + m_refinementSettings->uiCapability()->setUiTreeHidden( true ); + + setDeletable( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimRefinementRegion::isActive() const +{ + return m_isActive(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimRefinementRegion::regionName() const +{ + return m_regionName(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegion::setRegionName( const QString& name ) +{ + m_regionName = name; + setUiName( name ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int RimRefinementRegion::startI() const +{ + return m_startI(); +} + +int RimRefinementRegion::startJ() const +{ + return m_startJ(); +} + +int RimRefinementRegion::startK() const +{ + return m_startK(); +} + +int RimRefinementRegion::cellCountI() const +{ + return m_cellCountI(); +} + +int RimRefinementRegion::cellCountJ() const +{ + return m_cellCountJ(); +} + +int RimRefinementRegion::cellCountK() const +{ + return m_cellCountK(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::VecIjk0 RimRefinementRegion::ijkMin() const +{ + return caf::VecIjk0( static_cast( std::max( 1, m_startI() ) - 1 ), + static_cast( std::max( 1, m_startJ() ) - 1 ), + static_cast( std::max( 1, m_startK() ) - 1 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::VecIjk0 RimRefinementRegion::ijkMax() const +{ + int endI = m_startI() + std::max( 1, m_cellCountI() ) - 1; + int endJ = m_startJ() + std::max( 1, m_cellCountJ() ) - 1; + int endK = m_startK() + std::max( 1, m_cellCountK() ) - 1; + + return caf::VecIjk0( static_cast( std::max( 1, endI ) - 1 ), + static_cast( std::max( 1, endJ ) - 1 ), + static_cast( std::max( 1, endK ) - 1 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegion::setDefaultsFromCase( RimEclipseCase* eclipseCase ) +{ + if ( !eclipseCase ) return; + auto grid = eclipseCase->mainGrid(); + if ( !grid ) return; + + m_startI = 1; + m_startJ = 1; + m_startK = 1; + m_cellCountI = static_cast( grid->cellCountI() ); + m_cellCountJ = static_cast( grid->cellCountJ() ); + m_cellCountK = static_cast( grid->cellCountK() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::unique_ptr RimRefinementRegion::effectiveRefinement() const +{ + m_refinementSettings->setSectorBounds( ijkMin(), ijkMax() ); + return m_refinementSettings->effectiveRefinement(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RicRefinementSettings* RimRefinementRegion::refinementSettings() const +{ + return m_refinementSettings(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimRefinementRegion::validateWithinSector( int sectorMinI, int sectorMinJ, int sectorMinK, int sectorMaxI, int sectorMaxJ, int sectorMaxK ) const +{ + int endI = m_startI() + m_cellCountI() - 1; + int endJ = m_startJ() + m_cellCountJ() - 1; + int endK = m_startK() + m_cellCountK() - 1; + + if ( m_startI() < sectorMinI || endI > sectorMaxI || m_startJ() < sectorMinJ || endJ > sectorMaxJ || m_startK() < sectorMinK || + endK > sectorMaxK ) + { + return QString( "Region '%1' (I=%2-%3, J=%4-%5, K=%6-%7) is outside the sector bounds " + "(I=%8-%9, J=%10-%11, K=%12-%13)." ) + .arg( m_regionName() ) + .arg( m_startI() ) + .arg( endI ) + .arg( m_startJ() ) + .arg( endJ ) + .arg( m_startK() ) + .arg( endK ) + .arg( sectorMinI ) + .arg( sectorMaxI ) + .arg( sectorMinJ ) + .arg( sectorMaxJ ) + .arg( sectorMinK ) + .arg( sectorMaxK ); + } + return {}; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::Color3f RimRefinementRegion::previewColor() const +{ + return m_previewColor(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::PdmFieldHandle* RimRefinementRegion::objectToggleField() +{ + return &m_isActive; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::PdmFieldHandle* RimRefinementRegion::userDescriptionField() +{ + return &m_regionName; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegion::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + auto* rangeGroup = uiOrdering.addNewGroup( "Region Bounds" ); + rangeGroup->add( &m_startI ); + rangeGroup->add( &m_cellCountI ); + rangeGroup->add( &m_startJ ); + rangeGroup->add( &m_cellCountJ ); + rangeGroup->add( &m_startK ); + rangeGroup->add( &m_cellCountK ); + + auto* appearanceGroup = uiOrdering.addNewGroup( "Appearance" ); + appearanceGroup->add( &m_previewColor ); + appearanceGroup->setCollapsedByDefault(); + + m_refinementSettings->addToUiOrdering( uiOrdering ); + + uiOrdering.skipRemainingFields( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegion::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) +{ + auto* sliderAttr = dynamic_cast( attribute ); + if ( !sliderAttr ) return; + + auto grid = mainGrid(); + if ( !grid ) return; + + if ( field == &m_startI || field == &m_cellCountI ) + { + sliderAttr->m_minimum = 1; + sliderAttr->m_maximum = static_cast( grid->cellCountI() ); + } + else if ( field == &m_startJ || field == &m_cellCountJ ) + { + sliderAttr->m_minimum = 1; + sliderAttr->m_maximum = static_cast( grid->cellCountJ() ); + } + else if ( field == &m_startK || field == &m_cellCountK ) + { + sliderAttr->m_minimum = 1; + sliderAttr->m_maximum = static_cast( grid->cellCountK() ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegion::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) +{ + if ( changedField == &m_regionName ) + { + setUiName( m_regionName() ); + } + + if ( auto grid = mainGrid() ) + { + int maxI = static_cast( grid->cellCountI() ); + int maxJ = static_cast( grid->cellCountJ() ); + int maxK = static_cast( grid->cellCountK() ); + m_startI = std::clamp( m_startI.v(), 1, maxI ); + m_startJ = std::clamp( m_startJ.v(), 1, maxJ ); + m_startK = std::clamp( m_startK.v(), 1, maxK ); + m_cellCountI = std::clamp( m_cellCountI.v(), 1, maxI - m_startI() + 1 ); + m_cellCountJ = std::clamp( m_cellCountJ.v(), 1, maxJ - m_startJ() + 1 ); + m_cellCountK = std::clamp( m_cellCountK.v(), 1, maxK - m_startK() + 1 ); + } + + if ( auto view = firstAncestorOrThisOfType() ) + { + view->scheduleCreateDisplayModelAndRedraw(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +const RigMainGrid* RimRefinementRegion::mainGrid() const +{ + auto view = firstAncestorOrThisOfType(); + if ( !view ) return nullptr; + auto eclipseCase = view->eclipseCase(); + return eclipseCase ? eclipseCase->mainGrid() : nullptr; +} diff --git a/ApplicationLibCode/ProjectDataModel/RimRefinementRegion.h b/ApplicationLibCode/ProjectDataModel/RimRefinementRegion.h new file mode 100644 index 0000000000..894c8ee0d1 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimRefinementRegion.h @@ -0,0 +1,104 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "cafPdmChildField.h" +#include "cafPdmField.h" +#include "cafPdmObject.h" +#include "cafVecIjk.h" + +#include "cvfColor3.h" + +#include + +class RicRefinementSettings; +class RigMainGrid; +class RigRefinement; +class RimEclipseCase; + +//================================================================================================== +/// +/// One declarative refinement region for sector export. Defines an axis-aligned IJK box plus +/// a refinement specification (uniform or non-uniform). Rendered as a wireframe box in the 3D +/// view so the user can preview it before opening the sector-export dialog. +/// +//================================================================================================== +class RimRefinementRegion : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimRefinementRegion(); + + bool isActive() const; + QString regionName() const; + void setRegionName( const QString& name ); + + // IJK bounds, 1-based (Eclipse convention) as stored in the UI fields + int startI() const; + int startJ() const; + int startK() const; + int cellCountI() const; + int cellCountJ() const; + int cellCountK() const; + + // 0-based convenience accessors + caf::VecIjk0 ijkMin() const; + caf::VecIjk0 ijkMax() const; + + // Populate the region with defaults derived from the case's main grid (full grid, refinement 1,1,1). + void setDefaultsFromCase( RimEclipseCase* eclipseCase ); + + // Build the refinement for this region alone, sized to its own bounds. + std::unique_ptr effectiveRefinement() const; + + // Access the embedded refinement settings (used by the collection to assemble combined refinement). + RicRefinementSettings* refinementSettings() const; + + // Validate that the region lies entirely within the given sector bounds (1-based). + // Returns an empty string on success, otherwise a user-readable error message. + QString validateWithinSector( int sectorMinI, int sectorMinJ, int sectorMinK, int sectorMaxI, int sectorMaxJ, int sectorMaxK ) const; + + cvf::Color3f previewColor() const; + + caf::PdmFieldHandle* objectToggleField() override; + +protected: + caf::PdmFieldHandle* userDescriptionField() override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; + +private: + const RigMainGrid* mainGrid() const; + + caf::PdmField m_isActive; + caf::PdmField m_regionName; + + caf::PdmField m_startI; + caf::PdmField m_startJ; + caf::PdmField m_startK; + caf::PdmField m_cellCountI; + caf::PdmField m_cellCountJ; + caf::PdmField m_cellCountK; + + caf::PdmField m_previewColor; + + caf::PdmChildField m_refinementSettings; +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.cpp b/ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.cpp new file mode 100644 index 0000000000..7023f40b11 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.cpp @@ -0,0 +1,246 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "RimRefinementRegionCollection.h" + +#include "RigNoRefinement.h" +#include "RigNonUniformRefinement.h" + +#include "Rim3dView.h" +#include "RimEclipseCase.h" +#include "RimRefinementRegion.h" + +#include +#include + +CAF_PDM_SOURCE_INIT( RimRefinementRegionCollection, "RefinementRegionCollection" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimRefinementRegionCollection::RimRefinementRegionCollection() +{ + CAF_PDM_InitObject( "Refinement Regions", ":/CellFilter_Range.png" ); + + CAF_PDM_InitField( &m_isActive, "IsActive", true, "Show Regions in 3D View" ); + m_isActive.uiCapability()->setUiHidden( true ); + + CAF_PDM_InitFieldNoDefault( &m_regions, "Regions", "Regions" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimRefinementRegionCollection::isActive() const +{ + return m_isActive(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimRefinementRegionCollection::regions() const +{ + return m_regions.childrenByType(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimRefinementRegionCollection::activeRegions() const +{ + std::vector result; + for ( auto r : m_regions ) + { + if ( r && r->isActive() ) result.push_back( r ); + } + return result; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimRefinementRegion* RimRefinementRegionCollection::addNewRegion( RimEclipseCase* eclipseCase ) +{ + auto* region = new RimRefinementRegion(); + region->setRegionName( QString( "Region %1" ).arg( m_regions.size() + 1 ) ); + m_regions.push_back( region ); + + // Defaults depend on the case's grid; set after the region is inserted into the tree so + // that view/case ancestor lookups work inside the region. + region->setDefaultsFromCase( eclipseCase ); + + updateConnectedEditors(); + return region; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegionCollection::addRegion( RimRefinementRegion* region ) +{ + if ( region ) m_regions.push_back( region ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegionCollection::removeRegion( RimRefinementRegion* region ) +{ + if ( !region ) return; + m_regions.removeChild( region ); + delete region; + updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::PdmFieldHandle* RimRefinementRegionCollection::objectToggleField() +{ + return &m_isActive; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimRefinementRegionCollection::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) +{ + if ( auto view = firstAncestorOrThisOfType() ) + { + view->scheduleCreateDisplayModelAndRedraw(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected, QString> + RimRefinementRegionCollection::combineRefinements( const caf::VecIjk0& sectorMin, + const caf::VecIjk0& sectorMax, + const std::vector& regions ) +{ + cvf::Vec3st sectorSize( sectorMax.x() - sectorMin.x() + 1, sectorMax.y() - sectorMin.y() + 1, sectorMax.z() - sectorMin.z() + 1 ); + + if ( regions.empty() ) + { + return std::unique_ptr( std::make_unique( sectorSize ) ); + } + + // Sector bounds, 1-based, for error messages and validation + int sectorMinI = static_cast( sectorMin.x() ) + 1; + int sectorMinJ = static_cast( sectorMin.y() ) + 1; + int sectorMinK = static_cast( sectorMin.z() ) + 1; + int sectorMaxI = static_cast( sectorMax.x() ) + 1; + int sectorMaxJ = static_cast( sectorMax.y() ) + 1; + int sectorMaxK = static_cast( sectorMax.z() ) + 1; + + for ( auto* r : regions ) + { + if ( !r ) continue; + auto err = r->validateWithinSector( sectorMinI, sectorMinJ, sectorMinK, sectorMaxI, sectorMaxJ, sectorMaxK ); + if ( !err.isEmpty() ) return std::unexpected( err ); + } + + auto result = std::make_unique( sectorSize ); + + auto fractionsEqual = []( const std::vector& a, const std::vector& b ) + { + if ( a.size() != b.size() ) return false; + for ( size_t i = 0; i < a.size(); ++i ) + { + if ( std::abs( a[i] - b[i] ) > 1e-9 ) return false; + } + return true; + }; + + const char* dimLabel[3] = { "I", "J", "K" }; + const size_t sectorMin0[3] = { sectorMin.x(), sectorMin.y(), sectorMin.z() }; + + // For each dimension, track which region "owns" each sector index so we can give + // meaningful error messages on overlap. + std::vector ownerI( sectorSize.x(), nullptr ); + std::vector ownerJ( sectorSize.y(), nullptr ); + std::vector ownerK( sectorSize.z(), nullptr ); + std::vector* owners[3] = { &ownerI, &ownerJ, &ownerK }; + + size_t regionStarts[3]; + size_t regionEnds[3]; + + for ( auto* region : regions ) + { + if ( !region ) continue; + auto regionRefinement = region->effectiveRefinement(); + if ( !regionRefinement ) continue; + + auto regMin = region->ijkMin(); + auto regMax = region->ijkMax(); + + regionStarts[0] = regMin.x(); + regionStarts[1] = regMin.y(); + regionStarts[2] = regMin.z(); + regionEnds[0] = regMax.x(); + regionEnds[1] = regMax.y(); + regionEnds[2] = regMax.z(); + + for ( size_t d = 0; d < 3; ++d ) + { + auto dim = static_cast( d ); + + for ( size_t absIdx = regionStarts[d]; absIdx <= regionEnds[d]; ++absIdx ) + { + size_t sectorRelIdx = absIdx - sectorMin0[d]; + size_t regionRelIdx = absIdx - regionStarts[d]; + + const auto& newFracs = regionRefinement->cumulativeFractions( dim, regionRelIdx ); + + // If this cell is still default identity {1.0}, accept the region's fractions unconditionally. + const auto& existing = result->cumulativeFractions( dim, sectorRelIdx ); + bool existingIsIdentity = ( existing.size() == 1 && std::abs( existing[0] - 1.0 ) < 1e-12 ); + + if ( existingIsIdentity ) + { + result->setCumulativeFractions( dim, sectorRelIdx, newFracs ); + ( *owners[d] )[sectorRelIdx] = region; + } + else + { + // Already claimed by another region. Allow if the refinement matches exactly. + if ( !fractionsEqual( existing, newFracs ) ) + { + auto* prevOwner = ( *owners[d] )[sectorRelIdx]; + return std::unexpected( QString( "Refinement regions '%1' and '%2' both refine %3-index %4 with " + "different subdivisions. Regions must have disjoint projections on each " + "axis (or identical refinement on shared indices)." ) + .arg( prevOwner ? prevOwner->regionName() : QString( "" ) ) + .arg( region->regionName() ) + .arg( dimLabel[d] ) + .arg( static_cast( absIdx ) + 1 ) ); + } + } + } + } + } + + if ( !result->hasRefinement() ) + { + return std::unique_ptr( std::make_unique( sectorSize ) ); + } + + return std::unique_ptr( std::move( result ) ); +} diff --git a/ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.h b/ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.h new file mode 100644 index 0000000000..68bf7f004e --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimRefinementRegionCollection.h @@ -0,0 +1,73 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 "cafPdmChildArrayField.h" +#include "cafPdmField.h" +#include "cafPdmObject.h" +#include "cafVecIjk.h" + +#include +#include +#include +#include + +#include + +class RigRefinement; +class RimEclipseCase; +class RimRefinementRegion; + +//================================================================================================== +/// +/// Collection of RimRefinementRegion objects attached to a RimEclipseView. Combines selected +/// regions into a single RigRefinement for use in sector-model export. +/// +//================================================================================================== +class RimRefinementRegionCollection : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimRefinementRegionCollection(); + + bool isActive() const; + + std::vector regions() const; + std::vector activeRegions() const; + + RimRefinementRegion* addNewRegion( RimEclipseCase* eclipseCase ); + void addRegion( RimRefinementRegion* region ); + void removeRegion( RimRefinementRegion* region ); + + // Combine the given regions into a single RigRefinement sized to the supplied sector + // bounds (0-based, inclusive). Regions with disagreeing per-axis refinement on a shared + // cell-index return a user-readable error message in the unexpected state. + static std::expected, QString> + combineRefinements( const caf::VecIjk0& sectorMin, const caf::VecIjk0& sectorMax, const std::vector& regions ); + + caf::PdmFieldHandle* objectToggleField() override; + +protected: + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; + +private: + caf::PdmField m_isActive; + caf::PdmChildArrayField m_regions; +}; diff --git a/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h b/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h index 3257afbb28..9ac7b46beb 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h +++ b/ApplicationLibCode/ReservoirDataModel/RigGridExportAdapter.h @@ -100,6 +100,14 @@ public: bool applyRefinementCentering = false, bool isBoxMaxCoordinate = false ); + // Helper to calculate original cell indices and subcell indices from refined indices + struct CellMapping + { + size_t originalI, originalJ, originalK; // Original cell indices + size_t subI, subJ, subK; // Subcell indices within original cell + }; + CellMapping mapRefinedToOriginal( size_t refinedI, size_t refinedJ, size_t refinedK ) const; + private: // Internal methods to handle original vs refined cell access std::array getOriginalCellCorners( size_t origI, size_t origJ, size_t origK ) const; @@ -114,14 +122,6 @@ private: void applyCoordinateTransformation( std::array& corners ) const; void applyCoordinateTransformation( std::array& corners ) const; - // Helper to calculate original cell indices and subcell indices from refined indices - struct CellMapping - { - size_t originalI, originalJ, originalK; // Original cell indices - size_t subI, subJ, subK; // Subcell indices within original cell - }; - CellMapping mapRefinedToOriginal( size_t refinedI, size_t refinedJ, size_t refinedK ) const; - const RigMainGrid* m_mainGrid; const RigActiveCellInfo* m_activeCellInfo; const cvf::UByteArray* m_cellVisibilityOverride;