Proposal: Filter Factory
VTK often has multiple filters that perform the same logical operation. There are several reasons why these multiple versions exist; one common one being that they are intended for different input data types. In ParaView, we often don't like to expose each individual VTK filter implementation as a separate filter in the application, instead we like to group them together into a meta-filter. We put together a vtkPV...
class that chooses in the correct implementation based on the input dataset type (or other criteria) at runtime.
There are several issues with such meta filters:
- they are cumbersome to write and annoying to debug
- since all internal filters don't often share exactly the same public interface, we end up with proxies that have properties that only make sense for a specific input type. While we often hide them from the UI, the Python trace/state ends up looking ugly and confusing. e.g. here's a python trace generated from the Slice filter:
# create a new 'Slice'
slice1 = Slice(Input=canex2)
slice1.SliceType = 'Plane'
slice1.HyperTreeGridSlicer = 'Plane' #<--- this is extraneous
slice1.SliceOffsetValues = [0.0]
- they are not extensible by plugins.
This issue proposes a new mechanism to handle such use-cases.
Concept: Filter Factory
Similar to reader/writer factories, we introduce a filter factory to create new filters. Any UI component like common filters toolbar, filter menu will not directly create a filter proxy based on its name instead use the filter factory to create a appropriate proxy for the given input(s). Similarly with Python API, request to create a filter for example Slice(Input=..)
will use the filter factory.
Implementation
To help explain the implementation, let's consider the "Slice" filter and say we have two internal implementations "DataSetSlice" and "HyperTreeGridSlice" to pick from based on the input type.
We introduce a new type of vtkSMSourceProxy
subclass, called vtkSMSourceProxyFactory
. We use this new proxy to define a class of filters e.g. for the Slice filter, this can be done in the XML definition as follows:
<ProxyGroup name="filters">
<SourceProxyFactory name="Slice">
</SourceProxyFactory>
</ProxyGroup>
Concrete types can be defined as regular filter proxies which a special hint as follows:
<ProxyGroup name="filters">
<SourceProxy name="DataSetSlice" ...>
<InputProperty .. />
<Hints>
<Factory group="filters" type="Slice" />
</Hints>
</SourceProxy>
<SourceProxy name="HyperTreeGridSlice" ...>
<InputProperty .. />
<Hints>
<Factory group="filters" type="Slice" />
</Hints>
</SourceProxy>
</ProxyGroup>
When (filters, Slice)
proxy is created, it scans for all proxies for appropriate hints and creates them as subproxies.
For every InputProperty
with a unique name, an InputProperty is created on with a domain that is a boolean OR of all input property domains from the subproxies.
As far as the application is concerned, post-creation, "Slice" will have an Input property with a domain that is an OR of the domains from the Input property on DataSetSlice and HypertreeGridSlice. Thus, the application can use standard logic to set input property and check is-in-domain to see if the filter is applicable for the given input.
Then, during vtkSMParaViewPipelineController::PostInitializeProxy
, which is called after the Input has been set, the vtkSMSourceProxyFactory which "activate" one of its subproxies by finding one that accepts the input specified and then exposing all properties on that subproxy alone (except Input properties that were already exposed). Thus the public interface of the proxy gets defined in PostInitializeProxy call and is limited to the chosen internal filter proxy. This is the stage where vtkSMSourceProxyFactory makes it so that its output ports are simply the output ports of the activated sub-proxy. Thus, from this point on, this acts as if the activated subproxy was the one that application explicitly created.