diff --git a/README b/README
deleted file mode 100644
index 0e8a7211e1eb317758e52d65a7cb9db00ebdaff3..0000000000000000000000000000000000000000
--- a/README
+++ /dev/null
@@ -1,22 +0,0 @@
-What:
-
-    Library and function object for embedding the ParaView Catalyst
-    into OpenFOAM.
-
-Requirements:
-
-    OpenFOAM.com development version (April-2018 or newer) or OpenFOAM-v1806.
-
-    ParaView or ParaView Catalyst 5.4 or newer, compiled with mpi and
-    python support.
-
-Authors:
-
-    Mark Olesen <Mark.Olesen@esi-group.com>
-    Simone Bna <Simone.Bna@cineca.it>
-
-License:
-
-    Same terms as OpenFOAM.
-    Licensed under GNU General Public License <http://www.gnu.org/licenses/>.
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7b8b247110b33f97dd591c1f1ce36b255ef10799
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+## What
+
+Library and function object for embedding ParaView Catalyst into OpenFOAM.
+
+## Requirements
+
+1. OpenFOAM.com development version (11-May-2018 or newer) or OpenFOAM-v1806.
+2. ParaView or ParaView Catalyst 5.5 or newer, compiled with mpi and
+python support.
+
+It is highly recommended to patch the ParaView 5.5 sources (eg,
+using the OpenFOAM ThirdParty makeParaView script) to ensure that
+they properly handle outputting results in directories other than
+the main simulation directory:
+
+* [MR2433]
+* [MR2436]
+
+
+## Authors
+
+* Mark Olesen <Mark.Olesen@esi-group.com>
+* Simone Bna <Simone.Bna@cineca.it>
+
+
+## License
+
+Same terms as OpenFOAM.
+Licensed under GNU General Public License <http://www.gnu.org/licenses/>.
+
+
+[MR2433]: https://gitlab.kitware.com/paraview/paraview/merge_requests/2433
+[MR2436]: https://gitlab.kitware.com/paraview/paraview/merge_requests/2436
+
diff --git a/etc/allinputsgridwriter.py b/etc/allinputsgridwriter.py
index fcd0c77826cb0365547fd59f5b842ac3d42bc9d6..46354d9aff453b4d807529d71f13919dc324de49 100644
--- a/etc/allinputsgridwriter.py
+++ b/etc/allinputsgridwriter.py
@@ -10,7 +10,6 @@ def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
       for i in range(datadescription.GetNumberOfInputDescriptions()):
-        inputdescription = datadescription.GetInputDescription(i)
         name = datadescription.GetInputDescriptionName(i)
         adaptorinput = coprocessor.CreateProducer(datadescription, name)
         grid = adaptorinput.GetClientSideObject().GetOutputDataObject(0)
