Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "RimEclipseCase.h"
#include "RimFilterInViewCollection.h"
#include "RimGridView.h"
#include "RimReservoirGridEnsemble.h"

#include "Riu3DMainWindowTools.h"

Expand Down Expand Up @@ -84,10 +85,10 @@ inline RimCellFilterCollection* resolveTargetCellFilterCollection()
}

//--------------------------------------------------------------------------------------------------
/// Resolve the case-level RimDataFilterCollection to target from the current selection: either the
/// data-filter collection node itself, or a selected RimEclipseCase (whose "Data Filters" node is
/// hidden while empty, so the case node is right-clicked to create the first filter). Returns
/// nullptr if neither is selected.
/// Resolve the RimDataFilterCollection to target from the current selection: either the data-filter
/// collection node itself, or a selected RimEclipseCase or RimReservoirGridEnsemble (whose "Data
/// Filters" node is hidden while empty, so the owner node is right-clicked to create the first
/// filter). Returns nullptr if none is selected.
//--------------------------------------------------------------------------------------------------
inline RimDataFilterCollection* selectedDataFilterCollection()
{
Expand All @@ -97,6 +98,9 @@ inline RimDataFilterCollection* selectedDataFilterCollection()
auto cases = caf::selectedObjectsByTypeStrict<RimEclipseCase*>();
if ( !cases.empty() && cases.front() ) return cases.front()->dataFilterCollection();

auto ensembles = caf::selectedObjectsByTypeStrict<RimReservoirGridEnsemble*>();
if ( !ensembles.empty() && ensembles.front() ) return ensembles.front()->dataFilterCollection();

return nullptr;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ void RicNewPolygonFilterFeature::onActionTriggered( bool isChecked )
return;
}

