CellFilters: Add combined filter for mixing cell and property filters

Add RimCombinedFilter, a node placed under the Eclipse property-filter
collection that holds child filters (cell or property) and combines their
results with a user-chosen AND or OR mode. Supports nesting and is exposed
to Python via the generated rips CombinedFilter class.

To let the same evaluation machinery handle any filter type, introduce a
unified applyToCellVisibility(cellVis, grid, timeStep) virtual on
RimCellFilter with a default implementation that bridges the existing
range/index dispatch. RimEclipsePropertyFilter overrides it with the body
extracted from RivReservoirViewPartMgr::computePropertyVisibility, which
collapses to a generic per-child call and preserves the prior behavior
for ordinary property filters.

Loosen RimEclipsePropertyFilterCollection::m_propertyFilters to
PdmChildArrayField<RimCellFilter*>, keeping the XML tag "PropertyFilters"
and the typed propertyFilters() accessor. Add recursive helpers
(hasActiveEvaluatableDescendant, hasActiveDynamicPropertyDescendant,
hasActiveFormationNamesPropertyDescendant) so hasActiveFilters,
hasActiveDynamicFilters and isUsingFormationNames see property filters
nested inside a combined filter. Bridge filter changes inside a combined
filter to updateDisplayModelNotifyManagedViews, since the property-filter
collection doesn't subscribe to filterChanged signals.

The existing RicNew* cell-filter creation features now route the new child
into a selected combined filter when one is selected, otherwise into the
top-level cell-filter collection as before. The combined filter's context
menu offers the full mixed set: property, polygon, range, index,
user-defined, and nested combined filter.
This commit is contained in:
Kristian Bendiksen committed 2026-04-29 11:40:18 +02:00
1 parent b27199f4ba
commit 9d5a8c28f4
23 files changed
+1188 -119

No files matched your search