diff --git a/etc/area.cfg b/etc/area.cfg
deleted file mode 100644
index 2f6850f99531553441118e215c8bd3eca5c86142..0000000000000000000000000000000000000000
--- a/etc/area.cfg
+++ /dev/null
@@ -1,16 +0,0 @@
-/*--------------------------------*- C++ -*----------------------------------*\
-| =========                 |                                                 |
-| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  plus                                  |
-|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
-|    \\/     M anipulation  |                                                 |
-\*---------------------------------------------------------------------------*/
-// Insitu processing of finiteArea fields with ParaView Catalyst
-
-type            catalyst::area;
-libs            ("libcatalystFoam.so");
-
-executeControl  timeStep;
-writeControl    none;
-
-// ************************************************************************* //
diff --git a/etc/cloud.cfg b/etc/cloud.cfg
deleted file mode 100644
index d860e901a9003aa5716b796653b2c0e9ae1e19f1..0000000000000000000000000000000000000000
--- a/etc/cloud.cfg
+++ /dev/null
@@ -1,16 +0,0 @@
-/*--------------------------------*- C++ -*----------------------------------*\
-| =========                 |                                                 |
-| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  plus                                  |
-|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
-|    \\/     M anipulation  |                                                 |
-\*---------------------------------------------------------------------------*/
-// Insitu processing of lagrangian clouds with ParaView Catalyst
-
-type            catalyst::cloud;
-libs            ("libcatalystFoam.so");
-
-executeControl  timeStep;
-writeControl    none;
-
-// ************************************************************************* //
diff --git a/etc/default.cfg b/etc/default.cfg
deleted file mode 100644
index e9ace6b706c1c55b456b01faf4a564c82d13bce9..0000000000000000000000000000000000000000
--- a/etc/default.cfg
+++ /dev/null
@@ -1,16 +0,0 @@
-/*--------------------------------*- C++ -*----------------------------------*\
-| =========                 |                                                 |
-| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  plus                                  |
-|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
-|    \\/     M anipulation  |                                                 |
-\*---------------------------------------------------------------------------*/
-// Insitu processing of finiteVolume fields with ParaView Catalyst
-
-type            catalyst;
-libs            ("libcatalystFoam.so");
-
-executeControl  timeStep;
-writeControl    none;
-
-// ************************************************************************* //
diff --git a/etc/printChannels.py b/etc/printChannels.py
deleted file mode 100644
index 8fcb4472e7cf15c9e3db16f474c7a6fea2a80892..0000000000000000000000000000000000000000
--- a/etc/printChannels.py
+++ /dev/null
@@ -1,66 +0,0 @@
-from paraview.simple import *
-from paraview import coprocessing
-
-# The frequency to output everything
-outputfrequency = 1
-
-# ----------------------- CoProcessor definition -----------------------
-
-def CreateCoProcessor():
-  def _CreatePipeline(coprocessor, datadescription):
-    class Pipeline:
-      for i in range(datadescription.GetNumberOfInputDescriptions()):
-        name = datadescription.GetInputDescriptionName(i)
-        adaptorinput = coprocessor.CreateProducer(datadescription, name)
-        grid = adaptorinput.GetClientSideObject().GetOutputDataObject(0)
-        print "Channel <" + name + "> is a ", grid.GetClassName()
-
-    return Pipeline()
-
-  class CoProcessor(coprocessing.CoProcessor):
-    def CreatePipeline(self, datadescription):
-      self.Pipeline = _CreatePipeline(self, datadescription)
-
-  return CoProcessor()
-
-#--------------------------------------------------------------
-# Global variables that will hold the pipeline for each timestep
-# Creating the CoProcessor object, doesn't actually create the ParaView pipeline.
-# It will be automatically setup when coprocessor.UpdateProducers() is called the
-# first time.
-coprocessor = CreateCoProcessor()
-
-#--------------------------------------------------------------
-# Enable Live-Visualizaton with ParaView
-coprocessor.EnableLiveVisualization(False)
-
-
-# ---------------------- Data Selection method ----------------------
-
-def RequestDataDescription(datadescription):
-    "Callback to populate the request for current timestep"
-    global coprocessor
-    if datadescription.GetForceOutput() == True or datadescription.GetTimeStep() % outputfrequency == 0:
-        # We are just going to request all fields and meshes from the simulation
-        # code/adaptor.
-        for i in range(datadescription.GetNumberOfInputDescriptions()):
-            datadescription.GetInputDescription(i).AllFieldsOn()
-            datadescription.GetInputDescription(i).GenerateMeshOn()
-        return
-
-    # setup requests for all inputs based on the requirements of the
-    # pipeline.
-    coprocessor.LoadRequestedData(datadescription)
-
-# ------------------------ Processing method ------------------------
-
-def DoCoProcessing(datadescription):
-    "Callback to do co-processing for current timestep"
-    global coprocessor
-
-    # Update the coprocessor by providing it the newly generated simulation data.
-    # If the pipeline hasn't been setup yet, this will setup the pipeline.
-    coprocessor.UpdateProducers(datadescription)
-
-    # Write output data, if appropriate.
-    coprocessor.WriteData(datadescription);
diff --git a/etc/writeArea.py b/etc/writeArea.py
deleted file mode 100644
index 1911a9da9fc2d9cd404cbe5cb0fdb9cff5290295..0000000000000000000000000000000000000000
--- a/etc/writeArea.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from paraview.simple import *
-from paraview import coprocessing
-
-# ----------------------- CoProcessor definition -----------------------
-
-def CreateCoProcessor():
-  def _CreatePipeline(coprocessor, datadescription):
-    class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'input')
-      writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
-
-      coprocessor.RegisterWriter(writer1, filename='area_%t.vtm', freq=2)
-
-    return Pipeline()
-
-  class CoProcessor(coprocessing.CoProcessor):
-    def CreatePipeline(self, datadescription):
-      self.Pipeline = _CreatePipeline(self, datadescription)
-
-  coprocessor = CoProcessor()
-  freqs = {'input': [10]}
-  coprocessor.SetUpdateFrequencies(freqs)
-  return coprocessor
-
-#--------------------------------------------------------------
-# Global variables that will hold the pipeline for each timestep
-# Creating the CoProcessor object, doesn't actually create the ParaView pipeline.
-# It will be automatically setup when coprocessor.UpdateProducers() is called the
-# first time.
-coprocessor = CreateCoProcessor()
-
-#--------------------------------------------------------------
-# Enable Live-Visualizaton with ParaView
-coprocessor.EnableLiveVisualization(True)
-
-# ---------------------- Data Selection method ----------------------
-
-def RequestDataDescription(datadescription):
-    'Callback to populate the request for current timestep'
-    global coprocessor
-    if datadescription.GetForceOutput() == True:
-        # We are just going to request all fields and meshes from the simulation
-        # code/adaptor.
-        for i in range(datadescription.GetNumberOfInputDescriptions()):
-            datadescription.GetInputDescription(i).AllFieldsOn()
-            datadescription.GetInputDescription(i).GenerateMeshOn()
-        return
-
-    # setup requests for all inputs based on the requirements of the
-    # pipeline.
-    coprocessor.LoadRequestedData(datadescription)
-
-# ------------------------ Processing method ------------------------
-
-def DoCoProcessing(datadescription):
-    'Callback to do co-processing for current timestep'
-    global coprocessor
-
-    # Update the coprocessor by providing it the newly generated simulation data.
-    # If the pipeline hasn't been setup yet, this will setup the pipeline.
-    coprocessor.UpdateProducers(datadescription)
-
-    # Write output data, if appropriate.
-    coprocessor.WriteData(datadescription);
-
-    # Write image capture (Last arg: rescale lookup table), if appropriate.
-    coprocessor.WriteImages(datadescription, rescale_lookuptable=False)
-
-    # Live Visualization, if enabled.
-    coprocessor.DoLiveVisualization(datadescription, 'localhost', 22222)
diff --git a/etc/writeCloud.py b/etc/writeCloud.py
deleted file mode 100644
index db348ea49c18683d31f030958c1237c52921fdb6..0000000000000000000000000000000000000000
--- a/etc/writeCloud.py
+++ /dev/null
@@ -1,72 +0,0 @@
-from paraview.simple import *
-from paraview import coprocessing
-
-# ----------------------- CoProcessor definition -----------------------
-
-def CreateCoProcessor():
-  def _CreatePipeline(coprocessor, datadescription):
-    class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'cloud')
-      writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
-
-      # register writer with coprocessor, with filename + output freq
-      coprocessor.RegisterWriter(writer1, filename='cloud_%t.vtm', freq=10)
-
-    return Pipeline()
-
-  class CoProcessor(coprocessing.CoProcessor):
-    def CreatePipeline(self, datadescription):
-      self.Pipeline = _CreatePipeline(self, datadescription)
-
-  coprocessor = CoProcessor()
-  freqs = {'cloud': [10]}
-  coprocessor.SetUpdateFrequencies(freqs)
-  return coprocessor
-
-#--------------------------------------------------------------
-# Global variables that will hold the pipeline for each timestep
-# Creating the CoProcessor object, doesn't actually create the ParaView pipeline.
-# It will be automatically setup when coprocessor.UpdateProducers() is called the
-# first time.
-coprocessor = CreateCoProcessor()
-
-#--------------------------------------------------------------
-# Enable Live-Visualizaton with ParaView
-coprocessor.EnableLiveVisualization(True)
-
-
-# ---------------------- Data Selection method ----------------------
-
-def RequestDataDescription(datadescription):
-    'Callback to populate the request for current timestep'
-    global coprocessor
-    if datadescription.GetForceOutput() == True:
-        # We are just going to request all fields and meshes from the simulation
-        # code/adaptor.
-        for i in range(datadescription.GetNumberOfInputDescriptions()):
-            datadescription.GetInputDescription(i).AllFieldsOn()
-            datadescription.GetInputDescription(i).GenerateMeshOn()
-        return
-
-    # setup requests for all inputs based on the requirements of the
-    # pipeline.
-    coprocessor.LoadRequestedData(datadescription)
-
-# ------------------------ Processing method ------------------------
-
-def DoCoProcessing(datadescription):
-    'Callback to do co-processing for current timestep'
-    global coprocessor
-
-    # Update the coprocessor by providing it the newly generated simulation data.
-    # If the pipeline hasn't been setup yet, this will setup the pipeline.
-    coprocessor.UpdateProducers(datadescription)
-
-    # Write output data, if appropriate.
-    coprocessor.WriteData(datadescription);
-
-    # Write image capture (Last arg: rescale lookup table), if appropriate.
-    coprocessor.WriteImages(datadescription, rescale_lookuptable=False)
-
-    # Live Visualization, if enabled.
-    coprocessor.DoLiveVisualization(datadescription, 'localhost', 22222)
diff --git a/src/catalyst/CMakeLists-Common.txt b/src/catalyst/CMakeLists-Common.txt
index 4ee2eef7b5cddfed78ff1d5ff72dd70635385bbb..341fed060b61bb8b3b132d95aa48bd5a13467f7c 100644
--- a/src/catalyst/CMakeLists-Common.txt
+++ b/src/catalyst/CMakeLists-Common.txt
@@ -63,6 +63,9 @@ set(LIBRARY_OUTPUT_PATH $ENV{FOAM_LIBBIN}
 
 file(GLOB SOURCE_FILES
     catalystCoprocess.C
+    catalystTools.C
+    catalystInput.C
+    catalystFunctionObject.C
 
     cloud/catalystCloud.C
     cloud/foamVtkCloudAdaptor.C
diff --git a/src/catalyst/areaMesh/catalystFaMesh.C b/src/catalyst/areaMesh/catalystFaMesh.C
index 02f8760df165f137627db3a38afa504b6b464ad7..94406f2a78455fe17fe8af2af7caa6b1d86cfb69 100644
--- a/src/catalyst/areaMesh/catalystFaMesh.C
+++ b/src/catalyst/areaMesh/catalystFaMesh.C
@@ -24,12 +24,10 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "catalystFaMesh.H"
-#include "catalystCoprocess.H"
 #include "addToRunTimeSelectionTable.H"
 #include "faMesh.H"
 #include "fvMesh.H"
 
-#include <vtkNew.h>
 #include <vtkCPDataDescription.h>
 #include <vtkMultiBlockDataSet.h>
 #include <vtkInformation.h>
@@ -38,76 +36,39 @@ License
 
 namespace Foam
 {
-namespace functionObjects
+namespace catalyst
 {
-    defineTypeNameAndDebug(catalystFaMesh, 0);
-    addToRunTimeSelectionTable(functionObject, catalystFaMesh, dictionary);
+    defineTypeNameAndDebug(faMeshInput, 0);
+    addNamedToRunTimeSelectionTable
+    (
+        catalystInput,
+        faMeshInput,
+        dictionary,
+        area
+    );
 }
 }
 
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-bool Foam::functionObjects::catalystFaMesh::readBasics(const dictionary& dict)
+void Foam::catalyst::faMeshInput::update()
 {
-    int debugLevel = 0;
-    if (dict.readIfPresent("debug", debugLevel))
-    {
-        catalystCoprocess::debug = debugLevel;
-    }
+    // Backend requires a corresponding mesh
+    backends_.filterKeys
+    (
+        [this](const word& k){ return meshes_.found(k); }
+    );
 
-    if (Pstream::master())
+    forAllConstIters(meshes_, iter)
     {
-        fileName dir;
-        if (dict.readIfPresent("mkdir", dir))
+        if (!backends_.found(iter.key()))
         {
-            dir.expand();
-            dir.clean();
-        }
-        Foam::mkDir(dir);
-    }
-
-    dict.readIfPresent("outputDir", outputDir_);
-    outputDir_.expand();
-    outputDir_.clean();
-    if (Pstream::master())
-    {
-        Foam::mkDir(outputDir_);
-    }
-
-    dict.lookup("scripts") >> scripts_;         // Python scripts
-    catalystCoprocess::expand(scripts_, dict);  // Expand and check availability
-
-    if (adaptor_.valid())
-    {
-        // Run-time modification of pipeline
-        adaptor_().reset(outputDir_, scripts_);
-    }
-
-    return true;
-}
-
-
-void Foam::functionObjects::catalystFaMesh::updateState
-(
-    polyMesh::readUpdateState state
-)
-{
-    // Trigger change of state
-
-    // Be really paranoid and verify if the mesh actually exists
-    const wordList regionNames(backends_.toc());
-
-    for (const word& regionName : regionNames)
-    {
-        if (meshes_.found(regionName) && time_.found(regionName))
-        {
-            backends_[regionName]->updateState(state);
-        }
-        else
-        {
-            backends_.erase(regionName);
-            meshes_.erase(regionName);
+            backends_.insert
+            (
+                iter.key(),
+                new Foam::vtk::faMeshAdaptor(*(iter.object()))
+            );
         }
     }
 }
@@ -115,61 +76,52 @@ void Foam::functionObjects::catalystFaMesh::updateState
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::functionObjects::catalystFaMesh::catalystFaMesh
+Foam::catalyst::faMeshInput::faMeshInput
 (
     const word& name,
     const Time& runTime,
     const dictionary& dict
 )
 :
-    fvMeshFunctionObject(name, runTime, dict),
-    outputDir_("<case>/insitu"),
-    scripts_(),
-    adaptor_(),
+    catalystInput(name),
+    time_(runTime),
+    regionName_(),
     selectAreas_(),
     selectFields_(),
     meshes_(),
     backends_()
 {
-    if (postProcess)
-    {
-        // Disable for post-process mode.
-        // Emit as FatalError for the try/catch in the caller.
-        FatalError
-            << type() << " disabled in post-process mode"
-            << exit(FatalError);
-    }
     read(dict);
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::functionObjects::catalystFaMesh::~catalystFaMesh()
-{}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-bool Foam::functionObjects::catalystFaMesh::read(const dictionary& dict)
+bool Foam::catalyst::faMeshInput::read(const dictionary& dict)
 {
-    fvMeshFunctionObject::read(dict);
+    catalystInput::read(dict);
 
-    readBasics(dict);
+    selectAreas_.clear();
+    selectFields_.clear();
+    backends_.clear();
 
-    // All possible meshes
-    meshes_ = mesh_.lookupClass<faMesh>();
+    regionName_ =
+        dict.lookupOrDefault<word>("region", polyMesh::defaultRegion);
+
+    const objectRegistry& obr =
+        time_.lookupObject<objectRegistry>(regionName_);
+
+    // All possible meshes for the given region
+    meshes_ = obr.lookupClass<faMesh>();
 
-    selectAreas_.clear();
     dict.readIfPresent("areas", selectAreas_);
 
     if (selectAreas_.empty())
     {
         word areaName;
-
         if (!dict.readIfPresent("area", areaName))
         {
-            wordList available = mesh_.sortedNames<faMesh>();
+            wordList available = obr.sortedNames<faMesh>();
 
             if (available.size())
             {
@@ -185,59 +137,49 @@ bool Foam::functionObjects::catalystFaMesh::read(const dictionary& dict)
     }
 
     // Restrict to specified meshes
-    meshes_.filterKeys(wordRes(selectAreas_));
+    meshes_.filterKeys(selectAreas_);
 
     dict.lookup("fields") >> selectFields_;
 
-    Info<< type() << " " << name() << ":" << nl
-        <<"    areas   " << flatOutput(selectAreas_) << nl
-        <<"    meshes  " << flatOutput(meshes_.sortedToc()) << nl
-        <<"    fields  " << flatOutput(selectFields_) << nl
-        <<"    scripts " << scripts_ << nl;
-
-
-    // Ensure consistency - only retain backends with corresponding mesh region
-    backends_.retain(meshes_);
-
     return true;
 }
 
 
-bool Foam::functionObjects::catalystFaMesh::execute()
+void Foam::catalyst::faMeshInput::update(polyMesh::readUpdateState state)
 {
-    const wordList regionNames(meshes_.sortedToc());
+    // Trigger change of state
 
-    if (regionNames.empty())
-    {
-        return false;
-    }
+    const objectRegistry& obr =
+        time_.lookupObject<objectRegistry>(regionName_);
+
+    // Be really paranoid and verify if the mesh actually exists
+    const wordList regionNames(backends_.toc());
 
-    // Enforce sanity for backends and adaptor
+    for (const word& regionName : regionNames)
     {
-        bool updateAdaptor = false;
-        forAllConstIters(meshes_, iter)
+        if (meshes_.found(regionName) && obr.found(regionName))
         {
-            if (!backends_.found(iter.key()))
-            {
-                backends_.insert
-                (
-                    iter.key(),
-                    new Foam::vtk::faMeshAdaptor(*(iter.object()))
-                );
-                updateAdaptor = true;
-            }
+            backends_[regionName]->updateState(state);
         }
-
-        if (updateAdaptor && !adaptor_.valid())
+        else
         {
-            adaptor_.reset(new catalystCoprocess());
-            adaptor_().reset(outputDir_, scripts_);
+            backends_.erase(regionName);
+            meshes_.erase(regionName);
         }
     }
+}
 
 
-    // Gather all fields that we know how to convert
+Foam::label Foam::catalyst::faMeshInput::addChannels(dataQuery& dataq)
+{
+    update();   // Enforce sanity for backends and adaptor
+
+    if (backends_.empty())
+    {
+        return 0;
+    }
 
+    // Gather all fields that we know how to convert
     wordHashSet allFields;
     forAllConstIters(backends_, iter)
     {
@@ -245,96 +187,89 @@ bool Foam::functionObjects::catalystFaMesh::execute()
     }
 
 
-    // Data description for co-processing
-    vtkNew<vtkCPDataDescription> descrip;
+    dataq.set(name(), allFields);
 
-    // Form data query for catalyst
-    catalystCoprocess::dataQuery dataq
-    (
-        vtk::faMeshAdaptor::channelNames.names(),
-        time_,  // timeQuery
-        descrip.Get()
-    );
+    return 1;
+}
 
-    // Query catalyst
-    const HashTable<wordHashSet> expecting(adaptor_().query(dataq, allFields));
 
-    if (catalystCoprocess::debug)
+bool Foam::catalyst::faMeshInput::convert
+(
+    dataQuery& dataq,
+    outputChannels& outputs
+)
+{
+    const wordList regionNames(backends_.sortedToc());
+
+    if (regionNames.empty())
     {
-        if (expecting.empty())
-        {
-            Info<< type() << ": expecting no data" << nl;
-        }
-        else
-        {
-            Info<< type() << ": expecting data " << expecting << nl;
-        }
+        return false;  // skip - not available
     }
 
-    if (expecting.empty())
+    // Single channel only
+
+    label nChannels = 0;
+
+    if (dataq.found(name()))
+    {
+        ++nChannels;
+    }
+
+    if (!nChannels)
     {
-        return true;
+        return false;  // skip - not requested
     }
 
-    auto output = vtkSmartPointer<vtkMultiBlockDataSet>::New();
 
     // TODO: currently don't rely on the results from expecting much at all
 
     // Each region in a separate block
-    unsigned int regionNo = 0;
+    unsigned int blockNo = 0;
     for (const word& regionName : regionNames)
     {
-        auto pieces = backends_[regionName]->output(selectFields_);
-
-        output->SetBlock(regionNo, pieces);
-
-        output->GetMetaData(regionNo)->Set
-        (
-            vtkCompositeDataSet::NAME(),
-            regionName
-        );
-        ++regionNo;
-    }
+        auto dataset =
+            backends_[regionName]->output(selectFields_);
 
-    if (regionNo)
-    {
-        Log << type() << ": send data" << nl;
-
-        adaptor_().process(dataq, output);
-    }
+        {
+            const fileName channel = name();
 
-    return true;
-}
+            if (dataq.found(channel))
+            {
+                // Get existing or new
+                vtkSmartPointer<vtkMultiBlockDataSet> block =
+                    outputs.lookup
+                    (
+                        channel,
+                        vtkSmartPointer<vtkMultiBlockDataSet>::New()
+                    );
 
+                block->SetBlock(blockNo, dataset);
 
-bool Foam::functionObjects::catalystFaMesh::write()
-{
-    return true;
-}
+                block->GetMetaData(blockNo)->Set
+                (
+                    vtkCompositeDataSet::NAME(),
+                    regionName
+                );
 
+                outputs.set(channel, block);  // overwrites existing
+            }
+        }
 
-bool Foam::functionObjects::catalystFaMesh::end()
-{
-    // Only here for extra feedback
-    if (log && adaptor_.valid())
-    {
-        Info<< type() << ": Disconnecting ParaView Catalyst..." << nl;
+        ++blockNo;
     }
 
-    adaptor_.clear();
     return true;
 }
 
 
-void Foam::functionObjects::catalystFaMesh::updateMesh(const mapPolyMesh&)
+Foam::Ostream& Foam::catalyst::faMeshInput::print(Ostream& os) const
 {
-    updateState(polyMesh::TOPO_CHANGE);
-}
-
+    os  << name() << nl
+        <<"    areas   " << flatOutput(selectAreas_) << nl
+        <<"    meshes  " << flatOutput(meshes_.sortedToc()) << nl
+        <<"    fields  " << flatOutput(selectFields_) << nl;
 
-void Foam::functionObjects::catalystFaMesh::movePoints(const polyMesh&)
-{
-    updateState(polyMesh::POINTS_MOVED);
+    return os;
 }
 
 
diff --git a/src/catalyst/areaMesh/catalystFaMesh.H b/src/catalyst/areaMesh/catalystFaMesh.H
index 17dc3f3eac4f5b7a66f95dde48d57989ad019999..275ab601bcd95a8fe13f8e715025240426bff50e 100644
--- a/src/catalyst/areaMesh/catalystFaMesh.H
+++ b/src/catalyst/areaMesh/catalystFaMesh.H
@@ -22,51 +22,35 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::functionObjects::catalystFaMesh
-
-Group
-    grpUtilitiesFunctionObjects
+    Foam::catalyst::faMeshInput
 
 Description
-    A Paraview Catalyst adaptor for OpenFOAM faMesh regions.
+    A Paraview Catalyst source for OpenFOAM faMesh regions.
 
-    The output comprises a single "input" channel, which is a multi-block
-    dataset (one block per area mesh).
+    The source comprises a single internal "input" channel,
+    which is a multi-block dataset (one block per area mesh).
+    On output, the "input" sub-channel receives the name of the source.
 
-    Example of function object specification:
+    Example specification:
     \verbatim
-    catalyst
+    myArea
     {
-        type            catalyst::area;
-        libs            ("libcatalystFoam.so");
+        type            area;
         fields          (U p);
-        scripts         ( ... );
-        executeControl  timeStep;
-        executeInterval 1;
     }
     \endverbatim
 
 Usage
     \table
         Property     | Description                 | Required    | Default
-        type         | catalyst::area              | yes         |
-        log          | report extra information    | no          | false
-        mkdir        | initial directory to create | no          |
+        type         | area                        | yes         |
         region       |                             | no          | region0
         regions      | wordRe list of regions      | no          |
         fields       | wordRe list of fields       | yes         |
-        scripts      | Python pipeline scripts     | yes         |
     \endtable
 
-Note
-    The execution frequency can be defined by the functionObject and
-    by the Catalyst pipeline.
-
 See also
-    Foam::functionObjects::functionObject
-    Foam::functionObjects::fvMeshFunctionObject
-    Foam::functionObjects::timeControl
-    Foam::catalystCoprocess
+    Foam::catalyst::catalystInput
     Foam::vtk::faMeshAdaptor
 
 SourceFiles
@@ -75,47 +59,38 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef functionObjects_catalystFaMesh_H
-#define functionObjects_catalystFaMesh_H
+#ifndef catalyst_faMeshInput_H
+#define catalyst_faMeshInput_H
 
-#include "fvMeshFunctionObject.H"
-#include "foamVtkFaMeshAdaptor.H"
 #include "wordRes.H"
 #include "HashPtrTable.H"
+#include "catalystInput.H"
+#include "foamVtkFaMeshAdaptor.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
-
-// Forward declarations
-
-class catalystCoprocess;
-
-namespace functionObjects
+namespace catalyst
 {
 
 /*---------------------------------------------------------------------------*\
-                          Class catalystFaMesh Declaration
+                          Class catalyst::faMeshInput Declaration
 \*---------------------------------------------------------------------------*/
 
-class catalystFaMesh
+class faMeshInput
 :
-    public fvMeshFunctionObject
+    public catalystInput
 {
 protected:
 
     // Protected data
 
-        //- The output directory
-        fileName outputDir_;
-
-        //- Python scripts for the catalyst pipeline
-        stringList scripts_;
-
-        //- The catalyst coprocess
-        autoPtr<catalystCoprocess> adaptor_;
+        //- Reference to the time database
+        const Time& time_;
 
+        //- The region name for the area meshes
+        word regionName_;
 
         //- Requested names of areas to process
         wordRes selectAreas_;
@@ -132,29 +107,25 @@ protected:
 
     // Protected Member Functions
 
-        //- Common boilerplate settings
-        bool readBasics(const dictionary& dict);
-
-        //- On movement
-        void updateState(polyMesh::readUpdateState state);
-
+        //- Update/synchronize internals with catalyst backends
+        void update();
 
         //- No copy construct
-        catalystFaMesh(const catalystFaMesh&) = delete;
+        faMeshInput(const faMeshInput&) = delete;
 
         //- No copy assignment
-        void operator=(const catalystFaMesh&) = delete;
+        void operator=(const faMeshInput&) = delete;
 
 public:
 
     //- Runtime type information
-    TypeName("catalyst::area");
+    ClassName("catalyst::faMesh");
 
 
     // Constructors
 
         //- Construct from Time and dictionary
-        catalystFaMesh
+        faMeshInput
         (
             const word& name,
             const Time& runTime,
@@ -163,7 +134,7 @@ public:
 
 
     //- Destructor
-    virtual ~catalystFaMesh();
+    virtual ~faMeshInput() = default;
 
 
     // Member Functions
@@ -171,27 +142,23 @@ public:
         //- Read the specification
         virtual bool read(const dictionary& dict);
 
-        //- Execute catalyst pipelines
-        virtual bool execute();
-
-        //- Write - does nothing
-        virtual bool write();
-
-        //- On end - provide feedback about disconnecting from catatyst.
-        virtual bool end();
+        //- Update for changes of mesh or mesh point-motion
+        virtual void update(polyMesh::readUpdateState state);
 
-        //- Update for changes of mesh
-        virtual void updateMesh(const mapPolyMesh& mpm);
+        //- Add available channels (with fields) to data query
+        virtual label addChannels(dataQuery& dataq);
 
-        //- Update for mesh point-motion
-        virtual void movePoints(const polyMesh& mesh);
+        //- Convert channels to vtkMultiBlockDataSet outputs
+        virtual bool convert(dataQuery& dataq, outputChannels& outputs);
 
+        //- Print information
+        virtual Ostream& print(Ostream& os) const;
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace functionObjects
+} // End namespace catalyst
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.C b/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.C
index a19f41c947ebef02b8d2bb077a22292d0d967649..47a89cf8f0f3d49ed30f3351d2ce872b29c8776a 100644
--- a/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.C
+++ b/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.C
@@ -46,16 +46,6 @@ namespace vtk
 }
 }
 
-const Foam::Enum
-<
-    Foam::vtk::faMeshAdaptor::channel
->
-Foam::vtk::faMeshAdaptor::channelNames
-{
-    { channel::INPUT,   "input" },
-};
-
-
 const Foam::word Foam::vtk::faMeshAdaptor::internalName("internal");
 
 
diff --git a/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.H b/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.H
index 5b37ccb36c1b58dffb18eaf62d356aa9ad3ef1b5..129d780dc41e0e3228684b44f82e1f436f4a1225 100644
--- a/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.H
+++ b/src/catalyst/areaMesh/foamVtkFaMeshAdaptor.H
@@ -45,7 +45,6 @@ SourceFiles
 #include "fileName.H"
 #include "stringList.H"
 #include "wordList.H"
-#include "Enum.H"
 #include "polyMesh.H"
 #include "areaFieldsFwd.H"
 #include "foamVtkTools.H"
@@ -89,14 +88,6 @@ public:
 
         //- Public Data Members
 
-        //- The Catalyst output channels
-        enum channel
-        {
-            INPUT = 0x3
-        };
-
-        static const Enum<channel> channelNames;
-
         //- Name for internal mesh ("internal")
         static const word internalName;
 
diff --git a/src/catalyst/areaMesh/foamVtkFaMeshAdaptorFieldTemplates.C b/src/catalyst/areaMesh/foamVtkFaMeshAdaptorFieldTemplates.C
index e682a2bd2c45066ad30cfc6729d0cfc24ae2c9d1..6b2f6adad1a5c17b349dd4da165feb08520b7bb9 100644
--- a/src/catalyst/areaMesh/foamVtkFaMeshAdaptorFieldTemplates.C
+++ b/src/catalyst/areaMesh/foamVtkFaMeshAdaptorFieldTemplates.C
@@ -28,11 +28,6 @@ License
 
 // OpenFOAM includes
 #include "error.H"
-#include "emptyFvPatchField.H"
-#include "wallPolyPatch.H"
-#include "volPointInterpolation.H"
-#include "zeroGradientFvPatchField.H"
-#include "interpolatePointToCell.H"
 
 // vtk includes
 #include "vtkFloatArray.h"
diff --git a/src/catalyst/catalystCoprocess.C b/src/catalyst/catalystCoprocess.C
index 967fdfb1c06361f0bf0006577df08300f4ad4691..12e4d59c51093fa048bcb2989af3c3e151eb2f1a 100644
--- a/src/catalyst/catalystCoprocess.C
+++ b/src/catalyst/catalystCoprocess.C
@@ -24,8 +24,6 @@ License
 
 #include "catalystCoprocess.H"
 #include "Time.H"
-#include "stringOps.H"
-#include "OSspecific.H"
 
 // VTK includes
 #include <vtkNew.h>
@@ -42,135 +40,38 @@ License
 
 namespace Foam
 {
-    defineTypeNameAndDebug(catalystCoprocess, 0);
-}
-
-
-// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-namespace
-{
-
-#if 0
-static void printInfo(vtkCPDataDescription* descrip)
+namespace catalyst
 {
-    if (!descrip)
-    {
-        return;
-    }
-
-    const unsigned nItems = descrip->GetNumberOfInputDescriptions();
-
-    for (unsigned itemi = 0; itemi < nItems; ++itemi)
-    {
-        vtkCPInputDataDescription* input = descrip->GetInputDescription(itemi);
-        if (!input) continue;  // should not happen
-
-        Info<<"input: " << descrip->GetInputDescriptionName(itemi) << nl;
-
-        const unsigned nFields = input->GetNumberOfFields();
-        for (unsigned fieldi = 0; fieldi < nFields; ++fieldi)
-        {
-            Info<< "    field: " << input->GetFieldName(fieldi) << nl;
-        }
-        if (!nFields) Info<<"     no fields requested" << nl;
-    }
+    defineTypeNameAndDebug(coprocess, 0);
 }
-#endif
-
-} // End anonymous namespace
-} // End namespace Foam
-
-
-// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
-
-Foam::label Foam::catalystCoprocess::expand
-(
-    List<string>& scripts,
-    const dictionary& dict
-)
-{
-    label nscript = 0;
-
-    forAll(scripts, scripti)
-    {
-        string& s = scripts[scripti];
-
-        stringOps::inplaceExpand(s, dict, true, true);
-        fileName::clean(s);  // Remove trailing, repeated slashes etc.
-
-        if (isFile(s))
-        {
-            if (nscript != scripti)
-            {
-                scripts[nscript] = std::move(s);
-            }
-            ++nscript;
-        }
-    }
-
-    scripts.resize(nscript);
-
-    return nscript;
 }
 
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 template<class DataType>
-bool Foam::catalystCoprocess::processImpl
-(
-    const dataQuery& dataq,
-    vtkSmartPointer<DataType>& output
-)
-{
-    vtkCPDataDescription* descrip = dataq.get();
-
-    if (!coproc_->RequestDataDescription(descrip))
-    {
-        return false;
-    }
-
-    for (const word& chanName : dataq.channels())
-    {
-        auto* input = descrip->GetInputDescriptionByName(chanName.c_str());
-
-        if (input && input->GetIfGridIsNecessary())
-        {
-            input->SetGrid(output);
-        }
-    }
-
-    coproc_->CoProcess(descrip);
-    return true;
-}
-
-
-template<class DataType>
-bool Foam::catalystCoprocess::processImpl
+bool Foam::catalyst::coprocess::processImpl
 (
     const dataQuery& dataq,
-    HashTable<vtkSmartPointer<DataType>>& outputs
+    HashTable<vtkSmartPointer<DataType>, fileName>& outputs
 )
 {
-    vtkCPDataDescription* descrip = dataq.get();
+    auto* descrip = dataq.description();
 
     if (!coproc_->RequestDataDescription(descrip))
     {
         return false;
     }
 
-    for (const word& chanName : dataq.channels())
+    for (const fileName& channel : dataq.names())
     {
-        if (outputs.found(chanName))
+        if (outputs.found(channel))
         {
-            auto* input = descrip->GetInputDescriptionByName(chanName.c_str());
+            auto* input = descrip->GetInputDescriptionByName(channel.c_str());
 
             if (input && input->GetIfGridIsNecessary())
             {
-                input->SetGrid(outputs[chanName]);
+                input->SetGrid(outputs[channel]);
             }
         }
     }
@@ -180,21 +81,11 @@ bool Foam::catalystCoprocess::processImpl
 }
 
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::catalystCoprocess::timeQuery::timeQuery(const Foam::Time& t)
-:
-    timeValue(t.timeOutputValue()),
-    timeIndex(t.timeIndex()),
-    forced(t.timeOutputValue() >= t.endTime().value())
-{}
-
-
 // * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
 
-Foam::catalystCoprocess::~catalystCoprocess()
+Foam::catalyst::coprocess::~coprocess()
 {
-    // stop(), but without output
+    // As per stop(), but without output
     if (coproc_)
     {
         coproc_->Delete();
@@ -205,13 +96,13 @@ Foam::catalystCoprocess::~catalystCoprocess()
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-bool Foam::catalystCoprocess::good() const
+bool Foam::catalyst::coprocess::good() const
 {
     return coproc_;
 }
 
 
-void Foam::catalystCoprocess::stop()
+void Foam::catalyst::coprocess::stop()
 {
     if (coproc_)
     {
@@ -222,7 +113,7 @@ void Foam::catalystCoprocess::stop()
 }
 
 
-void Foam::catalystCoprocess::reset(const fileName& outputDir)
+void Foam::catalyst::coprocess::reset(const fileName& outputDir)
 {
     #ifdef USE_CATALYST_WORKING_DIRECTORY
     if (coproc_ == nullptr)
@@ -253,6 +144,7 @@ void Foam::catalystCoprocess::reset(const fileName& outputDir)
     {
         coproc_ = vtkCPProcessor::New();
         coproc_->Initialize();
+        Info<< "Connecting ParaView Catalyst..." << endl;
     }
     else
     {
@@ -265,7 +157,7 @@ void Foam::catalystCoprocess::reset(const fileName& outputDir)
 }
 
 
-void Foam::catalystCoprocess::reset
+void Foam::catalyst::coprocess::reset
 (
     const fileName& outputDir,
     const UList<string>& scripts
@@ -288,139 +180,20 @@ void Foam::catalystCoprocess::reset
 }
 
 
-Foam::HashTable<Foam::wordHashSet>
-Foam::catalystCoprocess::query
-(
-    dataQuery& dataq,
-    const wordHashSet& allFields
-)
+Foam::label Foam::catalyst::coprocess::query(dataQuery& dataq)
 {
-    // Desirable to also know which fields have been requested
-    HashTable<wordHashSet> requests;
-
-    if (!good())
-    {
-        Info<< "No ParaView Catalyst initialized" << endl;
-        return requests;
-    }
-
-    if (dataq.channels().empty())
-    {
-        // No channels names have been published by the simulation
-        return requests;
-    }
-
-    vtkCPDataDescription* descrip = dataq.get();
-
-    descrip->SetTimeData(dataq.timeValue, dataq.timeIndex);
-    descrip->SetForceOutput(dataq.forced);
-
-    // Sort out which channels already exist, are new, or disappeared
-    {
-        // The currently defined channels
-        wordHashSet currChannels;
-
-        const unsigned n = descrip->GetNumberOfInputDescriptions();
-        for (unsigned i=0; i < n; ++i)
-        {
-            currChannels.insert
-            (
-                word::validate(descrip->GetInputDescriptionName(i))
-            );
-        }
-
-        wordHashSet newChannels(dataq.channels());
-        wordHashSet oldChannels(currChannels);
-        oldChannels.erase(newChannels);
-
-        if (oldChannels.size())
-        {
-            descrip->ResetAll();
-        }
-        else
-        {
-            newChannels.erase(currChannels);
-        }
-
-        // Add channels
-        for (const word& chanName : newChannels)
-        {
-            descrip->AddInput(chanName.c_str());
-            auto* input = descrip->GetInputDescriptionByName(chanName.c_str());
-
-            for (const word& fieldName : allFields)
-            {
-                input->AddPointField(fieldName.c_str());
-                input->AddCellField(fieldName.c_str());
-            }
-        }
-
-        // Note: this misses updating field information for previously
-        // existing inputs.
-    }
-
-    if
-    (
-        !coproc_->RequestDataDescription(descrip)
-     || !descrip->GetIfAnyGridNecessary()
-    )
-    {
-        return requests;
-    }
-
-    for (const word& chanName : dataq.channels())
-    {
-        auto* input = descrip->GetInputDescriptionByName(chanName.c_str());
-
-        if (input && input->GetIfGridIsNecessary())
-        {
-            wordHashSet& fields = requests(chanName);  // auto-vivify
-
-            for (const word& fieldName : allFields)
-            {
-                if (input->IsFieldNeeded(fieldName.c_str()))
-                {
-                    fields.insert(fieldName);
-                }
-            }
-        }
-    }
-
-    return requests;
+    return dataq.query(coproc_);
 }
 
 
-bool Foam::catalystCoprocess::process
+bool Foam::catalyst::coprocess::process
 (
     const dataQuery& dataq,
-    vtkSmartPointer<vtkMultiBlockDataSet>& output
-)
-{
-    return processImpl(dataq, output);
-}
-
-
-bool Foam::catalystCoprocess::process
-(
-    const dataQuery& dataq,
-    HashTable<vtkSmartPointer<vtkMultiBlockDataSet>>& outputs
+    outputChannels& outputs
 )
 {
     return processImpl(dataq, outputs);
 }
 
 
-// * * * * * * * * * * * * * * * Ostream Operator  * * * * * * * * * * * * * //
-
-Foam::Ostream& Foam::operator<<
-(
-    Ostream& os,
-    const catalystCoprocess::timeQuery& when
-)
-{
-    os << "Time = " << when.timeValue << ", index: " << when.timeIndex;
-    return os;
-}
-
-
 // ************************************************************************* //
diff --git a/src/catalyst/catalystCoprocess.H b/src/catalyst/catalystCoprocess.H
index 3366a4c8d2b6f71db2f123dfcaf29c5c0c74002b..bd7b1827c8267454ad1fff7197ef8e6f3692b22b 100644
--- a/src/catalyst/catalystCoprocess.H
+++ b/src/catalyst/catalystCoprocess.H
@@ -22,136 +22,37 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::catalystCoprocess
+    Foam::catalyst::coprocess
 
 Description
     Low-level interface between OpenFOAM and ParaView Catalyst.
 
-    \code
-    ... initialize catalyst
-
-    ... define a data query for catalyst
-
-    // Data description for co-processing
-    vtkNew<vtkCPDataDescription> descrip;
-
-    // Form data query for catalyst
-    catalystCoprocess::dataQuery dataq(channelNames, runTime, descrip.Get());
-
-    // Query catalyst
-    HashTable<wordHashSet> expecting = adaptor_().query(dataq, fields);
-
-    \endcode
-
 SourceFiles
     catalystCoprocess.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef functionObjects_catalystCoprocess_H
-#define functionObjects_catalystCoprocess_H
+#ifndef catalyst_coprocess_H
+#define catalyst_coprocess_H
 
 #include "className.H"
 #include "wordList.H"
 #include "stringList.H"
-#include "HashSet.H"
+#include "catalystTools.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward Declarations
-class vtkCPProcessor;
-class vtkCPDataDescription;
-class vtkMultiBlockDataSet;
-template<class T> class vtkSmartPointer;
-
 namespace Foam
 {
-
-// Forward Declarations
-class dictionary;
-class Time;
-class Ostream;
-class catalystCoprocess;
+namespace catalyst
+{
 
 /*---------------------------------------------------------------------------*\
-                      Class catalystCoprocess Declaration
+                     Class catalyst::coprocess Declaration
 \*---------------------------------------------------------------------------*/
 
-class catalystCoprocess
+class coprocess
 {
-public:
-
-    //- Simple structure for time queries
-    struct timeQuery
-    {
-        double timeValue;
-        long   timeIndex;
-        bool   forced;
-
-        constexpr timeQuery
-        (
-            double val,
-            long index,
-            bool forcedOutput=false
-        ) noexcept
-        :
-            timeValue(val),
-            timeIndex(index),
-            forced(forcedOutput)
-        {}
-
-        timeQuery(const Foam::Time& currTime);
-    };
-
-
-    //- Simple structure for data description queries
-    //  The storage for the description is held outside this class
-    class dataQuery
-    :
-        public timeQuery
-    {
-        //- Catalyst channel names to query
-        List<word> channels_;
-        mutable vtkCPDataDescription* descrip_;
-
-    public:
-
-        dataQuery
-        (
-            const UList<word>& channelNames,
-            const timeQuery& when,
-            vtkCPDataDescription* description
-        )
-        :
-            timeQuery(when),
-            channels_(channelNames),
-            descrip_(description)
-        {}
-
-        dataQuery
-        (
-            const UList<word>& channelNames,
-            const Foam::Time& when,
-            vtkCPDataDescription* description
-        )
-        :
-            dataQuery(channelNames, timeQuery(when), description)
-        {}
-
-        //- The declared output channels
-        const UList<word>& channels() const
-        {
-            return channels_;
-        }
-
-        //- Pointer to coprocess data description
-        vtkCPDataDescription* get() const
-        {
-            return this->descrip_;
-        }
-    };
-
-
 private:
 
     // Private Data
@@ -162,30 +63,16 @@ private:
 
     // Private Member Functions
 
-        //- Process single output channel
-        template<class DataType>
-        bool processImpl
-        (
-            const dataQuery& query,
-            vtkSmartPointer<DataType>& outputs
-        );
-
         //- Process multiple output channels
         template<class DataType>
         bool processImpl
         (
-            const dataQuery& query,
-            HashTable<vtkSmartPointer<DataType>>& outputs
+            const dataQuery& dataq,
+            HashTable<vtkSmartPointer<DataType>, fileName>& outputs
         );
 
 public:
 
-    // Static Methods
-
-        //- Expand strings as filenames, retaining only those that exist
-        static label expand(List<string>& scripts, const dictionary& dict);
-
-
     //- Define class name and debug
     ClassName("catalyst");
 
@@ -193,14 +80,14 @@ public:
     // Constructors
 
         //- Construct null. Does not initialize catalyst.
-        constexpr catalystCoprocess() noexcept
+        constexpr coprocess() noexcept
         :
             coproc_(nullptr)
         {}
 
 
     //- Destructor. Shutdown process.
-    ~catalystCoprocess();
+    ~coprocess();
 
 
     // Member Functions
@@ -225,35 +112,17 @@ public:
         //- iteration and possibly which fields they require.
         //
         // \param[in,out] dataq the data query for catalyst.
-        //     On input it contains the published channel names, the current
-        //     simulation time (index, value) and allocation for the coprocess
-        //     data description.
-        //     On output the data description will be filled with the field
-        //     names added per channel.
-        // \param[in] allFields the fields that can be published from the
-        //     simulation.
+        //     On input it contains the published channel names with their
+        //     published fields, as well as the current simulation time
+        //     (index, value).
+        //     On output the data description part of dataq will be filled
+        //     with the requested field names added per channel.
         //
-        // \return HashTable with fields requested (what Catalyst expects)
-        //     on a per-channel basis.
-        HashTable<wordHashSet> query
-        (
-            dataQuery& dataq,
-            const wordHashSet& allFields
-        );
-
-        //- Single-channel source (eg, "input" or "cloud", ...)
-        bool process
-        (
-            const dataQuery& dataq,
-            vtkSmartPointer<vtkMultiBlockDataSet>& output
-        );
+        // \return number of channels request at this time
+        label query(dataQuery& dataq);
 
-        //- Multi-channel source (eg, "input", "mesh", "patches")
-        bool process
-        (
-            const dataQuery& dataq,
-            HashTable<vtkSmartPointer<vtkMultiBlockDataSet>>& outputs
-        );
+        //- Process the output channels
+        bool process(const dataQuery& dataq, outputChannels& outputs);
 
         //- Finalize
         void stop();
@@ -261,10 +130,9 @@ public:
 };
 
 
-// Ostream Operator
-Ostream& operator<<(Ostream& os, const catalystCoprocess::timeQuery& when);
-
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+} // End namespace catalyst
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/catalyst/catalystDict b/src/catalyst/catalystDict
new file mode 100644
index 0000000000000000000000000000000000000000..ff7bd1f9f8b60fbc0ff99070f6abb851d78fcf50
--- /dev/null
+++ b/src/catalyst/catalystDict
@@ -0,0 +1,61 @@
+// ParaView Catalyst function object for OpenFOAM (-*- C++ -*-)
+
+functions
+{
+    catalyst
+    {
+        type    catalyst;
+        libs    ("libcatalystFoam.so");
+        executeControl  timeStep;
+        writeControl    none;
+
+        // OR #includeEtc "caseDicts/insitu/catalyst/catalyst.cfg"
+
+        // mkdir  "<case>/someDir";
+
+        scripts
+        (
+            // "<etc>/caseDicts/insitu/catalyst/printChannels.py"
+            "<etc>/caseDicts/insitu/catalyst/writeAll.py"
+        );
+
+        inputs
+        {
+            // fvMesh
+            region
+            {
+                type    default;
+
+                // All regions
+                regions (".*");
+
+                // Selected fields (words or regex)
+                fields  (T U p);
+            }
+
+            // faMesh
+            area
+            {
+                type    area;
+
+                // Selected fields (words or regex)
+                fields  ( ".*" );
+            }
+
+            // lagrangian
+            cloud
+            {
+                type    cloud;
+
+                // Selected clouds (words or regex)
+                clouds  ( coalCloud1 limestoneCloud1 );
+
+                // Selected fields (words or regex)
+                fields  ( T U p rho "Y.*" );
+            }
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/catalyst/catalystFunctionObject.C b/src/catalyst/catalystFunctionObject.C
new file mode 100644
index 0000000000000000000000000000000000000000..58d3bfb9a6e949b2d912d465742320a95fb8dd09
--- /dev/null
+++ b/src/catalyst/catalystFunctionObject.C
@@ -0,0 +1,344 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  | Copyright (C) 2018 CINECA
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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.
+
+    OpenFOAM 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
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "catalystFunctionObject.H"
+#include "catalystCoprocess.H"
+#include "stringOps.H"
+#include "OSHA1stream.H"
+#include "OSspecific.H"
+#include "addToRunTimeSelectionTable.H"
+
+#include <vtkCPDataDescription.h>
+#include <vtkMultiBlockDataSet.h>
+#include <vtkInformation.h>
+#include <vtkSmartPointer.h>
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+    defineTypeNameAndDebug(catalystFunctionObject, 0);
+    addToRunTimeSelectionTable
+    (
+        functionObject,
+        catalystFunctionObject,
+        dictionary
+    );
+} // End namespace functionObjects
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+Foam::label Foam::functionObjects::catalystFunctionObject::expand
+(
+    List<string>& scripts,
+    const dictionary& dict
+)
+{
+    label nscript = 0;
+
+    forAll(scripts, scripti)
+    {
+        string& s = scripts[scripti];
+
+        stringOps::inplaceExpand(s, dict, true, true);
+        fileName::clean(s);  // Remove trailing, repeated slashes etc.
+
+        if (isFile(s))
+        {
+            if (nscript != scripti)
+            {
+                scripts[nscript] = std::move(s);
+            }
+            ++nscript;
+        }
+    }
+
+    scripts.resize(nscript);
+
+    return nscript;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::catalystFunctionObject::catalystFunctionObject
+(
+    const word& name,
+    const Time& runTime,
+    const dictionary& dict
+)
+:
+    functionObject(name),
+    time_(runTime),
+    outputDir_("<case>/insitu"),
+    scripts_(),
+    adaptor_(),
+    inputs_()
+{
+    if (postProcess)
+    {
+        // Disable for post-process mode.
+        // Emit as FatalError for the try/catch in the caller.
+        FatalError
+            << type() << " disabled in post-process mode"
+            << exit(FatalError);
+    }
+
+    if (!read(dict))
+    {
+        FatalError
+            << type() << " with errors"
+            << exit(FatalError);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::functionObjects::catalystFunctionObject::~catalystFunctionObject()
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::catalystFunctionObject::read(const dictionary& dict)
+{
+    functionObject::read(dict);
+
+    const dictionary* inputDictPtr = dict.subDictPtr("inputs");
+
+    if (!inputDictPtr || inputDictPtr->empty())
+    {
+        inputs_.clear();
+
+        WarningInFunction
+            << type() << " missing inputs" << endl;
+
+        return false;
+    }
+
+    int debugLevel = 0;
+    if (dict.readIfPresent("debug", debugLevel))
+    {
+        catalyst::coprocess::debug = debugLevel;
+    }
+
+    if (Pstream::master())
+    {
+        fileName dir;
+        if (dict.readIfPresent("mkdir", dir))
+        {
+            dir.expand();
+            dir.clean();
+            Foam::mkDir(dir);
+        }
+    }
+
+
+    // Track changes in outputDir and/or scripts
+    OSHA1stream osha1;
+    osha1 << outputDir_ << scripts_;
+    const SHA1Digest oldDigest(osha1.digest());
+
+    dict.readIfPresent("outputDir", outputDir_);
+    outputDir_.expand();
+    outputDir_.clean();
+    if (Pstream::master())
+    {
+        Foam::mkDir(outputDir_);
+    }
+
+    dict.lookup("scripts") >> scripts_; // Python scripts
+    expand(scripts_, dict);             // Expand and check availability
+
+
+    // Any changes detected
+    osha1.reset();
+    osha1 << outputDir_ osha1 << scripts_;
+
+    if (adaptor_.valid() && oldDigest != osha1.digest())
+    {
+        adaptor_().reset(outputDir_, scripts_);
+    }
+
+
+    PtrList<catalyst::catalystInput> newList(inputDictPtr->size());
+
+    label nInputs = 0;
+    forAllConstIters(*inputDictPtr, iter)
+    {
+        const dictionary* subDictPtr = iter().dictPtr();
+        if (iter().keyword().isPattern() || !subDictPtr)
+        {
+            continue;
+        }
+
+        newList.set
+        (
+            nInputs,
+            catalyst::catalystInput::New
+            (
+                word(iter().keyword()),
+                time_,
+                *subDictPtr
+            ).ptr()
+        );
+
+        ++nInputs;
+    }
+
+    newList.resize(nInputs);
+    inputs_.transfer(newList);
+
+    if (inputs_.empty())
+    {
+        WarningInFunction
+            << type() << " missing inputs" << endl;
+    }
+    else
+    {
+        Info<< type() << " " << name() << ":" << nl
+            << "    scripts " << scripts_ << nl
+            << "    inputs:" << nl
+            << "(" << nl;
+
+        nInputs = 0;
+        for (const auto& inp : inputs_)
+        {
+            if (nInputs++) Info << nl;
+            inp.print(Info);
+        }
+
+        Info<< ")" << nl;
+    }
+
+    return true;
+}
+
+
+bool Foam::functionObjects::catalystFunctionObject::execute()
+{
+    // Enforce sanity for backends and adaptor
+
+    if (inputs_.empty())
+    {
+        return false;
+    }
+
+    if (!adaptor_.valid())
+    {
+        adaptor_.reset(new catalyst::coprocess());
+        adaptor_().reset(outputDir_, scripts_);
+    }
+
+    catalyst::dataQuery dataq(time_);
+
+    label nChannels = 0;
+    for (auto& inp : inputs_)
+    {
+        nChannels += inp.addChannels(dataq);
+    }
+
+    if (nChannels)
+    {
+        nChannels = adaptor_().query(dataq);
+    }
+
+    if (catalyst::coprocess::debug)
+    {
+        Info<< type() << ": expecting data for " << nChannels << nl;
+    }
+
+    if (!nChannels)
+    {
+        return true;
+    }
+
+    catalyst::outputChannels outputs;
+
+    for (auto& inp : inputs_)
+    {
+        inp.convert(dataq, outputs);
+    }
+
+    if (outputs.size())
+    {
+        Log << type() << ": send data" << nl;
+
+        adaptor_().process(dataq, outputs);
+    }
+
+    return true;
+}
+
+
+bool Foam::functionObjects::catalystFunctionObject::write()
+{
+    return true;
+}
+
+
+bool Foam::functionObjects::catalystFunctionObject::end()
+{
+    // Only here for extra feedback
+    if (log && adaptor_.valid())
+    {
+        Info<< type() << ": Disconnecting ParaView Catalyst..." << nl;
+    }
+
+    adaptor_.clear();
+    return true;
+}
+
+
+void Foam::functionObjects::catalystFunctionObject::updateMesh
+(
+    const mapPolyMesh&
+)
+{
+    for (auto& inp : inputs_)
+    {
+        inp.update(polyMesh::TOPO_CHANGE);
+    }
+}
+
+
+void Foam::functionObjects::catalystFunctionObject::movePoints
+(
+    const polyMesh&
+)
+{
+    for (auto& inp : inputs_)
+    {
+        inp.update(polyMesh::POINTS_MOVED);
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/catalyst/catalystFunctionObject.H b/src/catalyst/catalystFunctionObject.H
new file mode 100644
index 0000000000000000000000000000000000000000..13bd9cfcbb8020c06accbf317d31fdd9fcac3ac6
--- /dev/null
+++ b/src/catalyst/catalystFunctionObject.H
@@ -0,0 +1,195 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  | Copyright (C) 2018 CINECA
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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.
+
+    OpenFOAM 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
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::functionObjects::catalystFunctionObject
+
+Group
+    grpUtilitiesFunctionObjects
+
+Description
+    A Paraview Catalyst adaptor for OpenFOAM fvMesh regions.
+
+    The output comprises up to three channels ("input", "mesh", "patches"),
+    each of which is a multi-block dataset.
+
+    Example of function object specification:
+    \verbatim
+    catalyst
+    {
+        type            catalyst;
+        libs            ("libcatalystFoam.so");
+        scripts         ( ... );
+        executeControl  timeStep;
+        executeInterval 1;
+
+        inputs
+        {
+            type        default;
+            regions     ( ".*Solid" )
+            fields      (U p);
+        }
+    }
+    \endverbatim
+
+Usage
+    \table
+        Property     | Description                 | Required    | Default
+        type         | catalyst                    | yes         |
+        log          | report extra information    | no          | false
+        mkdir        | initial directory to create | no          |
+        scripts      | Python pipeline scripts     | yes         |
+    \endtable
+
+Note
+    The execution frequency can be defined by the functionObject and
+    by the Catalyst pipeline.
+
+See also
+    Foam::functionObjects::functionObject
+    Foam::functionObjects::fvMeshFunctionObject
+    Foam::functionObjects::timeControl
+    Foam::catalyst::coprocess
+    Foam::catalyst::cloudInput
+    Foam::catalyst::faMeshInput
+    Foam::catalyst::fvMeshInput
+
+SourceFiles
+    catalystFunctionObject.C
+    catalystFunctionObjectTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_catalystFunctionObject_H
+#define functionObjects_catalystFunctionObject_H
+
+#include "className.H"
+#include "wordList.H"
+#include "stringList.H"
+#include "polyMesh.H"
+#include "PtrList.H"
+#include "functionObject.H"
+#include "catalystCoprocess.H"
+#include "catalystInput.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+
+/*---------------------------------------------------------------------------*\
+                   Class catalystFunctionObject Declaration
+\*---------------------------------------------------------------------------*/
+
+class catalystFunctionObject
+:
+    public functionObject
+{
+    // Private data
+
+        //- Reference to the time database
+        const Time& time_;
+
+        //- The output directory
+        fileName outputDir_;
+
+        //- Python scripts for the catalyst pipeline
+        stringList scripts_;
+
+        //- The catalyst coprocess
+        autoPtr<catalyst::coprocess> adaptor_;
+
+        //- Pointers to the requested mesh regions
+        PtrList<catalyst::catalystInput> inputs_;
+
+
+    // Private Member Functions
+
+        //- No copy construct
+        catalystFunctionObject(const catalystFunctionObject&) = delete;
+
+        //- No copy assignment
+        void operator=(const catalystFunctionObject&) = delete;
+
+public:
+
+    // Static Methods
+
+        //- Expand strings as filenames, retaining only those that exist
+        static label expand(List<string>& scripts, const dictionary& dict);
+
+
+    //- Runtime type information
+    TypeName("catalyst");
+
+
+    // Constructors
+
+        //- Construct from Time and dictionary
+        catalystFunctionObject
+        (
+            const word& name,
+            const Time& runTime,
+            const dictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~catalystFunctionObject();
+
+
+    // Member Functions
+
+        //- Read the specification
+        virtual bool read(const dictionary& dict);
+
+        //- Execute catalyst pipelines
+        virtual bool execute();
+
+        //- Write - does nothing
+        virtual bool write();
+
+        //- On end - provide feedback about disconnecting from catatyst.
+        virtual bool end();
+
+        //- Update for changes of mesh
+        virtual void updateMesh(const mapPolyMesh& mpm);
+
+        //- Update for mesh point-motion
+        virtual void movePoints(const polyMesh& mesh);
+
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/catalyst/catalystInput.C b/src/catalyst/catalystInput.C
new file mode 100644
index 0000000000000000000000000000000000000000..4b1645d454d901ed6e888269eccf962ff8234e61
--- /dev/null
+++ b/src/catalyst/catalystInput.C
@@ -0,0 +1,95 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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.
+
+    OpenFOAM 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
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "catalystInput.H"
+#include "dictionary.H"
+#include "Time.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace catalyst
+{
+    defineTypeNameAndDebug(catalystInput, 0);
+    defineRunTimeSelectionTable(catalystInput, dictionary);
+}
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::catalyst::catalystInput::catalystInput(const word& channel)
+:
+    name_(channel)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::autoPtr<Foam::catalyst::catalystInput>
+Foam::catalyst::catalystInput::New
+(
+    const word& name,
+    const Time& runTime,
+    const dictionary& dict
+)
+{
+    const word sourceType = dict.lookupOrDefault<word>("type", "default");
+
+    auto cstrIter = dictionaryConstructorTablePtr_->cfind(sourceType);
+
+    if (!cstrIter.found())
+    {
+        FatalErrorInFunction
+            << "Unknown catalystInput "
+            << sourceType << nl << nl
+            << "Valid sources : " << endl
+            << dictionaryConstructorTablePtr_->sortedToc()
+            << exit(FatalError);
+    }
+
+    return autoPtr<catalystInput>(cstrIter()(name, runTime, dict));
+}
+
+
+bool Foam::catalyst::catalystInput::read(const dictionary&)
+{
+    return true;
+}
+
+
+void Foam::catalyst::catalystInput::update(polyMesh::readUpdateState state)
+{}
+
+
+Foam::Ostream& Foam::catalyst::catalystInput::print(Ostream& os) const
+{
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/catalyst/catalystInput.H b/src/catalyst/catalystInput.H
new file mode 100644
index 0000000000000000000000000000000000000000..f709f0035648ef94bf88b331ebb9a896045ff78b
--- /dev/null
+++ b/src/catalyst/catalystInput.H
@@ -0,0 +1,150 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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.
+
+    OpenFOAM 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
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::catalystInput
+
+Description
+    An OpenFOAM source for Paraview Catalyst.
+
+See also
+    Foam::catalyst::coprocess
+    Foam::catalyst::dataQuery
+    Foam::catalyst::timeQuery
+
+SourceFiles
+    catalystInput.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef catalyst_catalystInput_H
+#define catalyst_catalystInput_H
+
+#include "className.H"
+#include "polyMesh.H"
+#include "runTimeSelectionTables.H"
+#include "catalystTools.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward declarations
+class dictionary;
+class mapPolyMesh;
+class Time;
+
+namespace catalyst
+{
+
+/*---------------------------------------------------------------------------*\
+                   Class catalyst::catalystInput Declaration
+\*---------------------------------------------------------------------------*/
+
+class catalystInput
+{
+    // Private data
+
+        //- The name of the source
+        word name_;
+
+public:
+
+    // Declare run-time constructor selection table
+
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            catalystInput,
+            dictionary,
+            (
+                const word& name,
+                const Time& runTime,
+                const dictionary& dict
+            ),
+            (name, runTime, dict)
+        );
+
+
+    // Selectors
+
+        //- Return a reference to the selected source
+        static autoPtr<catalystInput> New
+        (
+            const word& name,
+            const Time& runTime,
+            const dictionary& dict
+        );
+
+
+    //- Runtime type information
+    ClassName("catalyst::input");
+
+
+    // Constructors
+
+        //- Construct with given channel name
+        explicit catalystInput(const word& channel);
+
+
+    //- Destructor
+    virtual ~catalystInput() = default;
+
+
+    // Member Functions
+
+        //- The name of the source (channel)
+        virtual const word& name() const
+        {
+            return name_;
+        }
+
+        //- Read the specification
+        virtual bool read(const dictionary& dict);
+
+        //- Update for changes of mesh or mesh point-motion
+        virtual void update(polyMesh::readUpdateState state);
+
+        //- Add channels (with fields) to data query
+        virtual label addChannels(dataQuery& dataq) = 0;
+
+        //- Convert channels to vtkMultiBlockDataSet outputs
+        virtual bool convert(dataQuery& dataq, outputChannels& outputs) = 0;
+
+
+        //- Print information
+        virtual Ostream& print(Ostream& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace catalyst
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/catalyst/catalystTools.C b/src/catalyst/catalystTools.C
new file mode 100644
index 0000000000000000000000000000000000000000..9ab35b7ba19247f8c61bed97ab60e8b19fff0b67
--- /dev/null
+++ b/src/catalyst/catalystTools.C
@@ -0,0 +1,233 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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.
+
+    OpenFOAM 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
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+\*---------------------------------------------------------------------------*/
+
+#include "catalystTools.H"
+#include "Time.H"
+
+#include <vtkCPDataDescription.h>
+#include <vtkCPInputDataDescription.h>
+#include <vtkCPProcessor.h>
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+const Foam::wordHashSet Foam::catalyst::dataQuery::emptyWordHash_;
+
+// * * * * * * * * * * * * * * * Global Functions  * * * * * * * * * * * * * //
+
+void Foam::catalyst::printInfo(Ostream& os, vtkCPDataDescription* descrip)
+{
+    if (!descrip)
+    {
+        return;
+    }
+
+    const unsigned nItems = descrip->GetNumberOfInputDescriptions();
+
+    for (unsigned itemi = 0; itemi < nItems; ++itemi)
+    {
+        vtkCPInputDataDescription* input = descrip->GetInputDescription(itemi);
+        if (!input) continue;  // should not happen
+
+        os <<"input: " << descrip->GetInputDescriptionName(itemi) << nl;
+
+        const unsigned nFields = input->GetNumberOfFields();
+        for (unsigned fieldi = 0; fieldi < nFields; ++fieldi)
+        {
+            os << "    field: " << input->GetFieldName(fieldi) << nl;
+        }
+        if (!nFields) os <<"     no fields requested" << nl;
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::catalyst::timeQuery::timeQuery(const Foam::Time& when)
+:
+    timeValue(when.timeOutputValue()),
+    timeIndex(when.timeIndex()),
+    forced(when.timeOutputValue() >= when.endTime().value())
+{}
+
+
+Foam::catalyst::dataQuery::dataQuery(const Foam::Time& when)
+:
+    timeQuery(when),
+    names_(),
+    fields_(),
+    active_(),
+    descrip_(vtkCPDataDescription::New())
+{}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::catalyst::dataQuery::~dataQuery()
+{
+    descrip_->Delete();
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::catalyst::dataQuery::set
+(
+    const fileName& channel,
+    const wordHashSet& fields
+)
+{
+    auto iter = fields_.find(channel);
+
+    if (iter.found())
+    {
+        *iter = fields;
+    }
+    else
+    {
+        active_.insert(names_.size());
+        fields_.insert(channel, fields);
+        names_.append(channel);
+    }
+}
+
+
+Foam::label Foam::catalyst::dataQuery::query(vtkCPProcessor* coproc)
+{
+    dataQuery& dataq = *this;
+
+    if (!coproc)
+    {
+        Info<< "No ParaView Catalyst initialized" << endl;
+        dataq.clear();
+        return dataq.size();
+    }
+
+    if (dataq.empty())
+    {
+        // No channels names have been published by the simulation
+        return dataq.size();
+    }
+
+    vtkCPDataDescription* descrip = dataq.description();
+
+    descrip->SetTimeData(dataq.timeValue, dataq.timeIndex);
+    descrip->SetForceOutput(dataq.forced);
+
+    const List<fileName> inputNames(dataq.names());
+
+    // Sort out which channels already exist, are new, or disappeared
+
+    // The currently defined channels
+    HashSet<fileName> currChannels;
+
+    const unsigned n = descrip->GetNumberOfInputDescriptions();
+    for (unsigned i=0; i < n; ++i)
+    {
+        currChannels.insert
+        (
+            fileName::validate(descrip->GetInputDescriptionName(i))
+        );
+    }
+
+    HashSet<fileName> oldChannels(currChannels);
+    oldChannels.eraseMany(inputNames);
+
+    if (oldChannels.size())
+    {
+        // Some channels disappear - remove and redo everything
+        descrip->ResetAll();
+        currChannels.clear();
+    }
+
+    // Add channels.
+
+    // Note: this misses updating field information for previously
+    // existing inputs.
+
+    for (const fileName& channel : inputNames)
+    {
+        if (currChannels.found(channel))
+        {
+            continue;
+        }
+
+        descrip->AddInput(channel.c_str());
+        auto* input =
+            descrip->GetInputDescriptionByName(channel.c_str());
+
+        for (const word& fieldName : dataq.fields(channel))
+        {
+            input->AddPointField(fieldName.c_str());
+            input->AddCellField(fieldName.c_str());
+        }
+    }
+
+    if
+    (
+        !coproc->RequestDataDescription(descrip)
+     || !descrip->GetIfAnyGridNecessary()
+    )
+    {
+        dataq.clear();
+        return dataq.size();
+    }
+
+    for (const fileName& channel : inputNames)
+    {
+        auto* input = descrip->GetInputDescriptionByName(channel.c_str());
+
+        if (input && input->GetIfGridIsNecessary())
+        {
+            wordHashSet requestedFields;
+
+            for (const word& fieldName : dataq.fields(channel))
+            {
+                if (input->IsFieldNeeded(fieldName.c_str()))
+                {
+                    requestedFields.insert(fieldName);
+                }
+            }
+
+            dataq.set(channel, requestedFields);
+        }
+        else
+        {
+            dataq.clear(channel);
+        }
+    }
+
+    return dataq.size();
+}
+
+
+// * * * * * * * * * * * * * * * Ostream Operator  * * * * * * * * * * * * * //
+
+Foam::Ostream& Foam::operator<<(Ostream& os, const catalyst::timeQuery& when)
+{
+    os << "Time = " << when.timeValue << ", index: " << when.timeIndex;
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/catalyst/catalystTools.H b/src/catalyst/catalystTools.H
new file mode 100644
index 0000000000000000000000000000000000000000..a47fedc6c4041d82e1aa73b86ed198ae549dd82b
--- /dev/null
+++ b/src/catalyst/catalystTools.H
@@ -0,0 +1,204 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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.
+
+    OpenFOAM 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
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::catalyst::timeQuery
+
+Description
+    Low-level handling for time queries
+
+SourceFiles
+    catalystTools.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_catalystTools_H
+#define functionObjects_catalystTools_H
+
+#include "wordList.H"
+#include "stringList.H"
+#include "DynamicList.H"
+#include "HashSet.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Forward declarations
+class vtkCPDataDescription;
+class vtkCPProcessor;
+class vtkMultiBlockDataSet;
+template<class T> class vtkSmartPointer;
+
+namespace Foam
+{
+
+// Forward declarations
+class Ostream;
+class Time;
+
+namespace catalyst
+{
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+//- Container holding VTK outputs per Catalyst channel name
+typedef
+    HashTable<vtkSmartPointer<vtkMultiBlockDataSet>, fileName>
+    outputChannels;
+
+
+//- Print information about data description
+void printInfo(Ostream& os, vtkCPDataDescription* descrip);
+
+
+/*---------------------------------------------------------------------------*\
+                     Class catalyst::timeQuery Declaration
+\*---------------------------------------------------------------------------*/
+
+//- Simple structure for time queries
+struct timeQuery
+{
+    double timeValue;
+    long   timeIndex;
+    bool   forced;
+
+    constexpr timeQuery
+    (
+        double val,
+        long index,
+        bool forcedOutput=false
+    ) noexcept
+    :
+        timeValue(val),
+        timeIndex(index),
+        forced(forcedOutput)
+    {}
+
+    timeQuery(const Foam::Time& currTime);
+};
+
+
+/*---------------------------------------------------------------------------*\
+                     Class catalyst::dataQuery Declaration
+\*---------------------------------------------------------------------------*/
+
+//- Simple structure for data description queries
+class dataQuery
+:
+    public timeQuery
+{
+    //- Catalyst channel names for query, in the order of addition
+    DynamicList<fileName> names_;
+
+    //- Fields per channel
+    HashTable<wordHashSet, fileName> fields_;
+
+    //- Indices (into names_) of active channels
+    labelHashSet active_;
+
+    //- The data description
+    vtkCPDataDescription* descrip_;
+
+    //- Empty dummy hash of field names
+    static const wordHashSet emptyWordHash_;
+
+
+    //- No copy construct
+    dataQuery(const dataQuery&) = delete;
+
+    //- No copy assignment
+    void operator=(const dataQuery&) = delete;
+
+
+public:
+
+    // Constructors
+
+        //- Construct query for given time. Add channels later
+        dataQuery(const Foam::Time& when);
+
+
+    //- Destructor
+    ~dataQuery();
+
+
+    // Member Functions
+
+        //- Pointer to coprocess data description
+        inline vtkCPDataDescription* description() const;
+
+        //- Clear the channel information
+        inline void clear();
+
+        //- Empty if there are no active channels
+        inline bool empty() const;
+
+        //- The number of active channels
+        inline label size() const;
+
+        //- True if given channel exists (in fields or active)
+        inline bool found(const fileName& channel) const;
+
+        //- The active channel numbers
+        inline List<label> active() const;
+
+        //- The channel names
+        inline List<fileName> names() const;
+
+        //- Fields for the given channel
+        inline const wordHashSet& fields(const fileName& channel) const;
+
+        //- Clear the specified channel
+        inline void clear(const fileName& channel);
+
+        //- Set fields for the specified channel and make it active
+        void set(const fileName& channel, const wordHashSet& fields);
+
+        //- Query coprocess, transform defined input channels to
+        //- known requests.
+        //  On input the dataQuery contains the channel names and their
+        //  associated published fields.
+        //  On output, retains the requested channels only.
+        label query(vtkCPProcessor* coproc);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace catalyst
+
+
+// Ostream Operator
+Ostream& operator<<(Ostream& os, const catalyst::timeQuery& when);
+
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "catalystToolsI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/catalyst/catalystToolsI.H b/src/catalyst/catalystToolsI.H
new file mode 100644
index 0000000000000000000000000000000000000000..3997b7d429344669f4172abffb7d231447d916ae
--- /dev/null
+++ b/src/catalyst/catalystToolsI.H
@@ -0,0 +1,95 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM 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.
+
+    OpenFOAM 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
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+inline vtkCPDataDescription*
+Foam::catalyst::dataQuery::description() const
+{
+    return descrip_;
+}
+
+
+inline void Foam::catalyst::dataQuery::clear()
+{
+    names_.clear();
+    fields_.clear();
+    active_.clear();
+}
+
+
+inline bool Foam::catalyst::dataQuery::empty() const
+{
+    return active_.empty();
+}
+
+
+inline Foam::label Foam::catalyst::dataQuery::size() const
+{
+    return active_.size();
+}
+
+
+inline bool Foam::catalyst::dataQuery::found
+(
+    const fileName& channel
+) const
+{
+    return fields_.found(channel);
+}
+
+
+inline Foam::List<Foam::label> Foam::catalyst::dataQuery::active() const
+{
+    return active_.sortedToc();
+}
+
+
+inline Foam::List<Foam::fileName> Foam::catalyst::dataQuery::names() const
+{
+    return List<fileName>(names_, active_.sortedToc());
+}
+
+
+inline const Foam::wordHashSet&
+Foam::catalyst::dataQuery::fields(const fileName& channel) const
+{
+    const auto iter = fields_.cfind(channel);
+    if (iter.found())
+    {
+        return *iter;
+    }
+
+    return emptyWordHash_;
+}
+
+
+inline void Foam::catalyst::dataQuery::clear(const fileName& channel)
+{
+    fields_.erase(channel);
+    active_.erase(names_.find(channel));
+}
+
+
+// ************************************************************************* //
diff --git a/src/catalyst/cloud/catalystCloud.C b/src/catalyst/cloud/catalystCloud.C
index 206e769cabad8a85eb0bc138592eaf0e849c5b7a..58e8faec6a7b9507ec852a8d597ef84a253c61e3 100644
--- a/src/catalyst/cloud/catalystCloud.C
+++ b/src/catalyst/cloud/catalystCloud.C
@@ -24,12 +24,10 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "catalystCloud.H"
-#include "catalystCoprocess.H"
 #include "cloud.H"
 #include "foamVtkCloudAdaptor.H"
 #include "addToRunTimeSelectionTable.H"
 
-#include <vtkNew.h>
 #include <vtkCPDataDescription.h>
 #include <vtkMultiBlockDataSet.h>
 #include <vtkInformation.h>
@@ -38,97 +36,47 @@ License
 
 namespace Foam
 {
-namespace functionObjects
+namespace catalyst
 {
-    defineTypeNameAndDebug(catalystCloud, 0);
-    addToRunTimeSelectionTable(functionObject, catalystCloud, dictionary);
+    defineTypeNameAndDebug(cloudInput, 0);
+    addNamedToRunTimeSelectionTable
+    (
+        catalystInput,
+        cloudInput,
+        dictionary,
+        cloud
+    );
 }
 }
 
 
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-bool Foam::functionObjects::catalystCloud::readBasics(const dictionary& dict)
-{
-    int debugLevel = 0;
-    if (dict.readIfPresent("debug", debugLevel))
-    {
-        catalystCoprocess::debug = debugLevel;
-    }
-
-    if (Pstream::master())
-    {
-        fileName dir;
-        if (dict.readIfPresent("mkdir", dir))
-        {
-            dir.expand();
-            dir.clean();
-        }
-        Foam::mkDir(dir);
-    }
-
-    dict.readIfPresent("outputDir", outputDir_);
-    outputDir_.expand();
-    outputDir_.clean();
-    if (Pstream::master())
-    {
-        Foam::mkDir(outputDir_);
-    }
-
-    dict.lookup("scripts") >> scripts_;         // Python scripts
-    catalystCoprocess::expand(scripts_, dict);  // Expand and check availability
-
-    if (adaptor_.valid())
-    {
-        // Run-time modification of pipeline
-        adaptor_().reset(outputDir_, scripts_);
-    }
-
-    return true;
-}
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::functionObjects::catalystCloud::catalystCloud
+Foam::catalyst::cloudInput::cloudInput
 (
     const word& name,
     const Time& runTime,
     const dictionary& dict
 )
 :
-    fvMeshFunctionObject(name, runTime, dict),
-    outputDir_("<case>/insitu"),
-    scripts_(),
-    adaptor_(),
+    catalystInput(name),
+    time_(runTime),
+    regionName_(),
     selectClouds_(),
     selectFields_()
 {
-    if (postProcess)
-    {
-        // Disable for post-process mode.
-        // Emit as FatalError for the try/catch in the caller.
-        FatalError
-            << type() << " disabled in post-process mode"
-            << exit(FatalError);
-    }
     read(dict);
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::functionObjects::catalystCloud::~catalystCloud()
-{}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-bool Foam::functionObjects::catalystCloud::read(const dictionary& dict)
+bool Foam::catalyst::cloudInput::read(const dictionary& dict)
 {
-    fvMeshFunctionObject::read(dict);
+    catalystInput::read(dict);
 
-    readBasics(dict);
+    regionName_ =
+        dict.lookupOrDefault<word>("region", polyMesh::defaultRegion);
 
     selectClouds_.clear();
     dict.readIfPresent("clouds", selectClouds_);
@@ -143,116 +91,101 @@ bool Foam::functionObjects::catalystCloud::read(const dictionary& dict)
     selectFields_.clear();
     dict.readIfPresent("fields", selectFields_);
 
-    Info<< type() << " " << name() << ":" << nl
-        <<"    clouds  " << flatOutput(selectClouds_) << nl
-        <<"    fields  " << flatOutput(selectFields_) << nl
-        <<"    scripts " << scripts_ << nl;
-
     return true;
 }
 
 
-bool Foam::functionObjects::catalystCloud::execute()
+Foam::label Foam::catalyst::cloudInput::addChannels(dataQuery& dataq)
 {
-    const wordList cloudNames(mesh_.sortedNames<cloud>(selectClouds_));
-
-    if (cloudNames.empty())
-    {
-        return true;
-    }
+    const fvMesh& fvm = time_.lookupObject<fvMesh>(regionName_);
 
-    // Enforce sanity for backends and adaptor
+    if (fvm.names<cloud>(selectClouds_).empty())
     {
-        if (!adaptor_.valid())
-        {
-            adaptor_.reset(new catalystCoprocess());
-            adaptor_().reset(outputDir_, scripts_);
-        }
+        return 0;
     }
 
-
     // Difficult to get the names of the fields from particles
     // ... need to skip for now
     wordHashSet allFields;
 
+    dataq.set(name(), allFields);
 
-    // Data description for co-processing
-    vtkNew<vtkCPDataDescription> descrip;
+    return 1;
+}
 
-    // Form data query for catalyst
-    catalystCoprocess::dataQuery dataq
-    (
-        vtk::cloudAdaptor::channelNames.names(),
-        time_,  // timeQuery
-        descrip.Get()
-    );
 
-    // Query catalyst
-    const HashTable<wordHashSet> expecting(adaptor_().query(dataq, allFields));
+bool Foam::catalyst::cloudInput::convert
+(
+    dataQuery& dataq,
+    outputChannels& outputs
+)
+{
+    const fvMesh& fvm = time_.lookupObject<fvMesh>(regionName_);
+    const wordList cloudNames(fvm.sortedNames<cloud>(selectClouds_));
 
-    if (catalystCoprocess::debug)
+    if (cloudNames.empty())
     {
-        if (expecting.empty())
-        {
-            Info<< type() << ": expecting no data" << nl;
-        }
-        else
-        {
-            Info<< type() << ": expecting data " << expecting << nl;
-        }
+        return false;  // skip - not available
     }
 
-    if (expecting.empty())
+    // Single channel only
+
+    label nChannels = 0;
+
+    if (dataq.found(name()))
     {
-        return true;
+        ++nChannels;
     }
 
-    auto output = vtkSmartPointer<vtkMultiBlockDataSet>::New();
+    if (!nChannels)
+    {
+        return false;  // skip - not requested
+    }
 
     // Each cloud in a separate block.
-    unsigned int cloudNo = 0;
+    unsigned int blockNo = 0;
     for (const word& cloudName : cloudNames)
     {
-        auto pieces =
-            vtk::cloudAdaptor(mesh_).getCloud(cloudName, selectFields_);
+        auto dataset =
+            vtk::cloudAdaptor(fvm).getCloud(cloudName, selectFields_);
 
-        output->SetBlock(cloudNo, pieces);
-        output->GetMetaData(cloudNo)->Set
-        (
-            vtkCompositeDataSet::NAME(),
-            cloudName
-        );
-
-        ++cloudNo;
-    }
+        const fileName channel = name();
 
-    if (cloudNo)
-    {
-        Log << type() << ": send data" << nl;
+        if (dataq.found(channel))
+        {
+            // Get existing or new
+            vtkSmartPointer<vtkMultiBlockDataSet> block =
+                outputs.lookup
+                (
+                    channel,
+                    vtkSmartPointer<vtkMultiBlockDataSet>::New()
+                );
+
+            block->SetBlock(blockNo, dataset);
+
+            block->GetMetaData(blockNo)->Set
+            (
+                vtkCompositeDataSet::NAME(),
+                cloudName
+            );
+
+            outputs.set(channel, block);  // overwrites existing
+        }
 
-        adaptor_().process(dataq, output);
+        ++blockNo;
     }
 
     return true;
 }
 
 
-bool Foam::functionObjects::catalystCloud::write()
-{
-    return true;
-}
-
-
-bool Foam::functionObjects::catalystCloud::end()
+Foam::Ostream& Foam::catalyst::cloudInput::print(Ostream& os) const
 {
-    // Only here for extra feedback
-    if (log && adaptor_.valid())
-    {
-        Info<< type() << ": Disconnecting ParaView Catalyst..." << nl;
-    }
+    os  << name() << nl
+        <<"    clouds  " << flatOutput(selectClouds_) << nl
+        <<"    fields  " << flatOutput(selectFields_) << nl;
 
-    adaptor_.clear();
-    return true;
+    return os;
 }
 
 
diff --git a/src/catalyst/cloud/catalystCloud.H b/src/catalyst/cloud/catalystCloud.H
index 1f81166bbdb43983524878ec66a511806ea7b3cc..ef18d71bf7ef3df3e66485aa0a40daeef7fb1580 100644
--- a/src/catalyst/cloud/catalystCloud.H
+++ b/src/catalyst/cloud/catalystCloud.H
@@ -22,54 +22,37 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::functionObjects::catalystCloud
-
-Group
-    grpUtilitiesFunctionObjects
+    Foam::catalystCloud
 
 Description
-    A Paraview Catalyst adaptor for a single OpenFOAM cloud (lagrangian).
+    A Paraview Catalyst source for OpenFOAM clouds (lagrangian).
 
-    The output comprises a single "cloud" channel, which is a multi-block
-    dataset (one block per cloud).
+    The source comprises a single internal "cloud" channel,
+    which is a multi-block dataset (one block per cloud).
+    On output, the "cloud" sub-channel receives the name of the source.
 
-    Example of function object specification:
+    Example specification:
     \verbatim
-    catalystCloud1
+    myCloud
     {
-        type            catalyst::cloud;
-        libs            ("libcatalystFoam.so");
+        type            cloud;
         cloud           NAME;
         fields          (U T rho);
-        scripts         ( ... );
-        executeControl  timeStep;
-        executeInterval 1;
     }
     \endverbatim
 
 Usage
     \table
-        Property     | Description                 | Required    | Default value
-        type         | catalyst::cloud             | yes         |
-        log          | report extra information    | no          | false
-        mkdir        | initial directory to create | no          |
+        Property     | Description                 | Required    | Default
+        type         | cloud                       | yes         |
+        region       |                             | no          | region0
         cloud        |                             | no          | defaultCloud
         clouds       | wordRe list of clouds       | no          |
-        region       |                             | no          | region0
-        regions      | wordRe list of regions      | no          |
         fields       | wordRe list of fields       | yes         |
-        scripts      | Python pipeline scripts     | yes         |
     \endtable
 
-Note
-    The execution frequency can be defined by the functionObject and
-    by the Catalyst pipeline.
-
 See also
-    Foam::functionObjects::functionObject
-    Foam::functionObjects::fvMeshFunctionObject
-    Foam::functionObjects::timeControl
-    Foam::catalystCoprocess
+    Foam::catalyst::catalystInput
     Foam::vtk::cloudAdaptor
 
 SourceFiles
@@ -78,45 +61,36 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef functionObjects_catalystCloud_H
-#define functionObjects_catalystCloud_H
+#ifndef catalyst_cloudInput_H
+#define catalyst_cloudInput_H
 
-#include "fvMeshFunctionObject.H"
 #include "wordRes.H"
+#include "catalystInput.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
-
-// Forward declarations
-
-class catalystCoprocess;
-
-namespace functionObjects
+namespace catalyst
 {
 
 /*---------------------------------------------------------------------------*\
-                        Class catalystCloud Declaration
+                    Class catalyst::cloudInput Declaration
 \*---------------------------------------------------------------------------*/
 
-class catalystCloud
+class cloudInput
 :
-    public fvMeshFunctionObject
+    public catalystInput
 {
 protected:
 
     // Protected data
 
-        //- The output directory
-        fileName outputDir_;
-
-        //- Python scripts for the catalyst pipeline
-        stringList scripts_;
-
-        //- The catalyst coprocess
-        autoPtr<catalystCoprocess> adaptor_;
+        //- Reference to the time database
+        const Time& time_;
 
+        //- The region name for the clouds
+        word regionName_;
 
         //- Requested names of clouds to process
         wordRes selectClouds_;
@@ -127,26 +101,22 @@ protected:
 
     // Protected Member Functions
 
-        //- Common boilerplate settings
-        bool readBasics(const dictionary& dict);
-
-
         //- No copy construct
-        catalystCloud(const catalystCloud&) = delete;
+        cloudInput(const cloudInput&) = delete;
 
         //- No copy assignment
-        void operator=(const catalystCloud&) = delete;
+        void operator=(const cloudInput&) = delete;
 
 public:
 
     //- Runtime type information
-    TypeName("catalyst::cloud");
+    ClassName("catalyst::cloud");
 
 
     // Constructors
 
         //- Construct from Time and dictionary
-        catalystCloud
+        cloudInput
         (
             const word& name,
             const Time& runTime,
@@ -155,7 +125,7 @@ public:
 
 
     //- Destructor
-    virtual ~catalystCloud();
+    virtual ~cloudInput() = default;
 
 
     // Member Functions
@@ -163,20 +133,20 @@ public:
         //- Read the specification
         virtual bool read(const dictionary& dict);
 
-        //- Execute catalyst pipelines
-        virtual bool execute();
+        //- Add available channels (with fields) to data query
+        virtual label addChannels(dataQuery& dataq);
 
-        //- Write - does nothing
-        virtual bool write();
+        //- Convert channels to vtkMultiBlockDataSet outputs
+        virtual bool convert(dataQuery& dataq, outputChannels& outputs);
 
-        //- On end - provide feedback about disconnecting from catatyst.
-        virtual bool end();
+        //- Print information
+        virtual Ostream& print(Ostream& os) const;
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace functionObjects
+} // End namespace catalyst
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/catalyst/cloud/foamVtkCloudAdaptor.C b/src/catalyst/cloud/foamVtkCloudAdaptor.C
index 184196f9f0259a74122376b50a60f605db37c5c5..e9b8b5e76ea3acd36c75a3da6be8b9d300b43880 100644
--- a/src/catalyst/cloud/foamVtkCloudAdaptor.C
+++ b/src/catalyst/cloud/foamVtkCloudAdaptor.C
@@ -37,14 +37,13 @@ License
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
-const Foam::Enum
-<
-    Foam::vtk::cloudAdaptor::channel
->
-Foam::vtk::cloudAdaptor::channelNames
+namespace Foam
 {
-    { channel::CLOUD, "cloud" },
-};
+namespace vtk
+{
+    defineTypeNameAndDebug(cloudAdaptor, 0);
+}
+}
 
 
 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
diff --git a/src/catalyst/cloud/foamVtkCloudAdaptor.H b/src/catalyst/cloud/foamVtkCloudAdaptor.H
index d8c6de02bfeed4835990fdbb76eabca54016965d..72374e9943a06917db1a43c98b7206dd25ba95b8 100644
--- a/src/catalyst/cloud/foamVtkCloudAdaptor.H
+++ b/src/catalyst/cloud/foamVtkCloudAdaptor.H
@@ -42,8 +42,8 @@ SourceFiles
 #ifndef foamVtkCloudAdaptor_H
 #define foamVtkCloudAdaptor_H
 
+#include "className.H"
 #include "fvMesh.H"
-#include "Enum.H"
 #include "foamVtkTools.H"
 
 #include <vtkPolyData.h>
@@ -63,22 +63,7 @@ namespace vtk
 
 class cloudAdaptor
 {
-public:
-
-    //- Public Data
-
-        //- The Catalyst output channels
-        enum class channel
-        {
-            CLOUD
-        };
-
-        static const Enum<channel> channelNames;
-
-
-private:
-
-    // Private Data
+    // Private data
 
         const fvMesh& mesh_;
 
@@ -120,6 +105,11 @@ private:
 
 public:
 
+    //- Static Data Members
+
+        ClassName("vtk::cloudAdaptor");
+
+
     // Constructors
 
         //- Construct for a particular mesh region
diff --git a/src/catalyst/volMesh/catalystFvMesh.C b/src/catalyst/volMesh/catalystFvMesh.C
index 22e86889c784882a0728a5289f3f9f70c91e04ec..bb7fcb95c436efa5b4ac1d2c838f91373ac44e33 100644
--- a/src/catalyst/volMesh/catalystFvMesh.C
+++ b/src/catalyst/volMesh/catalystFvMesh.C
@@ -24,10 +24,9 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "catalystFvMesh.H"
-#include "catalystCoprocess.H"
 #include "addToRunTimeSelectionTable.H"
+#include "fileNameList.H"
 
-#include <vtkNew.h>
 #include <vtkCPDataDescription.h>
 #include <vtkMultiBlockDataSet.h>
 #include <vtkInformation.h>
@@ -36,76 +35,50 @@ License
 
 namespace Foam
 {
-namespace functionObjects
+namespace catalyst
 {
-    defineTypeNameAndDebug(catalystFvMesh, 0);
-    addToRunTimeSelectionTable(functionObject, catalystFvMesh, dictionary);
+    defineTypeNameAndDebug(fvMeshInput, 0);
+    addNamedToRunTimeSelectionTable
+    (
+        catalystInput,
+        fvMeshInput,
+        dictionary,
+        default
+    );
 }
 }
 
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-bool Foam::functionObjects::catalystFvMesh::readBasics(const dictionary& dict)
+Foam::fileName Foam::catalyst::fvMeshInput::channelName(channelEnum chan) const
 {
-    int debugLevel = 0;
-    if (dict.readIfPresent("debug", debugLevel))
-    {
-        catalystCoprocess::debug = debugLevel;
-    }
-
-    if (Pstream::master())
+    if (chan == channelEnum::INPUT)
     {
-        fileName dir;
-        if (dict.readIfPresent("mkdir", dir))
-        {
-            dir.expand();
-            dir.clean();
-        }
-        Foam::mkDir(dir);
+        return name();
     }
 
-    dict.readIfPresent("outputDir", outputDir_);
-    outputDir_.expand();
-    outputDir_.clean();
-    if (Pstream::master())
-    {
-        Foam::mkDir(outputDir_);
-    }
-
-    dict.lookup("scripts") >> scripts_;         // Python scripts
-    catalystCoprocess::expand(scripts_, dict);  // Expand and check availability
-
-    if (adaptor_.valid())
-    {
-        // Run-time modification of pipeline
-        adaptor_().reset(outputDir_, scripts_);
-    }
-
-    return true;
+    return name()/vtk::fvMeshAdaptor::channelNames[chan];
 }
 
 
-void Foam::functionObjects::catalystFvMesh::updateState
-(
-    polyMesh::readUpdateState state
-)
+void Foam::catalyst::fvMeshInput::update()
 {
-    // Trigger change of state
-
-    // Be really paranoid and verify if the mesh actually exists
-    const wordList regionNames(backends_.toc());
+    // Backend requires a corresponding mesh
+    backends_.filterKeys
+    (
+        [this](const word& k){ return meshes_.found(k); }
+    );
 
-    for (const word& regionName : regionNames)
+    forAllConstIters(meshes_, iter)
     {
-        if (meshes_.found(regionName) && time_.found(regionName))
-        {
-            backends_[regionName]->updateState(state);
-        }
-        else
+        if (!backends_.found(iter.key()))
         {
-            backends_.erase(regionName);
-            meshes_.erase(regionName);
+            backends_.insert
+            (
+                iter.key(),
+                new Foam::vtk::fvMeshAdaptor(*(iter.object()), decompose_)
+            );
         }
     }
 }
@@ -113,52 +86,39 @@ void Foam::functionObjects::catalystFvMesh::updateState
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::functionObjects::catalystFvMesh::catalystFvMesh
+Foam::catalyst::fvMeshInput::fvMeshInput
 (
     const word& name,
     const Time& runTime,
     const dictionary& dict
 )
 :
-    functionObject(name),
+    catalystInput(name),
     time_(runTime),
-    outputDir_("<case>/insitu"),
-    scripts_(),
-    adaptor_(),
     selectRegions_(),
     selectFields_(),
+    decompose_(false),
     meshes_(),
     backends_()
 {
-    if (postProcess)
-    {
-        // Disable for post-process mode.
-        // Emit as FatalError for the try/catch in the caller.
-        FatalError
-            << type() << " disabled in post-process mode"
-            << exit(FatalError);
-    }
     read(dict);
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::functionObjects::catalystFvMesh::~catalystFvMesh()
-{}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-bool Foam::functionObjects::catalystFvMesh::read(const dictionary& dict)
+bool Foam::catalyst::fvMeshInput::read(const dictionary& dict)
 {
-    functionObject::read(dict);
-    readBasics(dict);
+    catalystInput::read(dict);
+
+    backends_.clear();
+    selectFields_.clear();
+    selectRegions_.clear();
+    decompose_ = dict.lookupOrDefault("decompose", false);
 
     // All possible meshes
     meshes_ = time_.lookupClass<fvMesh>();
 
-    selectRegions_.clear();
     dict.readIfPresent("regions", selectRegions_);
 
     if (selectRegions_.empty())
@@ -169,58 +129,46 @@ bool Foam::functionObjects::catalystFvMesh::read(const dictionary& dict)
     }
 
     // Restrict to specified meshes
-    meshes_.filterKeys(wordRes(selectRegions_));
+    meshes_.filterKeys(selectRegions_);
 
     dict.lookup("fields") >> selectFields_;
 
-    Info<< type() << " " << name() << ":" << nl
-        <<"    regions " << flatOutput(selectRegions_) << nl
-        <<"    meshes  " << flatOutput(meshes_.sortedToc()) << nl
-        <<"    fields  " << flatOutput(selectFields_) << nl
-        <<"    scripts " << scripts_ << nl;
-
-    // Ensure consistency - only retain backends with corresponding mesh region
-    backends_.retain(meshes_);
-
     return true;
 }
 
 
-bool Foam::functionObjects::catalystFvMesh::execute()
+void Foam::catalyst::fvMeshInput::update(polyMesh::readUpdateState state)
 {
-    const wordList regionNames(meshes_.sortedToc());
+    // Trigger change of state
 
-    if (regionNames.empty())
-    {
-        return false;
-    }
+    // Be really paranoid and verify if the mesh actually exists
+    const wordList regionNames(backends_.toc());
 
-    // Enforce sanity for backends and adaptor
+    for (const word& regionName : regionNames)
     {
-        bool updateAdaptor = false;
-        forAllConstIters(meshes_, iter)
+        if (meshes_.found(regionName) && time_.found(regionName))
         {
-            if (!backends_.found(iter.key()))
-            {
-                backends_.insert
-                (
-                    iter.key(),
-                    new Foam::vtk::fvMeshAdaptor(*(iter.object()))
-                );
-                updateAdaptor = true;
-            }
+            backends_[regionName]->updateState(state);
         }
-
-        if (updateAdaptor && !adaptor_.valid())
+        else
         {
-            adaptor_.reset(new catalystCoprocess());
-            adaptor_().reset(outputDir_, scripts_);
+            backends_.erase(regionName);
+            meshes_.erase(regionName);
         }
     }
+}
 
 
-    // Gather all fields that we know how to convert
+Foam::label Foam::catalyst::fvMeshInput::addChannels(dataQuery& dataq)
+{
+    update();   // Enforce sanity for backends and adaptor
+
+    if (backends_.empty())
+    {
+        return 0;
+    }
 
+    // Gather all fields that we know how to convert
     wordHashSet allFields;
     forAllConstIters(backends_, iter)
     {
@@ -228,178 +176,168 @@ bool Foam::functionObjects::catalystFvMesh::execute()
     }
 
 
-    // Data description for co-processing
-    vtkNew<vtkCPDataDescription> descrip;
+    // This solution may be a temporary measure...
 
-    // Form data query for catalyst
-    catalystCoprocess::dataQuery dataq
-    (
-        vtk::fvMeshAdaptor::channelNames.names(),
-        time_,  // timeQuery
-        descrip.Get()
-    );
+    dataq.set(name(), allFields);  // channel::INPUT
+    dataq.set(channelName(channelEnum::MESH),    allFields);
+    dataq.set(channelName(channelEnum::PATCHES), allFields);
 
-    // Query catalyst
-    const HashTable<wordHashSet> expecting(adaptor_().query(dataq, allFields));
+    return 3;
+}
 
-    if (catalystCoprocess::debug)
+
+bool Foam::catalyst::fvMeshInput::convert
+(
+    dataQuery& dataq,
+    outputChannels& outputs
+)
+{
+    const wordList regionNames(backends_.sortedToc());
+
+    if (regionNames.empty())
     {
-        if (expecting.empty())
-        {
-            Info<< type() << ": expecting no data" << nl;
-        }
-        else
-        {
-            Info<< type() << ": expecting data " << expecting << nl;
-        }
+        return false;  // skip - not available
     }
 
-    if (expecting.empty())
+    // Multi-channel
+
+    // This solution may be a temporary measure...
+
+    unsigned whichChannels = channelEnum::NONE;
+
+    // channel::INPUT
+    if (dataq.found(name()))
     {
-        return true;
+        whichChannels |= channelEnum::INPUT;
+    }
+
+    // channel::MESH
+    if (dataq.found(channelName(channelEnum::MESH)))
+    {
+        whichChannels |= channelEnum::MESH;
+    }
+
+    // channel::PATCHES
+    if (dataq.found(channelName(channelEnum::PATCHES)))
+    {
+        whichChannels |= channelEnum::PATCHES;
+    }
+
+    if (channelEnum::NONE == whichChannels)
+    {
+        return false;  // skip - not requested
     }
 
-    HashTable<vtkSmartPointer<vtkMultiBlockDataSet>> outputs;
 
     // TODO: currently don't rely on the results from expecting much at all
 
-    // Each region in a separate block.
-    unsigned int regionNo = 0;
+    // Each region goes into a separate block.
+    unsigned int blockNo = 0;
     for (const word& regionName : regionNames)
     {
-        // (re)define output channels
-        backends_[regionName]->channels(expecting.toc());
+        // define/redefine output channels
+        backends_[regionName]->channels(whichChannels);
 
         vtkSmartPointer<vtkMultiBlockDataSet> dataset =
             backends_[regionName]->output(selectFields_);
 
+        // MESH = block 0
         {
-            const unsigned int channelNo = 0; // MESH
+            const unsigned int channelNo = 0;
 
-            const word& channelName =
-                vtk::fvMeshAdaptor::channelNames
-                [vtk::fvMeshAdaptor::channel::MESH];
+            const fileName channel(channelName(channelEnum::MESH));
 
-            if (expecting.found(channelName))
+            if (dataq.found(channel))
             {
                 // Get existing or new
                 vtkSmartPointer<vtkMultiBlockDataSet> block =
                     outputs.lookup
                     (
-                        channelName,
+                        channel,
                         vtkSmartPointer<vtkMultiBlockDataSet>::New()
                     );
 
-                block->SetBlock(regionNo, dataset->GetBlock(channelNo));
+                block->SetBlock(blockNo, dataset->GetBlock(channelNo));
 
-                block->GetMetaData(regionNo)->Set
+                block->GetMetaData(blockNo)->Set
                 (
                     vtkCompositeDataSet::NAME(),
                     regionName
                 );
 
-                outputs.set(channelName, block);  // overwrite existing
+                outputs.set(channel, block); // overwrites existing
             }
         }
 
+        // PATCHES = block 1
         {
-            const unsigned int channelNo = 1; // PATCHES
+            const unsigned int channelNo = 1;
 
-            const word& channelName =
-                vtk::fvMeshAdaptor::channelNames
-                [vtk::fvMeshAdaptor::channel::PATCHES];
+            const fileName channel(channelName(channelEnum::PATCHES));
 
-            if (expecting.found(channelName))
+            if (dataq.found(channel))
             {
                 // Get existing or new
                 vtkSmartPointer<vtkMultiBlockDataSet> block =
                     outputs.lookup
                     (
-                        channelName,
+                        channel,
                         vtkSmartPointer<vtkMultiBlockDataSet>::New()
                     );
 
-                block->SetBlock(regionNo, dataset->GetBlock(channelNo));
+                block->SetBlock(blockNo, dataset->GetBlock(channelNo));
 
-                block->GetMetaData(regionNo)->Set
+                block->GetMetaData(blockNo)->Set
                 (
                     vtkCompositeDataSet::NAME(),
                     regionName
                 );
 
-                outputs.set(channelName, block);  // overwrite existing
+                outputs.set(channel, block);  // overwrites existing
             }
         }
 
+        // INPUT
         {
-            const word& channelName =
-                vtk::fvMeshAdaptor::channelNames
-                [vtk::fvMeshAdaptor::channel::INPUT];
+            const fileName channel = name();
 
-            if (expecting.found(channelName))
+            if (dataq.found(channel))
             {
                 // Get existing or new
                 vtkSmartPointer<vtkMultiBlockDataSet> block =
                     outputs.lookup
                     (
-                        channelName,
+                        channel,
                         vtkSmartPointer<vtkMultiBlockDataSet>::New()
                     );
 
-                block->SetBlock(regionNo, dataset);
+                block->SetBlock(blockNo, dataset);
 
-                block->GetMetaData(regionNo)->Set
+                block->GetMetaData(blockNo)->Set
                 (
                     vtkCompositeDataSet::NAME(),
                     regionName
                 );
 
-                outputs.set(channelName, block);  // overwrite existing
+                outputs.set(channel, block);  // overwrites existing
             }
         }
 
-        ++regionNo;
-    }
-
-    if (regionNo)
-    {
-        Log << type() << ": send data" << nl;
-
-        adaptor_().process(dataq, outputs);
-    }
-
-    return true;
-}
-
-
-bool Foam::functionObjects::catalystFvMesh::write()
-{
-    return true;
-}
-
-
-bool Foam::functionObjects::catalystFvMesh::end()
-{
-    // Only here for extra feedback
-    if (log && adaptor_.valid())
-    {
-        Info<< type() << ": Disconnecting ParaView Catalyst..." << nl;
+        ++blockNo;
     }
 
-    adaptor_.clear();
     return true;
 }
 
 
-void Foam::functionObjects::catalystFvMesh::updateMesh(const mapPolyMesh&)
+Foam::Ostream& Foam::catalyst::fvMeshInput::print(Ostream& os) const
 {
-    updateState(polyMesh::TOPO_CHANGE);
-}
-
+    os  << name() << nl
+        <<"    regions " << flatOutput(selectRegions_) << nl
+        <<"    meshes  " << flatOutput(meshes_.sortedToc()) << nl
+        <<"    fields  " << flatOutput(selectFields_) << nl;
 
-void Foam::functionObjects::catalystFvMesh::movePoints(const polyMesh&)
-{
-    updateState(polyMesh::POINTS_MOVED);
+    return os;
 }
 
 
diff --git a/src/catalyst/volMesh/catalystFvMesh.H b/src/catalyst/volMesh/catalystFvMesh.H
index 3a59ef7e9ef635b7480110d991550e56209bbc53..82d4fcf1dc53b47258aaca0840b57c4d4c78591d 100644
--- a/src/catalyst/volMesh/catalystFvMesh.H
+++ b/src/catalyst/volMesh/catalystFvMesh.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2017-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
      \\/     M anipulation  | Copyright (C) 2018 CINECA
 -------------------------------------------------------------------------------
 License
@@ -22,52 +22,42 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::functionObjects::catalystFvMesh
-
-Group
-    grpUtilitiesFunctionObjects
+    Foam::catalyst::fvMeshInput
 
 Description
-    A Paraview Catalyst adaptor for OpenFOAM fvMesh regions.
+    A Paraview Catalyst source for OpenFOAM fvMesh regions.
 
-    The output comprises up to three channels ("input", "mesh", "patches"),
-    each of which is a multi-block dataset.
+    The source comprises up to three internal channels
+    ("input", "mesh", "patches"), each of which is a multi-block dataset.
+    On output, the "input" sub-channel receives the name of the source,
+    while the "mesh" and "patches" sub-channels are prefixed with the
+    name of the source.
 
-    Example of function object specification:
+    Example specification:
     \verbatim
-    catalyst
+    myName
     {
-        type            catalyst;
-        libs            ("libcatalystFoam.so");
+        type            default;
         regions         ( ".*Solid" )
         fields          (U p);
-        scripts         ( ... );
-        executeControl  timeStep;
-        executeInterval 1;
     }
     \endverbatim
 
+    Resulting in three output channels:
+    "myName", "myName/mesh", "myName/patches"
+
 Usage
     \table
-        Property     | Description                 | Required    | Default
-        type         | catalyst                    | yes         |
-        log          | report extra information    | no          | false
-        mkdir        | initial directory to create | no          |
-        region       |                             | no          | region0
-        regions      | wordRe list of regions      | no          |
-        fields       | wordRe list of fields       | yes         |
-        scripts      | Python pipeline scripts     | yes         |
+        Property     | Description                      | Required    | Default
+        type         | default                          | no          | default
+        region       |                                  | no          | region0
+        regions      | wordRe list of regions           | no          |
+        fields       | wordRe list of fields            | yes         |
+        decompose    | decompose polyhedra              | no          | false
     \endtable
 
-Note
-    The execution frequency can be defined by the functionObject and
-    by the Catalyst pipeline.
-
 See also
-    Foam::functionObjects::functionObject
-    Foam::functionObjects::fvMeshFunctionObject
-    Foam::functionObjects::timeControl
-    Foam::catalystCoprocess
+    Foam::catalyst::catalystInput
     Foam::vtk::fvMeshAdaptor
 
 SourceFiles
@@ -76,33 +66,28 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef functionObjects_catalystFvMesh_H
-#define functionObjects_catalystFvMesh_H
+#ifndef catalyst_fvMeshInput_H
+#define catalyst_fvMeshInput_H
 
-#include "functionObject.H"
-#include "foamVtkFvMeshAdaptor.H"
 #include "wordRes.H"
 #include "HashPtrTable.H"
+#include "catalystInput.H"
+#include "foamVtkFvMeshAdaptor.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
-
-// Forward declarations
-
-class catalystCoprocess;
-
-namespace functionObjects
+namespace catalyst
 {
 
 /*---------------------------------------------------------------------------*\
-                          Class catalystFvMesh Declaration
+                    Class catalyst::fvMeshInput Declaration
 \*---------------------------------------------------------------------------*/
 
-class catalystFvMesh
+class fvMeshInput
 :
-    public functionObject
+    public catalystInput
 {
 protected:
 
@@ -111,54 +96,49 @@ protected:
         //- Reference to the time database
         const Time& time_;
 
-        //- The output directory
-        fileName outputDir_;
-
-        //- Python scripts for the catalyst pipeline
-        stringList scripts_;
-
-        //- The catalyst coprocess
-        autoPtr<catalystCoprocess> adaptor_;
-
-
         //- Requested names of regions to process
         wordRes selectRegions_;
 
         //- Names of fields to process
         wordRes selectFields_;
 
+        //- Decompose polyhedra
+        bool decompose_;
+
         //- Pointers to the requested mesh regions
         HashTable<const fvMesh*> meshes_;
 
         //- Backends for OpenFOAM to VTK translation (with internal caching)
         HashPtrTable<vtk::fvMeshAdaptor> backends_;
 
+        //- Use channel enumeration from vtk::fvMeshAdaptor
+        using channelEnum = vtk::fvMeshAdaptor::channel;
 
-    // Protected Member Functions
 
-        //- Common boilerplate settings
-        bool readBasics(const dictionary& dict);
+    // Protected Member Functions
 
-        //- On movement
-        void updateState(polyMesh::readUpdateState state);
+        //- Return full channel name
+        fileName channelName(channelEnum chan) const;
 
+        //- Update/synchronize internals with catalyst backends
+        void update();
 
         //- No copy construct
-        catalystFvMesh(const catalystFvMesh&) = delete;
+        fvMeshInput(const fvMeshInput&) = delete;
 
         //- No copy assignment
-        void operator=(const catalystFvMesh&) = delete;
+        void operator=(const fvMeshInput&) = delete;
 
 public:
 
     //- Runtime type information
-    TypeName("catalyst");
+    ClassName("catalyst::fvMesh");
 
 
     // Constructors
 
         //- Construct from Time and dictionary
-        catalystFvMesh
+        fvMeshInput
         (
             const word& name,
             const Time& runTime,
@@ -167,7 +147,7 @@ public:
 
 
     //- Destructor
-    virtual ~catalystFvMesh();
+    virtual ~fvMeshInput() = default;
 
 
     // Member Functions
@@ -175,27 +155,23 @@ public:
         //- Read the specification
         virtual bool read(const dictionary& dict);
 
-        //- Execute catalyst pipelines
-        virtual bool execute();
-
-        //- Write - does nothing
-        virtual bool write();
-
-        //- On end - provide feedback about disconnecting from catatyst.
-        virtual bool end();
+        //- Update for changes of mesh or mesh point-motion
+        virtual void update(polyMesh::readUpdateState state);
 
-        //- Update for changes of mesh
-        virtual void updateMesh(const mapPolyMesh& mpm);
+        //- Add available channels (with fields) to data query
+        virtual label addChannels(dataQuery& dataq);
 
-        //- Update for mesh point-motion
-        virtual void movePoints(const polyMesh& mesh);
+        //- Convert channels to vtkMultiBlockDataSet outputs
+        virtual bool convert(dataQuery& dataq, outputChannels& outputs);
 
+        //- Print information
+        virtual Ostream& print(Ostream& os) const;
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace functionObjects
+} // End namespace catalyst
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/catalyst/volMesh/foamVtkFvMeshAdaptor.C b/src/catalyst/volMesh/foamVtkFvMeshAdaptor.C
index 180bacd2883f06d19f7ebd575baec22e3258a55f..56f5e1a946e995524974097a075cd986bb89b617 100644
--- a/src/catalyst/volMesh/foamVtkFvMeshAdaptor.C
+++ b/src/catalyst/volMesh/foamVtkFvMeshAdaptor.C
@@ -64,12 +64,13 @@ const Foam::word Foam::vtk::fvMeshAdaptor::internalName("internal");
 
 Foam::vtk::fvMeshAdaptor::fvMeshAdaptor
 (
-    const fvMesh& mesh
+    const fvMesh& mesh,
+    const bool decompose
 )
 :
     mesh_(mesh),
     channels_(INPUT),
-    decomposePoly_(false),
+    decomposePoly_(decompose),
     meshState_(polyMesh::TOPO_CHANGE)
 {}
 
@@ -146,9 +147,6 @@ void Foam::vtk::fvMeshAdaptor::updateContent(const wordRes& selectFields)
 {
     const bool oldDecomp = decomposePoly_;
 
-    // TODO from dictionary
-    //decomposePoly_ = !reader_->GetUseVTKPolyhedron();
-
     // Update cached, saved, unneed values.
 
     HashSet<string> nowActive;
diff --git a/src/catalyst/volMesh/foamVtkFvMeshAdaptor.H b/src/catalyst/volMesh/foamVtkFvMeshAdaptor.H
index ab21352382fef553c02d76d980268705bfdc6b06..025f14aefbf4dde5171be8bbc5867fcdb081599a 100644
--- a/src/catalyst/volMesh/foamVtkFvMeshAdaptor.H
+++ b/src/catalyst/volMesh/foamVtkFvMeshAdaptor.H
@@ -45,8 +45,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef foamVtkFvMeshAdaptor_H
-#define foamVtkFvMeshAdaptor_H
+#ifndef foamvtk_fvMeshAdaptor_H
+#define foamvtk_fvMeshAdaptor_H
 
 #include "className.H"
 #include "fileName.H"
@@ -99,6 +99,7 @@ public:
         //- The Catalyst output channels
         enum channel
         {
+            NONE = 0,
             MESH = 0x1,
             PATCHES = 0x2,
             INPUT = 0x3
@@ -267,7 +268,7 @@ public:
     // Constructors
 
         //- Construct from components
-        fvMeshAdaptor(const fvMesh& mesh);
+        fvMeshAdaptor(const fvMesh& mesh, const bool decompose=false);
 
 
     //- Destructor
diff --git a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/catalystArea b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/catalyst
similarity index 53%
rename from tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/catalystArea
rename to tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/catalyst
index ead19c9e96294f926ed352fcc5f5fd8389faf535..66b2730ddde0157737f3844fe3d427ee705c6963 100644
--- a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/catalystArea
+++ b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/catalyst
@@ -2,20 +2,23 @@
 
 functions
 {
-    catalystArea
+    catalyst
     {
-        #includeEtc "caseDicts/postProcessing/catalyst/area.cfg"
-
-        // Selected fields (words or regex)
-        fields  ( ".*" );
-
-        // Output directory
-        //mkdir   "<case>/images";
-
+        #includeEtc "caseDicts/insitu/catalyst/catalyst.cfg"
         scripts
         (
             "<system>/scripts/writeArea.py"
         );
+
+        inputs
+        {
+            area
+            {
+                type    area;
+
+                fields  ( ".*" );
+            }
+        }
     }
 }
 
diff --git a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/controlDict b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/controlDict
index 338911e4c2ddf4dd97a2b74c9a0f6b89df489bab..2887b91158bad70195b4fd02d5675f7e8c9264d2 100644
--- a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/controlDict
+++ b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/controlDict
@@ -43,6 +43,6 @@ timePrecision       6;
 
 runTimeModifiable   yes;
 
-#include   "catalystArea"
+#include   "catalyst"
 
 // ************************************************************************* //
diff --git a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/scripts/writeArea.py b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/scripts/writeArea.py
index 1911a9da9fc2d9cd404cbe5cb0fdb9cff5290295..93500d1078de369c6279708f763e8584dcfc386e 100644
--- a/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/scripts/writeArea.py
+++ b/tutorials/finiteArea/sphereSurfactantFoam/sphereTransport/system/scripts/writeArea.py
@@ -6,7 +6,7 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'input')
+      input1 = coprocessor.CreateProducer(datadescription, 'area')
       writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
 
       coprocessor.RegisterWriter(writer1, filename='area_%t.vtm', freq=2)
@@ -18,7 +18,7 @@ def CreateCoProcessor():
       self.Pipeline = _CreatePipeline(self, datadescription)
 
   coprocessor = CoProcessor()
-  freqs = {'input': [10]}
+  freqs = {'area': [10]}
   coprocessor.SetUpdateFrequencies(freqs)
   return coprocessor
 
diff --git a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/catalyst b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/catalyst
index ab49f45b0937627a60c4826359389a55eb4eb364..c8a1e6a71f4019f14e0c59728254dcca1b4b2bb7 100644
--- a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/catalyst
+++ b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/catalyst
@@ -4,19 +4,27 @@ functions
 {
     catalyst
     {
-        #includeEtc "caseDicts/postProcessing/catalyst/default.cfg"
-
-        // All regions
-        regions  (".*");
-
-        // Selected fields (words or regex)
-        fields  (T U p);
+        #includeEtc "<etc>/caseDicts/insitu/catalyst/catalyst.cfg"
 
         scripts
         (
             "<system>/scripts/slice1.py"
             "<system>/scripts/writePatches.py"
+            // "<etc>/caseDicts/insitu/catalyst/printChannels.py"
+            // "<etc>/caseDicts/insitu/catalyst/writeAll.py"
         );
+
+        inputs
+        {
+            region
+            {
+                // All regions
+                regions (".*");
+
+                // Selected fields (words or regex)
+                fields  (T U p);
+            }
+        }
     }
 }
 
diff --git a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/slice1.py b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/slice1.py
index 864dba02aebfea515fb6ae4114a70134cc31f386..c3c0feb014ae1ef123bf0ff0a58c19edf64f9f5d 100644
--- a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/slice1.py
+++ b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/slice1.py
@@ -6,7 +6,7 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      mesh = coprocessor.CreateProducer(datadescription, "mesh")
+      mesh = coprocessor.CreateProducer(datadescription, 'region/mesh')
 
       slice1 = Slice(Input=mesh, guiName="Slice1", SliceOffsetValues=[0.0], Triangulatetheslice=1, SliceType="Plane")
       slice1.SliceType.Offset = 0.0
@@ -34,7 +34,7 @@ def CreateCoProcessor():
       self.Pipeline = _CreatePipeline(self, datadescription)
 
   coprocessor = CoProcessor()
-  freqs = {'mesh': [10, 100]}
+  freqs = {'region/mesh': [10, 100]}
   coprocessor.SetUpdateFrequencies(freqs)
   return coprocessor
 
diff --git a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writeMesh.py b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writeMesh.py
index c40f022235937f83e1d9cf8adcf010c245bfe3e3..6b8fdd8d236d7e472e042deff20487251b9129a5 100644
--- a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writeMesh.py
+++ b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writeMesh.py
@@ -6,7 +6,7 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'mesh')
+      input1 = coprocessor.CreateProducer(datadescription, 'region/mesh')
       writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
 
       coprocessor.RegisterWriter(writer1, filename='mesh_%t.vtm', freq=2)
@@ -18,7 +18,7 @@ def CreateCoProcessor():
       self.Pipeline = _CreatePipeline(self, datadescription)
 
   coprocessor = CoProcessor()
-  freqs = {'mesh': [10]}
+  freqs = {'region/mesh': [10]}
   coprocessor.SetUpdateFrequencies(freqs)
   return coprocessor
 
diff --git a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writePatches.py b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writePatches.py
index 067e41d6d12993ffcc68e1e7946d4a3159366e33..de9def18c5405f87f9bef27bb3f5de15977e73a2 100644
--- a/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writePatches.py
+++ b/tutorials/heatTransfer/chtMultiRegionFoam/multiRegionHeater/system/scripts/writePatches.py
@@ -6,7 +6,7 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'patches')
+      input1 = coprocessor.CreateProducer(datadescription, 'region/patches')
       writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
 
       coprocessor.RegisterWriter(writer1, filename='patches_%t.vtm', freq=2)
@@ -18,7 +18,7 @@ def CreateCoProcessor():
       self.Pipeline = _CreatePipeline(self, datadescription)
 
   coprocessor = CoProcessor()
-  freqs = {'patches': [10]}
+  freqs = {'region/patches': [10]}
   coprocessor.SetUpdateFrequencies(freqs)
   return coprocessor
 
diff --git a/tutorials/incompressible/icoFoam/cavity/system/catalyst b/tutorials/incompressible/icoFoam/cavity/system/catalyst
index 4d40d140e5d9a17c467391027f57aa8cfb2f5075..ebb77626e199ee2e2373474f22b72ce311efd1e9 100644
--- a/tutorials/incompressible/icoFoam/cavity/system/catalyst
+++ b/tutorials/incompressible/icoFoam/cavity/system/catalyst
@@ -4,13 +4,7 @@ functions
 {
     catalyst
     {
-        #includeEtc "caseDicts/postProcessing/catalyst/default.cfg"
-
-        // Regions
-        // regions ( );
-
-        // Selected fields (words or regex)
-        fields      (U p);
+        #includeEtc "caseDicts/insitu/catalyst/catalyst.cfg"
 
         scripts
         (
@@ -24,6 +18,15 @@ functions
         // For testing: force endTime of catalyst and simulation
         // timeEnd   0.1;
         // "/endTime"    0.15;
+
+        inputs
+        {
+            region
+            {
+                // Selected fields (words or regex)
+                fields  (U p);
+            }
+        }
     }
 }
 
diff --git a/tutorials/incompressible/icoFoam/cavity/system/scripts/slice1.py b/tutorials/incompressible/icoFoam/cavity/system/scripts/slice1.py
index 39d7d28f6bd40ed6b441fd8db2322349b48c9951..7ded1ab34367dbd92b809aa85b2befa17580ab34 100644
--- a/tutorials/incompressible/icoFoam/cavity/system/scripts/slice1.py
+++ b/tutorials/incompressible/icoFoam/cavity/system/scripts/slice1.py
@@ -6,7 +6,7 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, "input")
+      input1 = coprocessor.CreateProducer(datadescription, 'region')
 
       slice1 = Slice(Input=input1, guiName="Slice1", Crinkleslice=0, SliceOffsetValues=[0.0], Triangulatetheslice=1, SliceType="Plane")
       slice1.SliceType.Offset = 0.0
@@ -26,7 +26,7 @@ def CreateCoProcessor():
       # register the writer with coprocessor
       # and provide it with information such as the filename to use,
       # how frequently to write the data, etc.
-      coprocessor.RegisterWriter(meshWriter, filename='fullgrid_%t.vtm', freq=10)
+      coprocessor.RegisterWriter(meshWriter, filename='region_%t.vtm', freq=10)
 
     return Pipeline()
 
@@ -35,7 +35,7 @@ def CreateCoProcessor():
       self.Pipeline = _CreatePipeline(self, datadescription)
 
   coprocessor = CoProcessor()
-  freqs = {'input': [10, 100]}
+  freqs = {'region': [10, 100]}
   coprocessor.SetUpdateFrequencies(freqs)
   return coprocessor
 
diff --git a/tutorials/incompressible/icoFoam/cavity/system/scripts/writeMesh.py b/tutorials/incompressible/icoFoam/cavity/system/scripts/writeMesh.py
index 576083955b3e07afc416ebc62e7a2d1f653ce11d..443bf24133778f200d0c804de3fec3ea06a1e358 100644
--- a/tutorials/incompressible/icoFoam/cavity/system/scripts/writeMesh.py
+++ b/tutorials/incompressible/icoFoam/cavity/system/scripts/writeMesh.py
@@ -6,7 +6,7 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, "mesh")
+      input1 = coprocessor.CreateProducer(datadescription, 'region/mesh')
       writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
       # Register with filename to use, output frequency
       coprocessor.RegisterWriter(writer1, filename='mesh_%t.vtm', freq=2)
@@ -22,7 +22,7 @@ def CreateCoProcessor():
       self.Pipeline = _CreatePipeline(self, datadescription)
 
   coprocessor = CoProcessor()
-  freqs = {"mesh": [2]}
+  freqs = {'region/mesh': [2]}
   coprocessor.SetUpdateFrequencies(freqs)
   return coprocessor
 
diff --git a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/catalyst b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/catalyst
new file mode 100644
index 0000000000000000000000000000000000000000..3a3aba32b637f2ede6ebfd3ee117d35114df15f2
--- /dev/null
+++ b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/catalyst
@@ -0,0 +1,39 @@
+// ParaView Catalyst function object for OpenFOAM (-*- C++ -*-)
+
+functions
+{
+    catalyst
+    {
+        #includeEtc "caseDicts/insitu/catalyst/catalyst.cfg"
+
+        scripts
+        (
+            "<system>/scripts/writeCloud.py"
+            "<system>/scripts/writePatches.py"
+        );
+
+        inputs
+        {
+            region
+            {
+                type    default;
+
+                // Selected fields (words or regex)
+                fields  ( T U p rho );
+            }
+
+            cloud
+            {
+                type    cloud;
+
+                // Selected clouds (words or regex)
+                clouds  ( coalCloud1 limestoneCloud1 );
+
+                // Selected fields (words or regex)
+                fields  ( T U p rho "Y.*" );
+            }
+        }
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/catalystCloud b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/catalystCloud
deleted file mode 100644
index e9b7336918330cdbbe428c582b9809e27b8e1404..0000000000000000000000000000000000000000
--- a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/catalystCloud
+++ /dev/null
@@ -1,22 +0,0 @@
-// ParaView Catalyst function object for OpenFOAM (-*- C++ -*-)
-
-functions
-{
-    catalystCloud1
-    {
-        #includeEtc "caseDicts/postProcessing/catalyst/cloud.cfg"
-
-        // Selected clouds (words or regex)
-        clouds  ( coalCloud1 limestoneCloud1 );
-
-        // Selected fields (words or regex)
-        fields  ( T U p rho "Y.*" );
-
-        scripts
-        (
-            "<system>/scripts/writeCloud.py"
-        );
-    }
-}
-
-// ************************************************************************* //
diff --git a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict
index d1bc650d884f34a5492c7796614a01c9a4afce74..224ef164a53d1dbb7f4b5f5c6f0899feadd5bca4 100644
--- a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict
+++ b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/controlDict
@@ -51,6 +51,6 @@ maxCo           1.0;
 
 maxDeltaT       1;
 
-#include "catalystCloud"
+#include "catalyst"
 
 // ************************************************************************* //
diff --git a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writeCloud.py b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writeCloud.py
index db348ea49c18683d31f030958c1237c52921fdb6..e6a98c64ea8262b168d0144817ee5f6a2d5aa56a 100644
--- a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writeCloud.py
+++ b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writeCloud.py
@@ -6,11 +6,12 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'cloud')
+      name1 = 'cloud'
+      input1 = coprocessor.CreateProducer(datadescription, name1)
       writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
 
       # register writer with coprocessor, with filename + output freq
-      coprocessor.RegisterWriter(writer1, filename='cloud_%t.vtm', freq=10)
+      coprocessor.RegisterWriter(writer1, filename=name1+'_%t.vtm', freq=10)
 
     return Pipeline()
 
diff --git a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writeMesh.py b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writeMesh.py
deleted file mode 100644
index c40f022235937f83e1d9cf8adcf010c245bfe3e3..0000000000000000000000000000000000000000
--- a/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writeMesh.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from paraview.simple import *
-from paraview import coprocessing
-
-# ----------------------- CoProcessor definition -----------------------
-
-def CreateCoProcessor():
-  def _CreatePipeline(coprocessor, datadescription):
-    class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'mesh')
-      writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
-
-      coprocessor.RegisterWriter(writer1, filename='mesh_%t.vtm', freq=2)
-
-    return Pipeline()
-
-  class CoProcessor(coprocessing.CoProcessor):
-    def CreatePipeline(self, datadescription):
-      self.Pipeline = _CreatePipeline(self, datadescription)
-
-  coprocessor = CoProcessor()
-  freqs = {'mesh': [10]}
-  coprocessor.SetUpdateFrequencies(freqs)
-  return coprocessor
-
-#--------------------------------------------------------------
-# Global variables that will hold the pipeline for each timestep
-# Creating the CoProcessor object, doesn't actually create the ParaView pipeline.
-# It will be automatically setup when coprocessor.UpdateProducers() is called the
-# first time.
-coprocessor = CreateCoProcessor()
-
-#--------------------------------------------------------------
-# Enable Live-Visualizaton with ParaView
-coprocessor.EnableLiveVisualization(True)
-
-# ---------------------- Data Selection method ----------------------
-
-def RequestDataDescription(datadescription):
-    'Callback to populate the request for current timestep'
-    global coprocessor
-    if datadescription.GetForceOutput() == True:
-        # We are just going to request all fields and meshes from the simulation
-        # code/adaptor.
-        for i in range(datadescription.GetNumberOfInputDescriptions()):
-            datadescription.GetInputDescription(i).AllFieldsOn()
-            datadescription.GetInputDescription(i).GenerateMeshOn()
-        return
-
-    # setup requests for all inputs based on the requirements of the
-    # pipeline.
-    coprocessor.LoadRequestedData(datadescription)
-
-# ------------------------ Processing method ------------------------
-
-def DoCoProcessing(datadescription):
-    'Callback to do co-processing for current timestep'
-    global coprocessor
-
-    # Update the coprocessor by providing it the newly generated simulation data.
-    # If the pipeline hasn't been setup yet, this will setup the pipeline.
-    coprocessor.UpdateProducers(datadescription)
-
-    # Write output data, if appropriate.
-    coprocessor.WriteData(datadescription);
-
-    # Write image capture (Last arg: rescale lookup table), if appropriate.
-    coprocessor.WriteImages(datadescription, rescale_lookuptable=False)
-
-    # Live Visualization, if enabled.
-    coprocessor.DoLiveVisualization(datadescription, 'localhost', 22222)
diff --git a/etc/writePatches.py b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writePatches.py
similarity index 92%
rename from etc/writePatches.py
rename to tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writePatches.py
index 067e41d6d12993ffcc68e1e7946d4a3159366e33..fd5f1eeaaa735da64f62a2a4d8b02d6aef08fc9c 100644
--- a/etc/writePatches.py
+++ b/tutorials/lagrangian/coalChemistryFoam/simplifiedSiwek/system/scripts/writePatches.py
@@ -6,10 +6,11 @@ from paraview import coprocessing
 def CreateCoProcessor():
   def _CreatePipeline(coprocessor, datadescription):
     class Pipeline:
-      input1 = coprocessor.CreateProducer(datadescription, 'patches')
+      name1 = 'region/patches'
+      input1 = coprocessor.CreateProducer(datadescription, name1)
       writer1 = servermanager.writers.XMLMultiBlockDataWriter(Input=input1)
 
-      coprocessor.RegisterWriter(writer1, filename='patches_%t.vtm', freq=2)
+      coprocessor.RegisterWriter(writer1, filename=name1+'_%t.vtm', freq=2)
 
     return Pipeline()
 
@@ -18,7 +19,7 @@ def CreateCoProcessor():
       self.Pipeline = _CreatePipeline(self, datadescription)
 
   coprocessor = CoProcessor()
-  freqs = {'patches': [10]}
+  freqs = {'region/patches': [10]}
   coprocessor.SetUpdateFrequencies(freqs)
   return coprocessor