// If a case-level Data Filter Collection is selected, add polygon filters there.
auto* dataCollection = caf::SelectionManager::instance()->selectedItemOfType<RimDataFilterCollection>();
// If a case or ensemble level Data Filter Collection is selected (or its owner node), add
// polygon filters there.
auto* dataCollection = RicCellFilterFeatureTools::selectedDataFilterCollection();
if ( dataCollection )
{
if ( polygons.empty() ) polygons.push_back( nullptr );
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimCellFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCellFilterCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCellFilterTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCellRangeFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCombinedFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RimDataFilterCollection.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ caf::AppEnum<RimCellFilter::FilterModeType> RimCellFilter::filterMode() const
return m_filterMode();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCellFilter::setFilterMode( FilterModeType filterMode )
{
m_filterMode = filterMode;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -322,7 +330,10 @@ QList<caf::PdmOptionItemInfo> RimCellFilter::calculateValueOptions( const caf::P
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCellFilter::applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t /*timeStepIndex*/ )
void RimCellFilter::applyToCellVisibility( cvf::UByteArray* cellVisibility,
const RigGridBase* grid,
size_t /*timeStepIndex*/,
RimEclipseCase* /*sourceCaseOverride*/ )
{
if ( cellVisibility == nullptr || grid == nullptr ) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class RimCellFilter : public RimCheckableNamedObject
virtual bool isFilterEnabled() const;

caf::AppEnum<FilterModeType> filterMode() const;
void setFilterMode( FilterModeType filterMode );
QString modeString() const;

bool propagateToSubGrids() const;
Expand All @@ -97,7 +98,13 @@ class RimCellFilter : public RimCheckableNamedObject
// Unified evaluation: take an incoming per-cell visibility mask and hide the cells that
// this filter rejects (respecting its own INCLUDE/EXCLUDE mode). The default implementation
// bridges the legacy range/index dispatch so existing subclasses do not need to override.
virtual void applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t timeStepIndex );
// When sourceCaseOverride is set, property filters evaluate against that case's results instead
// of the case the filter is bound to, allowing the same filter definition to be evaluated
// against each case in an ensemble.
virtual void applyToCellVisibility( cvf::UByteArray* cellVisibility,
const RigGridBase* grid,
size_t timeStepIndex,
RimEclipseCase* sourceCaseOverride = nullptr );

protected:
caf::PdmFieldHandle* userDescriptionField() override;
Expand Down
107 changes: 107 additions & 0 deletions ApplicationLibCode/ProjectDataModel/CellFilters/RimCellFilterTools.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////

#include "RimCellFilterTools.h"

#include "RigEclipseCaseData.h"
#include "RigGridBase.h"
#include "RigLocalGrid.h"
#include "RigMainGrid.h"

#include "RimCellFilter.h"
#include "RimEclipseCase.h"

//--------------------------------------------------------------------------------------------------
/// Evaluate the filter against the given case for all grids, and return the visibility indexed by
/// reservoir cell index. Property filters are evaluated against the given case's own result values.
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::UByteArray>
RimCellFilterTools::computeReservoirCellVisibility( RimCellFilter* filter, RimEclipseCase* eclipseCase, size_t timeStepIndex )
{
if ( !filter || !eclipseCase || !eclipseCase->eclipseCaseData() || !eclipseCase->eclipseCaseData()->mainGrid() ) return nullptr;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: Please handle !filter->isFilterEnabled() before evaluating the selected data filter. Property filters currently no-op when unchecked, but base range/index filters and RimCombinedFilter do not check their own active state, so an unchecked range or combined filter still changes the calculation. This makes filter activation behave differently depending on filter type.


RigEclipseCaseData* caseData = eclipseCase->eclipseCaseData();

cvf::ref<cvf::UByteArray> reservoirVisibility = new cvf::UByteArray( caseData->mainGrid()->totalCellCount() );
reservoirVisibility->setAll( false );

const bool isInclude = ( filter->filterMode() == RimCellFilter::INCLUDE );

// Grid local masks are kept to allow propagation of parent grid visibility into LGRs
std::vector<cvf::ref<cvf::UByteArray>> gridMasks( caseData->gridCount() );

for ( size_t gridIndex = 0; gridIndex < caseData->gridCount(); gridIndex++ )
{
RigGridBase* grid = caseData->grid( gridIndex );

gridMasks[gridIndex] = new cvf::UByteArray( grid->cellCount() );
cvf::UByteArray& gridMask = *gridMasks[gridIndex];
gridMask.setAll( true );

if ( filter->isRangeFilter() )
{
// Range filters evaluate only on their target grid. On other grids an INCLUDE filter
// contributes no cells, while an EXCLUDE filter removes none.
const bool isTargetGrid = ( filter->gridIndex() == static_cast<int>( gridIndex ) );
if ( isTargetGrid )
{
filter->applyToCellVisibility( &gridMask, grid, timeStepIndex, eclipseCase );
}
else
{
gridMask.setAll( !isInclude );
}
}
else
{
// Index filters (polygon, user defined) and property filters evaluate on all grids
filter->applyToCellVisibility( &gridMask, grid, timeStepIndex, eclipseCase );
}

// Cells in LGRs follow the visibility of their parent grid cell, as in the filtered geometry
// of a 3d view. Geometry based filters (range and index, e.g. an INDEX_K polygon) can fail to
// select the refined cells directly on a fine LGR, so propagate the parent grid visibility.
// Property filters evaluate each cell against its own result value and must be left untouched.
const bool isGeometryFilter = filter->isRangeFilter() || filter->isIndexFilter();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: RimCombinedFilter is declared with FilterDefinitionType::INDEX, so this treats every combined filter as geometry-only. If it contains a property child, the OR/AND with parentMask can override the LGR cell's own property evaluation—for example, a matching parent makes all refined children visible for an INCLUDE combined filter. Please make propagation depend on the combined filter's children, or perform geometry propagation at child evaluation time before combining masks.

if ( isGeometryFilter && !grid->isMainGrid() )
{
auto localGrid = static_cast<const RigLocalGrid*>( grid );
const cvf::UByteArray& parentMask = *gridMasks[localGrid->parentGrid()->gridIndex()];

for ( size_t localIdx = 0; localIdx < grid->cellCount(); localIdx++ )
{
const size_t parentCellIndex = grid->cell( localIdx ).parentCellIndex();
if ( isInclude )
{
gridMask[localIdx] = gridMask[localIdx] || parentMask[parentCellIndex];
}
else
{
gridMask[localIdx] = gridMask[localIdx] && parentMask[parentCellIndex];
}
}
}

for ( size_t localIdx = 0; localIdx < grid->cellCount(); localIdx++ )
{
reservoirVisibility->set( grid->reservoirCellIndex( localIdx ), gridMask[localIdx] );
}
}

return reservoirVisibility;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "cvfArray.h"
#include "cvfObject.h"

class RimCellFilter;
class RimEclipseCase;

//==================================================================================================
///
//==================================================================================================
class RimCellFilterTools
{
public:
static cvf::ref<cvf::UByteArray> computeReservoirCellVisibility( RimCellFilter* filter, RimEclipseCase* eclipseCase, size_t timeStepIndex );
};
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ void RimCombinedFilter::onGridChanged()
/// INCLUDE/EXCLUDE mode inside applyToCellVisibility), AND/OR combine the masks, then apply this
/// combined filter's INCLUDE/EXCLUDE mode onto the incoming cellVisibility.
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t timeStepIndex )
void RimCombinedFilter::applyToCellVisibility( cvf::UByteArray* cellVisibility,
const RigGridBase* grid,
size_t timeStepIndex,
RimEclipseCase* sourceCaseOverride )
{
if ( cellVisibility == nullptr || grid == nullptr ) return;

Expand All @@ -125,7 +128,7 @@ void RimCombinedFilter::applyToCellVisibility( cvf::UByteArray* cellVisibility,
{
cvf::UByteArray childMask( n );
childMask.setAll( 1 );
child->applyToCellVisibility( &childMask, grid, timeStepIndex );
child->applyToCellVisibility( &childMask, grid, timeStepIndex, sourceCaseOverride );

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: An INCLUDE range child produces the wrong mask on grids other than its target grid. RimCellFilter::applyToCellVisibility() deliberately returns without changing the mask in that case, so this all-true childMask remains all true and the combined filter includes every cell on the non-target grid. Please initialize/evaluate range-child masks with the same target-grid semantics used by computeReservoirCellVisibility (INCLUDE contributes no cells; EXCLUDE removes none), including the intended parent-grid propagation.


if ( m_combineMode() == CombineMode::AND )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ class RimCombinedFilter : public RimCellFilter
bool isFilterEnabled() const override;
void onGridChanged() override;

void applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t timeStepIndex ) override;
void applyToCellVisibility( cvf::UByteArray* cellVisibility,
const RigGridBase* grid,
size_t timeStepIndex,
RimEclipseCase* sourceCaseOverride = nullptr ) override;

// Bridge from legacy collection dispatch: combined filter is declared INDEX-type, so the cell
// filter collection's evaluation path (if ever called) would route through updateCellIndexFilter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@

#include "RimDataFilterCollection.h"

#include "Polygons/RimPolygon.h"
#include "Polygons/RimPolygonCollection.h"

#include "RimCase.h"
#include "RimCellRangeFilter.h"
#include "RimCombinedFilter.h"
#include "RimEclipsePropertyFilter.h"
#include "RimEclipseResultDefinition.h"
#include "RimTools.h"

#include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmFieldScriptingCapability.h"
Expand Down Expand Up @@ -80,6 +84,7 @@ void RimDataFilterCollection::removeFilter( RimCellFilter* f )
if ( !f ) return;
deleteItem( f );
filtersChanged.send();
updateOwnerEditors();
}

//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -157,9 +162,9 @@ void RimDataFilterCollection::onItemsChanged()
}
filtersChanged.send();

// The collection node is hidden from the case tree while empty, so refresh the owner case to
// The collection node is hidden from the owner's tree while empty, so refresh the owner to
// re-run its defineUiTreeOrdering and add the node once the first filter appears.
if ( auto* c = ownerCase() ) c->updateConnectedEditors();
updateOwnerEditors();
}

//--------------------------------------------------------------------------------------------------
Expand All @@ -171,8 +176,22 @@ void RimDataFilterCollection::onChildDeleted( caf::PdmChildArrayFieldHandle* chi
updateConnectedEditors();
filtersChanged.send();

// Refresh the owner case so its defineUiTreeOrdering re-runs and drops the node once empty.
if ( auto* c = ownerCase() ) c->updateConnectedEditors();
// Refresh the owner so its defineUiTreeOrdering re-runs and drops the node once empty.
updateOwnerEditors();
}

//--------------------------------------------------------------------------------------------------
/// Refresh the object owning this collection in the project tree. Note that the owner is not
/// necessarily the source case: for a collection in a grid ensemble, the source case is the main
/// case of the ensemble, while the tree node is owned by the ensemble.
//--------------------------------------------------------------------------------------------------
void RimDataFilterCollection::updateOwnerEditors()
{
caf::PdmObjectHandle* owner = parentField() ? parentField()->ownerObject() : nullptr;
if ( owner && owner->uiCapability() )
{
owner->uiCapability()->updateConnectedEditors();
}
}

//--------------------------------------------------------------------------------------------------
Expand All @@ -193,6 +212,21 @@ void RimDataFilterCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuB
{
menuBuilder << "RicEclipsePropertyFilterNewFeature";
menuBuilder << "Separator";

menuBuilder.subMenuStart( "Polygon Filter", QIcon( ":/CellFilter_Polygon.png" ) );
{
auto polygonCollection = RimTools::polygonCollection();
for ( auto p : polygonCollection->allPolygons() )
{
if ( !p ) continue;

menuBuilder.addCmdFeatureWithUserData( "RicNewPolygonFilterFeature", p->name(), QVariant::fromValue( static_cast<void*>( p ) ) );
}
}
menuBuilder.subMenuEnd();

menuBuilder << "RicNewPolygonFilterFeature";
menuBuilder << "Separator";
menuBuilder.subMenuStart( "Range Filter" );
menuBuilder << "RicNewRangeFilterSliceIFeature";
menuBuilder << "RicNewRangeFilterSliceJFeature";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class RimDataFilterCollection : public caf::PdmObjectCollection<RimCellFilter>
private:
void connectChildSignal( RimCellFilter* child );
void onChildFilterChanged( const caf::SignalEmitter* emitter );
void updateOwnerEditors();

caf::PdmPtrField<RimCase*> m_srcCase;
};
Loading