@@ -2,6 +2,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimCellFilter.h
${CMAKE_CURRENT_LIST_DIR}/RimCellFilterCollection.h
${CMAKE_CURRENT_LIST_DIR}/RimCellRangeFilter.h
${CMAKE_CURRENT_LIST_DIR}/RimCombinedFilter.h
${CMAKE_CURRENT_LIST_DIR}/RimPropertyFilter.h
${CMAKE_CURRENT_LIST_DIR}/RimPropertyFilterCollection.h
${CMAKE_CURRENT_LIST_DIR}/RimEclipsePropertyFilter.h
@@ -19,6 +20,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimCellFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCellFilterCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCellRangeFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCombinedFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RimPropertyFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RimPropertyFilterCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimEclipsePropertyFilter.cpp
@@ -18,6 +18,7 @@
#include "RimCellFilter.h"
#include "RigGridBase.h"
#include "RigReservoirGridTools.h"
#include "Rim3dView.h"
#include "RimCase.h"
@@ -302,6 +303,83 @@ QList<caf::PdmOptionItemInfo> RimCellFilter::calculateValueOptions( const caf::P
return options;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCellFilter::applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t /*timeStepIndex*/ )
{
if ( cellVisibility == nullptr || grid == nullptr ) return;
const size_t n = cellVisibility->size();
if ( n == 0 ) return;
// Determine which cells are "in this filter's set" — independent of the filter's INCLUDE/EXCLUDE mode.
cvf::UByteArray inSet( n );
inSet.setAll( 0 );
const int gIndx = static_cast<int>( grid->gridIndex() );
if ( isRangeFilter() )
{
// updateCompundFilter is a no-op when the range filter targets a different grid than the
// one being evaluated. Skip the cellVisibility mutation entirely so LGR cells are not
// erroneously blanked by a main-grid-targeted filter (legacy: parentGridVisibilities).
if ( gridIndex() != gIndx ) return;
cvf::CellRangeFilter rf;
updateCompundFilter( &rf, gIndx );
const bool isSubGrid = !grid->isMainGrid();
for ( size_t cellIdx = 0; cellIdx < n; ++cellIdx )
{
size_t i = 0, j = 0, k = 0;
if ( grid->ijkFromCellIndex( cellIdx, &i, &j, &k ) )
{
// isCellVisible returns false when there are no include ranges (pure EXCLUDE
// mode), so also consult isCellExcluded to mark those cells as "in the set".
if ( rf.isCellVisible( i, j, k, isSubGrid ) || rf.isCellExcluded( i, j, k, isSubGrid ) )
{
inSet[cellIdx] = 1;
}
}
}
}
else if ( isIndexFilter() )
{
// updateCellIndexFilter writes into include[] for INCLUDE-mode filters and exclude[] for
// EXCLUDE-mode filters. A cell is "in the set" iff either array was mutated at that index.
cvf::UByteArray incArr( n );
cvf::UByteArray excArr( n );
incArr.setAll( 0 );
excArr.setAll( 1 );
updateCellIndexFilter( &incArr, &excArr, gIndx );
for ( size_t i = 0; i < n; ++i )
{
if ( incArr[i] || !excArr[i] ) inSet[i] = 1;
}
}
else
{
// Unknown/property — subclass must override applyToCellVisibility. Default to no-op.
return;
}
const bool isInclude = ( filterMode() == INCLUDE );
for ( size_t i = 0; i < n; ++i )
{
const bool in = ( inSet[i] != 0 );
if ( isInclude )
{
if ( !in ) ( *cellVisibility )[i] = 0;
}
else
{
if ( in ) ( *cellVisibility )[i] = 0;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -37,6 +37,7 @@ class CellRangeFilter;
class RimGeoMechCase;
class RimEclipseCase;
class RimCase;
class RigGridBase;
//==================================================================================================
///
@@ -93,6 +94,11 @@ public:
virtual QString fullName() const;
virtual void onGridChanged() {};
// 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 );
protected:
caf::PdmFieldHandle* userDescriptionField() override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
@@ -0,0 +1,538 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "RimCombinedFilter.h"
#include "RigGridBase.h"
#include "RigReservoirGridTools.h"
#include "RimCase.h"
#include "RimCellIndexFilter.h"
#include "RimCellRangeFilter.h"
#include "RimPolygonFilter.h"
#include "RimUserDefinedFilter.h"
#include "RimUserDefinedIndexFilter.h"
#include "Polygons/RimPolygon.h"
#include "Polygons/RimPolygonCollection.h"
#include "RiaResultNames.h"
#include "RimEclipsePropertyFilter.h"
#include "RimEclipsePropertyFilterCollection.h"
#include "RimEclipseResultDefinition.h"
#include "RimTools.h"
#include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmFieldReorderCapability.h"
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmObjectScriptingCapability.h"
CAF_PDM_SOURCE_INIT( RimCombinedFilter, "CombinedFilter" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimCombinedFilter::RimCombinedFilter()
: RimCellFilter( FilterDefinitionType::INDEX )
{
CAF_PDM_InitScriptableObject( "Combined Filter", ":/CellFilter.png" );
setName( "Combined Filter" );
CAF_PDM_InitScriptableFieldNoDefault( &m_filters, "Filters", "Filters" );
caf::PdmFieldReorderCapability::addToField( &m_filters );
CAF_PDM_InitScriptableFieldNoDefault( &m_combineMode, "CombineMode", "Combine Mode" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimCombinedFilter::~RimCombinedFilter() = default;
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::setCase( RimCase* srcCase )
{
RimCellFilter::setCase( srcCase );
for ( RimCellFilter* child : m_filters )
{
if ( child ) child->setCase( srcCase );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimCombinedFilter::isFilterEnabled() const
{
if ( !isActive() ) return false;
for ( RimCellFilter* child : m_filters )
{
if ( child && child->isFilterEnabled() ) return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::onGridChanged()
{
for ( RimCellFilter* child : m_filters )
{
if ( child ) child->onGridChanged();
}
}
//--------------------------------------------------------------------------------------------------
/// Evaluate each enabled child to its own per-cell mask (each child respects its own
/// 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 )
{
if ( cellVisibility == nullptr || grid == nullptr ) return;
const size_t n = cellVisibility->size();
if ( n == 0 ) return;
std::vector<RimCellFilter*> enabledChildren;
for ( RimCellFilter* child : m_filters )
{
if ( child && child->isFilterEnabled() ) enabledChildren.push_back( child );
}
if ( enabledChildren.empty() ) return;
cvf::UByteArray combined( n );
combined.setAll( m_combineMode() == CombineMode::AND ? 1 : 0 );
for ( RimCellFilter* child : enabledChildren )
{
cvf::UByteArray childMask( n );
childMask.setAll( 1 );
child->applyToCellVisibility( &childMask, grid, timeStepIndex );
if ( m_combineMode() == CombineMode::AND )
{
for ( size_t i = 0; i < n; ++i )
combined[i] = combined[i] && childMask[i];
}
else
{
for ( size_t i = 0; i < n; ++i )
combined[i] = combined[i] || childMask[i];
}
}
const bool isInclude = ( filterMode() == INCLUDE );
for ( size_t i = 0; i < n; ++i )
{
if ( isInclude )
{
if ( !combined[i] ) ( *cellVisibility )[i] = 0;
}
else
{
if ( combined[i] ) ( *cellVisibility )[i] = 0;
}
}
}
//--------------------------------------------------------------------------------------------------
/// Bridge for the cell-filter collection's legacy index-based dispatch. Combined filters normally
/// live under the property-filter collection, but this override exists to keep the class usable if
/// a future caller places one in the cell-filter collection. Builds a per-cell mask by running
/// applyToCellVisibility against an all-visible scratch array, then folds the result into the
/// collection's include/exclude arrays respecting this filter's INCLUDE/EXCLUDE mode.
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::updateCellIndexFilter( cvf::UByteArray* includeVisibility, cvf::UByteArray* excludeVisibility, int gridIndex )
{
if ( includeVisibility == nullptr || excludeVisibility == nullptr ) return;
auto* sourceCase = m_srcCase();
if ( !sourceCase ) return;
const auto* structGrid = RigReservoirGridTools::gridByIndex( sourceCase, gridIndex );
const auto* grid = dynamic_cast<const RigGridBase*>( structGrid );
if ( grid == nullptr ) return;
const size_t n = includeVisibility->size();
if ( n == 0 ) return;
cvf::UByteArray scratch( n );
scratch.setAll( 1 );
applyToCellVisibility( &scratch, grid, 0 );
if ( filterMode() == INCLUDE )
{
for ( size_t i = 0; i < n; ++i )
if ( scratch[i] ) ( *includeVisibility )[i] = 1;
}
else
{
for ( size_t i = 0; i < n; ++i )
if ( !scratch[i] ) ( *excludeVisibility )[i] = 0;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimCombinedFilter::fullName() const
{
const QString modeStr = ( m_combineMode() == CombineMode::AND ) ? "AND" : "OR";
return QString( "%1 [%2: %3]" ).arg( name() ).arg( modeStr ).arg( m_filters.size() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::addFilter( RimCellFilter* child )
{
if ( !child ) return;
if ( wouldCreateCycle( child ) ) return;
m_filters.push_back( child );
child->setCase( m_srcCase() );
child->filterChanged.connect( this, &RimCombinedFilter::onChildFilterChanged );
updateConnectedEditors();
triggerFilterChanged();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::removeFilter( RimCellFilter* child )
{
if ( !child ) return;
m_filters.removeChild( child );
triggerFilterChanged();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimCellFilter*> RimCombinedFilter::filters() const
{
return m_filters.childrenByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimCellRangeFilter* RimCombinedFilter::addNewCellRangeFilter( RimCase* srcCase, int gridIndex, int sliceDirection, int defaultSlice )
{
auto* f = new RimCellRangeFilter();
addFilter( f );
f->setCase( srcCase );
f->setGridIndex( gridIndex );
f->setDefaultValues( sliceDirection, defaultSlice );
return f;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimPolygonFilter* RimCombinedFilter::addNewPolygonFilter( RimCase* srcCase, RimPolygon* polygon )
{
auto* f = new RimPolygonFilter();
addFilter( f );
f->setCase( srcCase );
f->setPolygon( polygon );
f->configurePolygonEditor();
if ( polygon )
{
f->enableFilter( true );
}
else
{
f->enablePicking( true );
}
return f;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimCellIndexFilter* RimCombinedFilter::addNewCellIndexFilter( RimCase* srcCase )
{
auto* f = new RimCellIndexFilter();
addFilter( f );
f->setCase( srcCase );
return f;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimUserDefinedFilter* RimCombinedFilter::addNewUserDefinedFilter( RimCase* srcCase )
{
auto* f = new RimUserDefinedFilter();
addFilter( f );
f->setCase( srcCase );
return f;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimUserDefinedIndexFilter* RimCombinedFilter::addNewUserDefinedIndexFilter( RimCase* srcCase, const std::vector<size_t>& defCellIndexes )
{
auto* f = new RimUserDefinedIndexFilter();
addFilter( f );
f->setCase( srcCase );
f->setCellIndexes( defCellIndexes );
return f;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimCombinedFilter* RimCombinedFilter::addNewCombinedFilter( RimCase* srcCase )
{
auto* f = new RimCombinedFilter();
addFilter( f );
f->setCase( srcCase );
return f;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::setCombineMode( CombineMode mode )
{
m_combineMode = mode;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimCombinedFilter::CombineMode RimCombinedFilter::combineMode() const
{
return m_combineMode();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( nameField() );
auto* group = uiOrdering.addNewGroup( "General" );
group->add( &m_filterMode );
group->add( &m_combineMode );
uiOrdering.skipRemainingFields( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName )
{
PdmObject::defineUiTreeOrdering( uiTreeOrdering, uiConfigName );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
updateIconState();
triggerFilterChanged();
notifyHostCollection();
}
//--------------------------------------------------------------------------------------------------
/// Called by CAF when a new object is pushed into m_filters (e.g. via our factory methods or via
/// drag-and-drop). Propagate so the host collection regenerates geometry.
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::onChildAdded( caf::PdmFieldHandle* /*containerForNewObject*/ )
{
triggerFilterChanged();
notifyHostCollection();
}
//--------------------------------------------------------------------------------------------------
/// Combined filters live only under the Eclipse property-filter collection, which triggers view
/// regen via an explicit updateDisplayModelNotifyManagedViews() call rather than by subscribing to
/// filterChanged. Call that API directly so our edits propagate.
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::notifyHostCollection()
{
if ( auto* propColl = firstAncestorOrThisOfType<RimEclipsePropertyFilterCollection>() )
{
propColl->updateDisplayModelNotifyManagedViews( nullptr );
}
}
//--------------------------------------------------------------------------------------------------
/// Called after reading from a project file. Re-wire child filterChanged signals so edits made to
/// deserialized children propagate up.
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::initAfterRead()
{
RimCellFilter::initAfterRead();
for ( RimCellFilter* child : m_filters )
{
if ( child ) child->filterChanged.connect( this, &RimCombinedFilter::onChildFilterChanged );
}
}
//--------------------------------------------------------------------------------------------------
/// Propagate a child's change up so the view re-renders.
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::onChildFilterChanged( const caf::SignalEmitter* /*emitter*/ )
{
triggerFilterChanged();
notifyHostCollection();
}
//--------------------------------------------------------------------------------------------------
/// Any enabled child that the combined filter would actually evaluate. Property-filter children
/// also need a loaded result; other cell filters are always evaluatable when enabled.
//--------------------------------------------------------------------------------------------------
bool RimCombinedFilter::hasActiveEvaluatableDescendant() const
{
for ( RimCellFilter* child : m_filters )
{
if ( !child || !child->isFilterEnabled() ) continue;
if ( auto* ep = dynamic_cast<RimEclipsePropertyFilter*>( child ) )
{
if ( ep->resultDefinition() && ep->resultDefinition()->hasResult() ) return true;
continue;
}
if ( auto* nested = dynamic_cast<RimCombinedFilter*>( child ) )
{
if ( nested->hasActiveEvaluatableDescendant() ) return true;
continue;
}
// Range, polygon, index, user-defined — evaluatable whenever enabled.
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------
/// True if any active descendant is an Eclipse property filter backed by a dynamic result. Drives
/// per-time-step cell-visibility cache invalidation in RimGridView.
//--------------------------------------------------------------------------------------------------
bool RimCombinedFilter::hasActiveDynamicPropertyDescendant() const
{
for ( RimCellFilter* child : m_filters )
{
if ( !child || !child->isFilterEnabled() ) continue;
if ( auto* ep = dynamic_cast<RimEclipsePropertyFilter*>( child ) )
{
if ( ep->resultDefinition() && ep->resultDefinition()->hasDynamicResult() ) return true;
}
else if ( auto* nested = dynamic_cast<RimCombinedFilter*>( child ) )
{
if ( nested->hasActiveDynamicPropertyDescendant() ) return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------
/// True if any active descendant is a formation-names property filter. Matches the collection-level
/// isUsingFormationNames() check.
//--------------------------------------------------------------------------------------------------
bool RimCombinedFilter::hasActiveFormationNamesPropertyDescendant() const
{
for ( RimCellFilter* child : m_filters )
{
if ( !child || !child->isFilterEnabled() ) continue;
if ( auto* ep = dynamic_cast<RimEclipsePropertyFilter*>( child ) )
{
auto* rd = ep->resultDefinition();
if ( rd && rd->resultType() == RiaDefines::ResultCatType::FORMATION_NAMES &&
rd->resultVariable() != RiaResultNames::undefinedResultName() )
return true;
}
else if ( auto* nested = dynamic_cast<RimCombinedFilter*>( child ) )
{
if ( nested->hasActiveFormationNamesPropertyDescendant() ) return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------
/// Right-click "add child" menu on the combined filter. Since combined filters only live under the
/// Eclipse property-filter collection, the menu always offers the full mixed-type set: property
/// filter, polygon, ranges, index, user-defined, and a nested combined filter.
//--------------------------------------------------------------------------------------------------
void RimCombinedFilter::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
{
menuBuilder << "RicEclipsePropertyFilterNewFeature";
menuBuilder << "Separator";
menuBuilder.subMenuStart( "Polygon Filter", QIcon( ":/CellFilter_Polygon.png" ) );
{
auto polygonCollection = RimTools::polygonCollection();
if ( 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";
menuBuilder << "RicNewRangeFilterSliceKFeature";
menuBuilder << "RicNewCellRangeFilterFeature";
menuBuilder.subMenuEnd();
menuBuilder << "RicNewCellIndexFilterFeature";
menuBuilder << "Separator";
menuBuilder << "RicNewUserDefinedFilterFeature";
menuBuilder << "RicNewUserDefinedIndexFilterFeature";
menuBuilder << "Separator";
menuBuilder << "RicEclipseCombinedPropertyFilterNewFeature";
}
//--------------------------------------------------------------------------------------------------
/// Refuse additions that would create a cycle: candidate must not be `this`, and if candidate is a
/// combined filter, `this` must not appear anywhere in its subtree.
//--------------------------------------------------------------------------------------------------
bool RimCombinedFilter::wouldCreateCycle( RimCellFilter* candidate ) const
{
if ( candidate == this ) return true;
auto* candidateCombined = dynamic_cast<RimCombinedFilter*>( candidate );
if ( !candidateCombined ) return false;
for ( RimCellFilter* grandchild : candidateCombined->filters() )
{
if ( grandchild == this ) return true;
if ( auto* gcCombined = dynamic_cast<RimCombinedFilter*>( grandchild ) )
{
if ( wouldCreateCycle( gcCombined ) ) return true;
}
}
return false;
}
@@ -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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimCellFilter.h"
#include "RimCellFilterCollection.h"
#include "cafAppEnum.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmField.h"
class RimCellIndexFilter;
class RimCellRangeFilter;
class RimPolygon;
class RimPolygonFilter;
class RimUserDefinedFilter;
class RimUserDefinedIndexFilter;
//==================================================================================================
/// A filter whose result is a boolean combination (AND / OR) of its child filters' results.
/// Child filters are evaluated to per-cell masks (each respecting its own INCLUDE/EXCLUDE mode),
/// then combined. The combined filter's own INCLUDE/EXCLUDE mode is applied to the output.
/// Nesting is supported — a combined filter may contain other combined filters. Lives only inside
/// RimEclipsePropertyFilterCollection; that's the one collection whose evaluation pipeline supplies
/// a time step, which is required for property-filter children.
//==================================================================================================
class RimCombinedFilter : public RimCellFilter
{
CAF_PDM_HEADER_INIT;
public:
using CombineMode = RimCellFilterCollection::CombineFilterModeType;
RimCombinedFilter();
~RimCombinedFilter() override;
void setCase( RimCase* srcCase ) override;
bool isFilterEnabled() const override;
void onGridChanged() override;
void applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t timeStepIndex ) 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.
void updateCompundFilter( cvf::CellRangeFilter*, int ) override {}
void updateCellIndexFilter( cvf::UByteArray* includeVisibility, cvf::UByteArray* excludeVisibility, int gridIndex ) override;
QString fullName() const override;
void addFilter( RimCellFilter* child );
void removeFilter( RimCellFilter* child );
std::vector<RimCellFilter*> filters() const;
// Recursive introspection helpers — used by the property-filter collection so that a combined
// filter sitting in that collection can report whether it has evaluation-worthy descendants.
bool hasActiveEvaluatableDescendant() const;
bool hasActiveDynamicPropertyDescendant() const;
bool hasActiveFormationNamesPropertyDescendant() const;
// Typed factory methods — mirror the ones on RimCellFilterCollection so features can add
// children of the correct type directly.
RimCellRangeFilter* addNewCellRangeFilter( RimCase* srcCase, int gridIndex, int sliceDirection = -1, int defaultSlice = -1 );
RimPolygonFilter* addNewPolygonFilter( RimCase* srcCase, RimPolygon* polygon );
RimCellIndexFilter* addNewCellIndexFilter( RimCase* srcCase );
RimUserDefinedFilter* addNewUserDefinedFilter( RimCase* srcCase );
RimUserDefinedIndexFilter* addNewUserDefinedIndexFilter( RimCase* srcCase, const std::vector<size_t>& defCellIndexes = {} );
RimCombinedFilter* addNewCombinedFilter( RimCase* srcCase );
void setCombineMode( CombineMode mode );
CombineMode combineMode() const;
protected:
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void initAfterRead() override;
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
void onChildAdded( caf::PdmFieldHandle* containerForNewObject ) override;
private:
bool wouldCreateCycle( RimCellFilter* candidate ) const;
void onChildFilterChanged( const caf::SignalEmitter* emitter );
// Combined filters only sit inside RimEclipsePropertyFilterCollection, which doesn't use
// filterChanged signals — it requires an explicit updateDisplayModelNotifyManagedViews() call
// to trigger a view regen. This helper performs that call.
void notifyHostCollection();
caf::PdmChildArrayField<RimCellFilter*> m_filters;
caf::PdmField<caf::AppEnum<CombineMode>> m_combineMode;
};
@@ -30,6 +30,9 @@
#include "RigEclipseResultAddress.h"
#include "RigFlowDiagResults.h"
#include "RigFormationNames.h"
#include "RigGridBase.h"
#include "RigResultAccessor.h"
#include "RigResultAccessorFactory.h"
#include "RimEclipseCase.h"
#include "RimEclipsePropertyFilterCollection.h"
@@ -698,6 +701,93 @@ void RimEclipsePropertyFilter::initAfterRead()
updateIconState();
}
//--------------------------------------------------------------------------------------------------
/// Evaluate this property filter against the incoming cell visibility mask. Only cells that
/// are currently visible are considered; non-matching cells are flipped to invisible according
/// to the INCLUDE/EXCLUDE mode. This is a generic hook the renderer uses to treat every filter
/// uniformly; the body is extracted from the former inline loop in
/// RivReservoirViewPartMgr::computePropertyVisibility.
//--------------------------------------------------------------------------------------------------
void RimEclipsePropertyFilter::applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t timeStepIndex )
{
if ( cellVisibility == nullptr || grid == nullptr ) return;
if ( !isActive() || !resultDefinition()->hasResult() ) return;
resultDefinition()->loadResult();
auto* container = parentContainer();
if ( !container ) return;
auto* view = container->reservoirView();
if ( !view || !view->eclipseCase() ) return;
RigEclipseCaseData* eclipseCase = view->eclipseCase()->eclipseCaseData();
if ( !eclipseCase ) return;
cvf::ref<RigResultAccessor> resultAccessor =
RigResultAccessorFactory::createFromResultDefinition( eclipseCase, grid->gridIndex(), timeStepIndex, resultDefinition() );
CVF_ASSERT( resultAccessor.notNull() );
const RimCellFilter::FilterModeType filterType = filterMode();
if ( isCategorySelectionActive() )
{
std::vector<int> integerVector = selectedCategoryValues();
std::set<int> integerSet( integerVector.begin(), integerVector.end() );
for ( int cellIndex = 0; cellIndex < static_cast<int>( grid->cellCount() ); cellIndex++ )
{
if ( ( *cellVisibility )[cellIndex] )
{
size_t resultValueIndex = cellIndex;
double scalarValue = resultAccessor->cellScalar( resultValueIndex );
if ( integerSet.find( static_cast<int>( scalarValue ) ) != integerSet.end() )
{
if ( filterType == RimCellFilter::EXCLUDE )
{
( *cellVisibility )[cellIndex] = false;
}
}
else
{
if ( filterType == RimCellFilter::INCLUDE )
{
( *cellVisibility )[cellIndex] = false;
}
}
}
}
}
else
{
double lowerBound = 0.0;
double upperBound = 0.0;
rangeValues( &lowerBound, &upperBound );
for ( int cellIndex = 0; cellIndex < static_cast<int>( grid->cellCount() ); cellIndex++ )
{
if ( ( *cellVisibility )[cellIndex] )
{
size_t resultValueIndex = cellIndex;
double scalarValue = resultAccessor->cellScalar( resultValueIndex );
if ( lowerBound <= scalarValue && scalarValue <= upperBound )
{
if ( filterType == RimCellFilter::EXCLUDE )
{
( *cellVisibility )[cellIndex] = false;
}
}
else
{
if ( filterType == RimCellFilter::INCLUDE )
{
( *cellVisibility )[cellIndex] = false;
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -57,6 +57,8 @@ public:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void initAfterRead() override;
void applyToCellVisibility( cvf::UByteArray* cellVisibility, const RigGridBase* grid, size_t timeStepIndex ) override;
void updateUiFieldsFromActiveResult();
std::map<QString, std::vector<caf::PdmFieldHandle*>> quickAccessFields() override;
@@ -22,6 +22,9 @@
#include "RiaResultNames.h"
#include "RimCellFilter.h"
#include "RimCombinedFilter.h"
#include "RimEclipseCase.h"
#include "RimEclipseCellColors.h"
#include "RimEclipsePropertyFilter.h"
#include "RimEclipseResultDefinition.h"
@@ -57,7 +60,7 @@ RimEclipseView* RimEclipsePropertyFilterCollection::reservoirView()
//--------------------------------------------------------------------------------------------------
void RimEclipsePropertyFilterCollection::setIsDuplicatedFromLinkedView()
{
for ( RimEclipsePropertyFilter* propertyFilter : m_propertyFilters )
for ( RimEclipsePropertyFilter* propertyFilter : propertyFilters() )
{
propertyFilter->setIsDuplicatedFromLinkedView( true );
}
@@ -68,23 +71,36 @@ void RimEclipsePropertyFilterCollection::setIsDuplicatedFromLinkedView()
//--------------------------------------------------------------------------------------------------
std::vector<RimEclipsePropertyFilter*> RimEclipsePropertyFilterCollection::propertyFilters() const
{
return m_propertyFilters.childrenByType();
std::vector<RimEclipsePropertyFilter*> typed;
for ( RimCellFilter* f : m_propertyFilters )
{
if ( auto* ep = dynamic_cast<RimEclipsePropertyFilter*>( f ) ) typed.push_back( ep );
}
return typed;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmChildArrayField<RimEclipsePropertyFilter*>& RimEclipsePropertyFilterCollection::propertyFiltersField()
caf::PdmChildArrayField<RimCellFilter*>& RimEclipsePropertyFilterCollection::propertyFiltersField()
{
return m_propertyFilters;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimCellFilter*> RimEclipsePropertyFilterCollection::filtersForEvaluation() const
{
return m_propertyFilters.childrenByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEclipsePropertyFilterCollection::loadAndInitializePropertyFilters()
{
for ( RimEclipsePropertyFilter* propertyFilter : m_propertyFilters )
for ( RimEclipsePropertyFilter* propertyFilter : propertyFilters() )
{
propertyFilter->resultDefinition()->setEclipseCase( reservoirView()->eclipseCase() );
propertyFilter->initAfterRead();
@@ -111,24 +127,47 @@ bool RimEclipsePropertyFilterCollection::hasActiveFilters() const
{
if ( !isActive ) return false;
for ( RimEclipsePropertyFilter* propertyFilter : m_propertyFilters )
for ( RimCellFilter* filter : filtersForEvaluation() )
{
if ( propertyFilter->isActive() && propertyFilter->resultDefinition()->hasResult() ) return true;
if ( !filter || !filter->isFilterEnabled() ) continue;
if ( auto* ep = dynamic_cast<RimEclipsePropertyFilter*>( filter ) )
{
if ( ep->resultDefinition() && ep->resultDefinition()->hasResult() ) return true;
continue;
}
if ( auto* comp = dynamic_cast<RimCombinedFilter*>( filter ) )
{
if ( comp->hasActiveEvaluatableDescendant() ) return true;
continue;
}
// Any other cell-filter subtype placed in this collection is evaluatable when enabled.
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------
/// Returns whether any of the active property filters are based on a dynamic result
/// Returns whether any of the active property filters are based on a dynamic result. Includes
/// property filters nested inside compound filters so time-step updates invalidate correctly.
//--------------------------------------------------------------------------------------------------
bool RimEclipsePropertyFilterCollection::hasActiveDynamicFilters() const
{
if ( !isActive ) return false;
for ( RimEclipsePropertyFilter* propertyFilter : m_propertyFilters )
for ( RimCellFilter* filter : filtersForEvaluation() )
{
if ( propertyFilter->isActive() && propertyFilter->resultDefinition()->hasDynamicResult() ) return true;
if ( !filter || !filter->isFilterEnabled() ) continue;
if ( auto* ep = dynamic_cast<RimEclipsePropertyFilter*>( filter ) )
{
if ( ep->resultDefinition() && ep->resultDefinition()->hasDynamicResult() ) return true;
}
else if ( auto* comp = dynamic_cast<RimCombinedFilter*>( filter ) )
{
if ( comp->hasActiveDynamicPropertyDescendant() ) return true;
}
}
return false;
@@ -141,11 +180,21 @@ bool RimEclipsePropertyFilterCollection::isUsingFormationNames() const
{
if ( !isActive ) return false;
for ( RimEclipsePropertyFilter* propertyFilter : m_propertyFilters )
for ( RimCellFilter* filter : filtersForEvaluation() )
{
if ( propertyFilter->isActive() && propertyFilter->resultDefinition()->resultType() == RiaDefines::ResultCatType::FORMATION_NAMES &&
propertyFilter->resultDefinition()->resultVariable() != RiaResultNames::undefinedResultName() )
return true;
if ( !filter || !filter->isFilterEnabled() ) continue;
if ( auto* ep = dynamic_cast<RimEclipsePropertyFilter*>( filter ) )
{
auto* rd = ep->resultDefinition();
if ( rd && rd->resultType() == RiaDefines::ResultCatType::FORMATION_NAMES &&
rd->resultVariable() != RiaResultNames::undefinedResultName() )
return true;
}
else if ( auto* comp = dynamic_cast<RimCombinedFilter*>( filter ) )
{
if ( comp->hasActiveFormationNamesPropertyDescendant() ) return true;
}
}
return false;
@@ -175,7 +224,7 @@ void RimEclipsePropertyFilterCollection::updateIconState()
updateUiIconFromState( activeIcon );
for ( RimEclipsePropertyFilter* cellFilter : m_propertyFilters )
for ( RimEclipsePropertyFilter* cellFilter : propertyFilters() )
{
cellFilter->updateActiveState();
cellFilter->updateIconState();
@@ -187,7 +236,7 @@ void RimEclipsePropertyFilterCollection::updateIconState()
//--------------------------------------------------------------------------------------------------
void RimEclipsePropertyFilterCollection::updateFromCurrentTimeStep()
{
for ( RimEclipsePropertyFilter* cellFilter : m_propertyFilters() )
for ( RimEclipsePropertyFilter* cellFilter : propertyFilters() )
{
cellFilter->updateFromCurrentTimeStep();
}
@@ -202,7 +251,7 @@ void RimEclipsePropertyFilterCollection::updateDefaultResult( const RimEclipseCe
if ( m_propertyFilters.empty() ) return;
auto view = reservoirView();
for ( auto filter : m_propertyFilters )
for ( RimEclipsePropertyFilter* filter : propertyFilters() )
{
if ( !filter->isLinkedWithCellResult() ) continue;
@@ -243,4 +292,24 @@ void RimEclipsePropertyFilterCollection::appendMenuItems( caf::CmdFeatureMenuBui
{
menuBuilder << "RicEclipsePropertyFilterNewFeature";
menuBuilder << "RicAddLinkedEclipsePropertyFilterFeature";
menuBuilder << "Separator";
menuBuilder << "RicEclipseCombinedPropertyFilterNewFeature";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimCombinedFilter* RimEclipsePropertyFilterCollection::addNewCombinedFilter()
{
auto* combined = new RimCombinedFilter();
auto view = reservoirView();
if ( view && view->eclipseCase() )
{
combined->setCase( view->eclipseCase() );
}
m_propertyFilters.push_back( combined );
updateConnectedEditors();
return combined;
}
@@ -24,6 +24,8 @@
#include "cafPdmChildArrayField.h"
class RimCellFilter;
class RimCombinedFilter;
class RimEclipsePropertyFilter;
class RimEclipseView;
class RimEclipseCellColors;
@@ -42,8 +44,12 @@ public:
RimEclipseView* reservoirView();
void setIsDuplicatedFromLinkedView();
std::vector<RimEclipsePropertyFilter*> propertyFilters() const;
caf::PdmChildArrayField<RimEclipsePropertyFilter*>& propertyFiltersField();
std::vector<RimEclipsePropertyFilter*> propertyFilters() const;
caf::PdmChildArrayField<RimCellFilter*>& propertyFiltersField();
// Returns all children as RimCellFilter*, including compound filters. Used by the
// visualization pipeline to call applyToCellVisibility() uniformly.
std::vector<RimCellFilter*> filtersForEvaluation() const;
bool hasActiveFilters() const override;
bool hasActiveDynamicFilters() const override;
@@ -56,11 +62,12 @@ public:
void updateDefaultResult( const RimEclipseCellColors* result );
RimEclipsePropertyFilter* addFilterLinkedToCellResult();
RimCombinedFilter* addNewCombinedFilter();
protected:
void initAfterRead() override;
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
private:
caf::PdmChildArrayField<RimEclipsePropertyFilter*> m_propertyFilters;
caf::PdmChildArrayField<RimCellFilter*> m_propertyFilters;
};