diff --git a/src/runTimePostProcessing/CMakeLists-Project.txt b/src/runTimePostProcessing/CMakeLists-Project.txt
index d64168acbde2fd0de130c189bbef424ba4e72d64..3db266e911ed24d7465da6ace0e073b557c10479 100644
--- a/src/runTimePostProcessing/CMakeLists-Project.txt
+++ b/src/runTimePostProcessing/CMakeLists-Project.txt
@@ -13,13 +13,40 @@ else()
     message(FATAL_ERROR " VTK version is too old - requires VTK6 or newer")
 endif()
 
+#-----------------------------------------------------------------------------
+# Test some characteristics
+set(test_file ${CMAKE_CURRENT_BINARY_DIR}/check_mpi.cxx)
+file(WRITE ${test_file}
+    "#include <vtkMPICommunicator.h>\n"
+    "int main() {\n"
+    "  vtkMPICommunicator* p = vtkMPICommunicator::New();\n"
+    "  p->Delete();\n"
+    "  return 0;\n"
+    "}"
+)
+try_compile(FOAM_USING_VTK_MPI
+    ${CMAKE_CURRENT_BINARY_DIR} ${test_file}
+    LINK_LIBRARIES vtkParallelMPI
+    CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${PARAVIEW_INCLUDE_DIRS}"
+)
+if (FOAM_USING_VTK_MPI)
+    add_definitions(-DFOAM_USING_VTK_MPI)
+    message("Building with VTK MPI")
+    include(vtkMPI)
+else()
+    message(WARNING "==== Building without VTK MPI ====")
+endif()
+
+#-----------------------------------------------------------------------------
+
 include_directories(
     ${LIB_SRC}/OpenFOAM/include
     ${LIB_SRC}/OpenFOAM/lnInclude
     ${LIB_SRC}/OSspecific/${WM_OSTYPE}/lnInclude
     ${LIB_SRC}/finiteVolume/lnInclude
-    ${LIB_SRC}/surfMesh/lnInclude
+    ${LIB_SRC}/fileFormats/lnInclude
     ${LIB_SRC}/conversion/lnInclude
+    ${LIB_SRC}/surfMesh/lnInclude
     ${CMAKE_CURRENT_SOURCE_DIR}
     ${CMAKE_CURRENT_BINARY_DIR}
 )
@@ -45,12 +72,16 @@ set(LIBRARY_OUTPUT_PATH $ENV{FOAM_LIBBIN}
 
 file(GLOB SOURCE_FILES
     fieldVisualisationBase.C
+    scalarBar.C
     functionObjectBase.C
     functionObjectCloud.C
     functionObjectLine.C
     functionObjectSurface.C
     geometryBase.C
+    geometryCloud.C
+    geometryCloudGather.C
     geometryPatches.C
+    geometryPatchesGather.C
     geometrySurface.C
     pathline.C
     pointData.C
@@ -58,16 +89,27 @@ file(GLOB SOURCE_FILES
     runTimePostProcessingFunctionObject.C
     scene.C
     surface.C
+    surfaceGather.C
     text.C
+    contourFilter.C
+    cuttingPlaneFilter.C
+    volumeFilter.C
 )
 
 set(OPENFOAM_LIBRARIES
     OpenFOAM
     finiteVolume
     surfMesh
+    fileFormats
     conversion
 )
 
+if (FOAM_USING_VTK_MPI)
+    set(LINK_LIBRARIES vtkParallelMPI)
+else()
+    set(LINK_LIBRARIES)
+endif()
+
 add_library(
     runTimePostProcessing
     SHARED
@@ -84,6 +126,7 @@ set_target_properties(
 target_link_libraries(
     runTimePostProcessing
     ${VTK_LIBRARIES}
+    ${LINK_LIBRARIES}
     ${OPENFOAM_LIBRARIES}
 )
 
diff --git a/src/runTimePostProcessing/contourFilter.C b/src/runTimePostProcessing/contourFilter.C
new file mode 100644
index 0000000000000000000000000000000000000000..2a6eb421a343e6fdd57bbe8b73a825927cfdb311
--- /dev/null
+++ b/src/runTimePostProcessing/contourFilter.C
@@ -0,0 +1,303 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "contourFilter.H"
+#include "runTimePostProcessing.H"
+#include "addToRunTimeSelectionTable.H"
+
+// VTK includes
+#include "vtkActor.h"
+#include "vtkCellDataToPointData.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkCompositePolyDataMapper.h"
+#include "vtkContourFilter.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPolyData.h"
+#include "vtkPolyDataMapper.h"
+#include "vtkRenderer.h"
+#include "vtkSmartPointer.h"
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+    defineTypeName(contourFilter);
+    addToRunTimeSelectionTable(surface, contourFilter, dictionary);
+}
+}
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::contourFilter::contourFilter
+(
+    const runTimePostProcessing& parent,
+    const dictionary& dict,
+    const HashPtrTable<Function1<vector>>& colours
+)
+:
+    volumeFilter(parent, dict, colours),
+    fieldVisualisationBase(dict, colours),
+    colourFieldName_(dict.get<word>("colourField")),
+    values_()
+{
+    dict.readEntry("values", values_);
+
+    // Extra safety
+    if (values_.empty())
+    {
+        values_.resize(1);
+        values_.first() = Zero;
+    }
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::runTimePostPro::contourFilter::
+addGeometry
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (!visible_)
+    {
+        return false;
+    }
+
+    if (needsCollective())
+    {
+        Info<< type() << " : Not available for collective operation" << endl;
+        return false;
+    }
+
+    DebugInfo << "    Adding iso-surface" << endl;
+
+    // Bookkeeping for vtkUnstructuredGrid
+    vtk::vtuAdaptor adaptor;
+    vtkSmartPointer<vtkMultiPieceDataSet> multiPiece = mesh(adaptor);
+
+
+    // Add (scalar/vector) field.
+    // - always need field(s) for glyphs or colourByField:
+
+    int nCmpt = 0;
+    {
+        const auto* ioptr =
+            parent().mesh().cfindObject<regIOobject>(fieldName_);
+
+        if (!nCmpt)
+        {
+            nCmpt = addDimField<scalar>
+            (
+                multiPiece, adaptor, ioptr, fieldName_
+            );
+        }
+        if (!nCmpt)
+        {
+            nCmpt = addDimField<vector>
+            (
+                multiPiece, adaptor, ioptr, fieldName_
+            );
+        }
+    }
+
+
+    // If the input is vector, need magnitude
+
+    word magFieldName = fieldName_;
+
+    if (nCmpt == 3)
+    {
+        addMagField(fieldName_, multiPiece);
+        magFieldName = "mag(" + fieldName_ + ")";
+    }
+
+    // Colouring
+    nCmpt = 0;
+    if (colourBy_ == cbField && fieldName_ != colourFieldName_)
+    {
+        const auto* ioptr =
+            parent().mesh().cfindObject<regIOobject>(fieldName_);
+
+        if (!nCmpt)
+        {
+            nCmpt = addDimField<scalar>
+            (
+                multiPiece, adaptor, ioptr, colourFieldName_
+            );
+        }
+        if (!nCmpt)
+        {
+            nCmpt = addDimField<vector>
+            (
+                multiPiece, adaptor, ioptr, colourFieldName_
+            );
+        }
+    }
+
+
+    // Now have a multi-piece dataset that is one of the following:
+    //
+    // - one-piece per processor (OpenFOAM = parallel, VTK=parallel)
+
+
+    // Re-query field information - we may have stored it differently
+    // than the original source.
+
+    fieldSummary fieldInfo = queryFieldSummary(magFieldName, multiPiece);
+    fieldInfo.reduce();
+
+    fieldSummary colourFieldInfo =
+        queryFieldSummary(colourFieldName_, multiPiece);
+    colourFieldInfo.reduce();
+
+
+    // Not rendered on this processor?
+    // This is where we stop, but could also have an MPI barrier
+    if (!renderer)
+    {
+        return true;
+    }
+
+
+    // Rendering
+    {
+        auto contour = vtkSmartPointer<vtkContourFilter>::New();
+
+        vtkSmartPointer<vtkCellDataToPointData> cellToPoint;
+
+        // CellData - Need a cell->point filter
+        if (!fieldInfo.hasPointData() || !colourFieldInfo.hasPointData())
+        {
+            cellToPoint = vtkSmartPointer<vtkCellDataToPointData>::New();
+            cellToPoint->SetInputData(multiPiece);
+
+            contour->SetInputConnection(cellToPoint->GetOutputPort());
+        }
+        else
+        {
+            contour->SetInputData(multiPiece);
+        }
+
+        contour->SetNumberOfContours(values_.size());
+        forAll(values_, valuei)
+        {
+            contour->SetValue(valuei, values_[valuei]);
+        }
+
+        contour->SetInputArrayToProcess
+        (
+            0,  // index: scalars(0)
+            0,  // port
+            0,  // connection
+            vtkDataObject::FIELD_ASSOCIATION_POINTS,
+            magFieldName.c_str()
+        );
+
+        contour->Modified();
+        contour->Update();
+
+        auto polyData = vtkSmartPointer<vtkCompositeDataGeometryFilter>::New();
+
+        polyData->SetInputConnection(contour->GetOutputPort());
+        polyData->Update();
+
+        auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
+        mapper->SetInputConnection(polyData->GetOutputPort());
+
+        if (representation_ == rtGlyph)
+        {
+            addGlyphs
+            (
+                position,
+                colourFieldName_, colourFieldInfo,  // scaling
+                colourFieldName_, colourFieldInfo,  // colouring
+                maxGlyphLength_,
+                polyData->GetOutput(),
+                surfaceActor_,
+                renderer
+            );
+        }
+        else
+        {
+            setField
+            (
+                position,
+                colourFieldName_,
+                FieldAssociation::POINT_DATA,
+                mapper,
+                renderer
+            );
+
+            surfaceActor_->SetMapper(mapper);
+
+            setRepresentation(surfaceActor_);
+
+            renderer->AddActor(surfaceActor_);
+        }
+    }
+
+    return true;
+}
+
+
+void Foam::functionObjects::runTimePostPro::contourFilter::
+addGeometryToScene
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (visible_)
+    {
+        // Live source
+        if (addGeometry(position, renderer))
+        {
+            return;
+        }
+
+        WarningInFunction
+            << "Unsupported for OpenFOAM parallel and VTK serial"
+            << endl;
+    }
+}
+
+
+bool Foam::functionObjects::runTimePostPro::contourFilter::clear()
+{
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/contourFilter.H b/src/runTimePostProcessing/contourFilter.H
new file mode 100644
index 0000000000000000000000000000000000000000..403052d43dc0c4383fc7872c47b9306c4a4bfd61
--- /dev/null
+++ b/src/runTimePostProcessing/contourFilter.H
@@ -0,0 +1,140 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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::functionObjects::runTimePostPro::contourFilter
+
+Description
+    Iso-surface contours of OpenFOAM volume fields.
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        type        | The surface type: isoSurface          | yes |
+        field       | The field defining the surface        | yes |
+        colourField | The field to display on the surface   | yes |
+        values      | List of iso-values to define the surface(s) | yes |
+    \endtable
+
+SourceFiles
+    contourFilter.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_runTimePostPro_contourFilter_H
+#define functionObjects_runTimePostPro_contourFilter_H
+
+#include "volumeFilter.H"
+#include "fieldVisualisationBase.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class contourFilter Declaration
+\*---------------------------------------------------------------------------*/
+
+class contourFilter
+:
+    public volumeFilter,
+    public fieldVisualisationBase
+{
+protected:
+
+    // Protected Data
+
+        //- Name of field to colour by
+        word colourFieldName_;
+
+        //- The iso values
+        List<scalar> values_;
+
+
+    // Protected Member Functions
+
+        //- No copy construct
+        contourFilter(const contourFilter&) = delete;
+
+        //- No copy assignment
+        void operator=(const contourFilter&) = delete;
+
+
+public:
+
+    //- Run-time type information
+    TypeNameNoDebug("isoSurface");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        contourFilter
+        (
+            const runTimePostProcessing& parent,
+            const dictionary& dict,
+            const HashPtrTable<Function1<vector>>& colours
+        );
+
+
+    //- Destructor
+    virtual ~contourFilter() = default;
+
+
+    // Member Functions
+
+        //- Add cutting planes to scene (using simulation source)
+        bool addGeometry
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Add cutting planes to scene (using simulation source)
+        virtual void addGeometryToScene
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Add cutting planes to scene (using simulation source)
+        virtual bool clear();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace runTimePostPro
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/cuttingPlaneFilter.C b/src/runTimePostProcessing/cuttingPlaneFilter.C
new file mode 100644
index 0000000000000000000000000000000000000000..929d5858acfef046db7dfe000849b08d78273579
--- /dev/null
+++ b/src/runTimePostProcessing/cuttingPlaneFilter.C
@@ -0,0 +1,292 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "cuttingPlaneFilter.H"
+#include "runTimePostProcessing.H"
+#include "addToRunTimeSelectionTable.H"
+
+// VTK includes
+#include "vtkActor.h"
+#include "vtkCellDataToPointData.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkCompositePolyDataMapper.h"
+#include "vtkCutter.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPlane.h"
+#include "vtkPolyData.h"
+#include "vtkPolyDataMapper.h"
+#include "vtkRenderer.h"
+#include "vtkSmartPointer.h"
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+    defineTypeName(cuttingPlaneFilter);
+    addToRunTimeSelectionTable(surface, cuttingPlaneFilter, dictionary);
+}
+}
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::cuttingPlaneFilter::cuttingPlaneFilter
+(
+    const runTimePostProcessing& parent,
+    const dictionary& dict,
+    const HashPtrTable<Function1<vector>>& colours
+)
+:
+    volumeFilter(parent, dict, colours),
+    fieldVisualisationBase(dict, colours),
+    plane_(dict),
+    values_()
+{
+    dict.readIfPresent("offsets", values_);
+
+    if (values_.empty())
+    {
+        values_.resize(1);
+        values_.first() = Zero;
+    }
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::runTimePostPro::cuttingPlaneFilter::
+addGeometry
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (!visible_)
+    {
+        return false;
+    }
+
+    if (needsCollective())
+    {
+        Info<< type() << " : Not available for collective operation" << endl;
+        return false;
+    }
+
+    DebugInfo << "    Adding cutting plane" << endl;
+
+
+    // Bookkeeping for vtkUnstructuredGrid
+    vtk::vtuAdaptor adaptor;
+    vtkSmartPointer<vtkMultiPieceDataSet> multiPiece = mesh(adaptor);
+
+
+    // Add (scalar/vector) field.
+    // - Need field(s) for glyphs or colourByField:
+
+    int nCmpt = 0;
+    if (representation_ == rtGlyph || colourBy_ == cbField)
+    {
+        const auto* ioptr =
+            parent().mesh().cfindObject<regIOobject>(fieldName_);
+
+        if (!nCmpt)
+        {
+            nCmpt = addDimField<scalar>
+            (
+                multiPiece, adaptor, ioptr, fieldName_
+            );
+        }
+        if (!nCmpt)
+        {
+            nCmpt = addDimField<vector>
+            (
+                multiPiece, adaptor, ioptr, fieldName_
+            );
+        }
+    }
+
+
+    // Now have a multi-piece dataset that is one of the following:
+    //
+    // - one-piece per processor (OpenFOAM = parallel, VTK=parallel)
+
+
+    // Re-query field information - we may have stored it differently
+    // than the original source.
+
+    fieldSummary fieldInfo = queryFieldSummary(fieldName_, multiPiece);
+    fieldInfo.reduce();
+
+
+    // Not rendered on this processor?
+    // This is where we stop, but could also have an MPI barrier
+    if (!renderer)
+    {
+        return true;
+    }
+
+
+    // Rendering
+    {
+        // OpenFOAM plane -> vtkPlane definition
+
+        auto pln = vtkSmartPointer<vtkPlane>::New();
+
+        pln->SetNormal
+        (
+            plane_.normal().x(),
+            plane_.normal().y(),
+            plane_.normal().z()
+        );
+        pln->SetOrigin
+        (
+            plane_.origin().x(),
+            plane_.origin().y(),
+            plane_.origin().z()
+        );
+
+
+        // Plane cutting algorithm
+
+        auto cutter = vtkSmartPointer<vtkCutter>::New();
+
+        cutter->SetInputData(multiPiece);
+        cutter->SetCutFunction(pln);
+
+        cutter->SetNumberOfContours(values_.size());
+
+        forAll(values_, pointi)
+        {
+            cutter->SetValue(pointi, values_[pointi]);
+        }
+
+        cutter->SetInputArrayToProcess
+        (
+            (nCmpt == 3 ? 1 : 0), // index: scalars(0), vectors(1)
+            0, // port
+            0, // connection
+            vtkDataObject::FIELD_ASSOCIATION_CELLS,
+            fieldName_.c_str()
+        );
+
+        cutter->Modified();
+        cutter->Update();
+
+        auto polyData = vtkSmartPointer<vtkCompositeDataGeometryFilter>::New();
+
+        polyData->SetInputConnection(cutter->GetOutputPort());
+        polyData->Update();
+
+        auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
+        mapper->SetInputConnection(polyData->GetOutputPort());
+
+        if (representation_ == rtGlyph)
+        {
+            addGlyphs
+            (
+                position,
+                fieldName_, fieldInfo,  // scaling
+                fieldName_, fieldInfo,  // colouring
+                maxGlyphLength_,
+                polyData->GetOutput(),
+                surfaceActor_,
+                renderer
+            );
+        }
+        else
+        {
+            vtkSmartPointer<vtkCellDataToPointData> cellToPoint;
+
+            // CellData - Need a cell->point filter
+            if (smooth_ && !fieldInfo.hasPointData())
+            {
+                cellToPoint = vtkSmartPointer<vtkCellDataToPointData>::New();
+                cellToPoint->SetInputConnection(cutter->GetOutputPort());
+
+                polyData->SetInputConnection(cellToPoint->GetOutputPort());
+                polyData->Update();
+            }
+
+            setField
+            (
+                position,
+                fieldName_,
+                (
+                    smooth_
+                  ? FieldAssociation::POINT_DATA
+                  : FieldAssociation(fieldInfo.association_)
+                ),
+                mapper,
+                renderer
+            );
+
+            surfaceActor_->SetMapper(mapper);
+
+            setRepresentation(surfaceActor_);
+
+            renderer->AddActor(surfaceActor_);
+        }
+    }
+
+    return true;
+}
+
+
+void Foam::functionObjects::runTimePostPro::cuttingPlaneFilter::
+addGeometryToScene
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (visible_)
+    {
+        // Live source
+        if (addGeometry(position, renderer))
+        {
+            return;
+        }
+
+        WarningInFunction
+            << "Unsupported for OpenFOAM parallel and VTK serial"
+            << endl;
+    }
+}
+
+
+bool Foam::functionObjects::runTimePostPro::cuttingPlaneFilter::clear()
+{
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/cuttingPlaneFilter.H b/src/runTimePostProcessing/cuttingPlaneFilter.H
new file mode 100644
index 0000000000000000000000000000000000000000..1c89db28e00ad1e457a59fb83c407a0ce38da993
--- /dev/null
+++ b/src/runTimePostProcessing/cuttingPlaneFilter.H
@@ -0,0 +1,160 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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::functionObjects::runTimePostPro::cuttingPlaneFilter
+
+Description
+    Cutting planes of OpenFOAM volume fields.
+
+    Example of text object specification:
+    \verbatim
+    planes
+    {
+        type            plane;
+        planeType       pointAndNormal;
+
+        pointAndNormalDict
+        {
+                point   (0 0 0);
+                normal  (1 0 0);
+        }
+
+        offsets         (0 10 20);
+
+        field    T;
+    }
+    \endverbatim
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        type        | The surface type: plane               | yes |
+        planeType   | Selector for plane description        | yes |
+        offsets     | Offets of the origin in the normal direction | no | (0)
+        field       | The field to display                  | yes |
+    \endtable
+
+SourceFiles
+    cuttingPlaneFilter.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_runTimePostPro_cuttingPlaneFilter_H
+#define functionObjects_runTimePostPro_cuttingPlaneFilter_H
+
+#include "plane.H"
+#include "volumeFilter.H"
+#include "fieldVisualisationBase.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+
+/*---------------------------------------------------------------------------*\
+                    Class cuttingPlaneFilter Declaration
+\*---------------------------------------------------------------------------*/
+
+class cuttingPlaneFilter
+:
+    public volumeFilter,
+    public fieldVisualisationBase
+{
+protected:
+
+    // Protected Data
+
+        //- The definition of the plane
+        plane plane_;
+
+        //- The offsets to the plane - defaults to (0).
+        List<scalar> values_;
+
+
+    // Protected Member Functions
+
+        //- No copy construct
+        cuttingPlaneFilter(const cuttingPlaneFilter&) = delete;
+
+        //- No copy assignment
+        void operator=(const cuttingPlaneFilter&) = delete;
+
+
+public:
+
+    //- Run-time type information
+    TypeNameNoDebug("plane");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        cuttingPlaneFilter
+        (
+            const runTimePostProcessing& parent,
+            const dictionary& dict,
+            const HashPtrTable<Function1<vector>>& colours
+        );
+
+
+    //- Destructor
+    virtual ~cuttingPlaneFilter() = default;
+
+
+    // Member Functions
+
+        //- Add cutting planes to scene (using simulation source)
+        bool addGeometry
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Add cutting planes to scene (using simulation source)
+        virtual void addGeometryToScene
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Cleanup files etc.
+        virtual bool clear();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace runTimePostPro
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/fieldVisualisationBase.C b/src/runTimePostProcessing/fieldVisualisationBase.C
index 5a2f58388b20b9d63d6a8a8e73b5fd50e126dc5b..08c3f6a44c477d39c7a7ce4d48c22a6d68774b36 100644
--- a/src/runTimePostProcessing/fieldVisualisationBase.C
+++ b/src/runTimePostProcessing/fieldVisualisationBase.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -27,25 +27,27 @@ License
 #include "fieldVisualisationBase.H"
 #include "runTimePostProcessing.H"
 
+#include "doubleVector.H"
+#include "foamVtkTools.H"
+
 // VTK includes
 #include "vtkArrowSource.h"
+#include "vtkCellDataToPointData.h"
 #include "vtkCellData.h"
 #include "vtkColorTransferFunction.h"
-#include "vtkFloatArray.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkDataObjectTreeIterator.h"
+#include "vtkFieldData.h"
 #include "vtkGlyph3D.h"
 #include "vtkLookupTable.h"
 #include "vtkPointData.h"
 #include "vtkPolyData.h"
 #include "vtkPolyDataMapper.h"
 #include "vtkRenderer.h"
-#include "vtkScalarBarActor.h"
 #include "vtkSmartPointer.h"
 #include "vtkSphereSource.h"
-#include "vtkTextActor.h"
-#include "vtkTextProperty.h"
-#include "vtkCellDataToPointData.h"
 
-// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
 const Foam::Enum
 <
@@ -67,13 +69,323 @@ const Foam::Enum
 Foam::functionObjects::runTimePostPro::fieldVisualisationBase::
 colourMapTypeNames
 ({
-    { colourMapType::cmRainbow, "rainbow" },
-    { colourMapType::cmBlueWhiteRed, "blueWhiteRed" },
+    { colourMapType::cmCoolToWarm, "coolToWarm" },
+    { colourMapType::cmCoolToWarm, "blueWhiteRed" },
+    { colourMapType::cmColdAndHot, "coldAndHot" },
     { colourMapType::cmFire, "fire" },
+    { colourMapType::cmRainbow, "rainbow" },
     { colourMapType::cmGreyscale, "greyscale" },
+    { colourMapType::cmGreyscale, "grayscale" },
+    { colourMapType::cmXray, "xray" },
 });
 
 
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::fieldSummary
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::
+queryFieldSummary
+(
+    const word& fieldName,
+    vtkDataSet* dataset
+)
+{
+    fieldSummary queried;
+
+    if (dataset)
+    {
+        vtkDataArray* array;
+
+        array = vtkDataArray::SafeDownCast
+        (
+            dataset->GetCellData()->GetAbstractArray(fieldName.c_str())
+        );
+
+        if (array)
+        {
+            queried.nComponents_ = array->GetNumberOfComponents();
+            queried.association_ |= FieldAssociation::CELL_DATA;
+            queried.range_ += vtk::Tools::rangeOf(array);
+        }
+
+        array = vtkDataArray::SafeDownCast
+        (
+            dataset->GetPointData()->GetAbstractArray(fieldName.c_str())
+        );
+
+        if (array)
+        {
+            queried.nComponents_ = array->GetNumberOfComponents();
+            queried.association_ |= FieldAssociation::POINT_DATA;
+            queried.range_ += vtk::Tools::rangeOf(array);
+        }
+    }
+
+    return queried;
+}
+
+
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::fieldSummary
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::
+queryFieldSummary
+(
+    const word& fieldName,
+    vtkCompositeDataSet* data
+)
+{
+    fieldSummary queried;
+
+    auto iter = vtkSmartPointer<vtkDataObjectTreeIterator>::New();
+
+    iter->SetDataSet(data);
+    iter->VisitOnlyLeavesOn();
+    iter->SkipEmptyNodesOn();
+
+    for
+    (
+        iter->InitTraversal();
+        !iter->IsDoneWithTraversal();
+        iter->GoToNextItem()
+    )
+    {
+        vtkDataSet* dataset = vtkDataSet::SafeDownCast
+        (
+            iter->GetCurrentDataObject()
+        );
+
+        if (dataset)
+        {
+            fieldSummary local(queryFieldSummary(fieldName, dataset));
+
+            if (!queried.nComponents_)
+            {
+                queried.nComponents_ = local.nComponents_;
+            }
+
+            queried.association_ |= local.association_;
+            queried.range_ += local.range_;
+        }
+    }
+
+    return queried;
+}
+
+
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::FieldAssociation
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::
+queryFieldAssociation
+(
+    const word& fieldName,
+    vtkDataSet* dataset
+)
+{
+    unsigned where(FieldAssociation::NO_DATA);
+
+    if (dataset)
+    {
+        if (dataset->GetCellData()->HasArray(fieldName.c_str()))
+        {
+            where |= FieldAssociation::CELL_DATA;
+        }
+        if (dataset->GetPointData()->HasArray(fieldName.c_str()))
+        {
+            where |= FieldAssociation::POINT_DATA;
+        }
+    }
+
+    return FieldAssociation(where);
+}
+
+
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::FieldAssociation
+Foam::functionObjects::runTimePostPro::fieldVisualisationBase::
+queryFieldAssociation
+(
+    const word& fieldName,
+    vtkCompositeDataSet* data
+)
+{
+    unsigned where(FieldAssociation::NO_DATA);
+
+    auto iter = vtkSmartPointer<vtkDataObjectTreeIterator>::New();
+
+    iter->SetDataSet(data);
+    iter->VisitOnlyLeavesOn();
+    iter->SkipEmptyNodesOn();
+
+    for
+    (
+        iter->InitTraversal();
+        !iter->IsDoneWithTraversal();
+        iter->GoToNextItem()
+    )
+    {
+        vtkDataSet* dataset = vtkDataSet::SafeDownCast
+        (
+            iter->GetCurrentDataObject()
+        );
+
+        where |= queryFieldAssociation(fieldName, dataset);
+    }
+
+    return FieldAssociation(where);
+}
+
+
+void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::addMagField
+(
+    const word& fieldName,
+    vtkFieldData* fieldData
+)
+{
+    if (!fieldData)
+    {
+        return;
+    }
+
+    vtkDataArray* input = vtkDataArray::SafeDownCast
+    (
+        fieldData->GetAbstractArray(fieldName.c_str())
+    );
+
+    if (!input)
+    {
+        return;
+    }
+
+    const word magFieldName = "mag(" + fieldName + ")";
+
+    vtkDataArray* output = vtkDataArray::SafeDownCast
+    (
+        fieldData->GetAbstractArray(magFieldName.c_str())
+    );
+
+    if (output)
+    {
+        return;
+    }
+
+
+    // Simplfy and only handle scalar/vector input
+
+    const int nCmpt = input->GetNumberOfComponents();
+    const vtkIdType len = input->GetNumberOfTuples();
+
+    if (nCmpt == 1)
+    {
+        auto data = vtkSmartPointer<vtkFloatArray>::New();
+
+        data->SetName(magFieldName.c_str());
+        data->SetNumberOfComponents(1);
+        data->SetNumberOfTuples(len);
+
+        double scratch;
+        for (vtkIdType i=0; i < len; ++i)
+        {
+            input->GetTuple(i, &scratch);
+
+            scratch = Foam::mag(scratch);
+            data->SetTuple(i, &scratch);
+        }
+
+        fieldData->AddArray(data);
+    }
+    else if (nCmpt == 3)
+    {
+        auto data = vtkSmartPointer<vtkFloatArray>::New();
+
+        data->SetName(magFieldName.c_str());
+        data->SetNumberOfComponents(1);
+        data->SetNumberOfTuples(len);
+
+        doubleVector scratch;
+        for (vtkIdType i=0; i < len; ++i)
+        {
+            input->GetTuple(i, scratch.v_);
+
+            scratch.x() = Foam::mag(scratch);
+
+            data->SetTuple(i, scratch.v_);
+        }
+
+        fieldData->AddArray(data);
+    }
+}
+
+
+void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::addMagField
+(
+    const word& fieldName,
+    vtkDataSet* dataset
+)
+{
+    if (dataset)
+    {
+        addMagField(fieldName, dataset->GetCellData());
+        addMagField(fieldName, dataset->GetPointData());
+    }
+}
+
+
+void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::addMagField
+(
+    const word& fieldName,
+    vtkCompositeDataSet* data
+)
+{
+    auto iter = vtkSmartPointer<vtkDataObjectTreeIterator>::New();
+
+    iter->SetDataSet(data);
+    iter->VisitOnlyLeavesOn();
+    iter->SkipEmptyNodesOn();
+
+    for
+    (
+        iter->InitTraversal();
+        !iter->IsDoneWithTraversal();
+        iter->GoToNextItem()
+    )
+    {
+        vtkDataSet* dataset = vtkDataSet::SafeDownCast
+        (
+            iter->GetCurrentDataObject()
+        );
+        addMagField(fieldName, dataset);
+    }
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::
+fieldSummary::reduce()
+{
+    if (Pstream::parRun())
+    {
+        Foam::reduce(nComponents_, maxOp<int>());
+        Foam::reduce(association_, bitOrOp<unsigned>());
+        Foam::reduce(range_, minMaxOp<scalar>());
+    }
+}
+
+
+Foam::Ostream& Foam::operator<<
+(
+    Ostream& os,
+    const InfoProxy
+    <
+        functionObjects::runTimePostPro::fieldVisualisationBase::fieldSummary
+    >& proxy
+)
+{
+    os  << "nComponents:" << proxy.t_.nComponents_
+        << " association:" << label(proxy.t_.association_)
+        << " min/max:" << proxy.t_.range_;
+
+    return os;
+}
+
+
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
 void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::
@@ -82,7 +394,7 @@ setColourMap
     vtkLookupTable* lut
 ) const
 {
-    label nColours = 256;
+    constexpr label nColours = 256;
 
     lut->SetNumberOfColors(nColours);
 
@@ -90,47 +402,73 @@ setColourMap
 
     switch (colourMap_)
     {
-        case cmRainbow:
-        {
-            ctf->SetColorSpaceToHSV();
-            ctf->AddRGBPoint(0, 0, 0, 1);
-            ctf->AddRGBPoint(0.5, 0, 1, 0);
-            ctf->AddRGBPoint(1, 1, 0, 0);
-            break;
-        }
-        case cmBlueWhiteRed:
+        case cmCoolToWarm:  // ParaView: "Cool To Warm"
         {
-            // Values taken from ParaView settings
             ctf->SetColorSpaceToDiverging();
-            ctf->AddRGBPoint(0.0, 0.231373, 0.298039, 0.752941);
+            ctf->AddRGBPoint(0.0, 0.231372, 0.298039, 0.752941);
             ctf->AddRGBPoint(0.5, 0.865003, 0.865003, 0.865003);
             ctf->AddRGBPoint(1.0, 0.705882, 0.0156863, 0.14902);
+            // ctf->SetNanColor(1, 1, 0);
+            break;
+        }
+
+        case cmColdAndHot:  // ParaView : "Cold and Hot"
+        {
+            ctf->SetColorSpaceToRGB();
+            ctf->AddRGBPoint(0, 0, 1, 1);
+            ctf->AddRGBPoint(0.45, 0, 0, 1);
+            ctf->AddRGBPoint(0.5, 0, 0, 0.5019608);
+            ctf->AddRGBPoint(0.55, 1, 0, 0);
+            ctf->AddRGBPoint(1, 1, 1, 0);
             break;
         }
-        case cmFire:
+
+        case cmFire:  // ParaView: Black-Body Radiation
         {
-            // Values taken from ParaView settings
             ctf->SetColorSpaceToRGB();
             ctf->AddRGBPoint(0, 0, 0, 0);
             ctf->AddRGBPoint(0.4, 0.901961, 0, 0);
             ctf->AddRGBPoint(0.8, 0.901961, 0.901961, 0);
             ctf->AddRGBPoint(1, 1, 1, 1);
+            // ctf->SetNanColor(0, 0.49804, 1);
+            break;
+        }
+
+        case cmRainbow:
+        {
+            ctf->SetColorSpaceToHSV();
+            ctf->AddRGBPoint(0, 0, 0, 1);
+            ctf->AddRGBPoint(0.5, 0, 1, 0);
+            ctf->AddRGBPoint(1, 1, 0, 0);
+            // ctf->SetNanColor(0.498039, 0.498039, 0.498039);
             break;
         }
-        case cmGreyscale:
+
+        case cmGreyscale: // ParaView: grayscale
         {
             ctf->SetColorSpaceToRGB();
             ctf->AddRGBPoint(0, 0, 0, 0);
             ctf->AddRGBPoint(1, 1, 1, 1);
+            // ctf->SetNanColor(1, 0, 0);
+            break;
+        }
+
+        case cmXray: // ParaView: "X ray"
+        {
+            ctf->SetColorSpaceToRGB();
+            ctf->AddRGBPoint(0, 1, 1, 1);
+            ctf->AddRGBPoint(1, 0, 0, 0);
+            // ctf->SetNanColor(1, 0, 0);
             break;
         }
     }
 
 
-    for (label i = 0; i < nColours; i++)
+    double rgba[4] = { 0, 0, 0, 1 };
+    for (label i = 0; i < nColours; ++i)
     {
-        double* c = ctf->GetColor(scalar(i)/scalar(nColours));
-        lut->SetTableValue(i, c[0], c[1], c[2], 1.0);
+        ctf->GetColor(scalar(i)/scalar(nColours), rgba);
+        lut->SetTableValue(i, rgba);
     }
 }
 
@@ -143,105 +481,11 @@ addScalarBar
     vtkLookupTable* lut
 ) const
 {
-    // Add scalar bar legend
-    if (!scalarBar_.visible_)
-    {
-        return;
-    }
-
-    auto sbar = vtkSmartPointer<vtkScalarBarActor>::New();
-    sbar->SetLookupTable(lut);
-    sbar->SetNumberOfLabels(scalarBar_.numberOfLabels_);
-
-    const vector textColour = colours_["text"]->value(position);
-
-    // Work-around to supply our own scalarbar title
-    // - Default scalar bar title text is scales by the scalar bar box
-    //   dimensions so if the title is a long string, the text is shrunk to fit
-    //   Instead, suppress title and set the title using a vtkTextActor
-    auto titleActor = vtkSmartPointer<vtkTextActor>::New();
-    sbar->SetTitle(" ");
-    titleActor->SetInput(scalarBar_.title_.c_str());
-    titleActor->GetTextProperty()->SetFontFamilyToArial();
-    titleActor->GetTextProperty()->SetFontSize(3*scalarBar_.fontSize_);
-    titleActor->GetTextProperty()->SetJustificationToCentered();
-    titleActor->GetTextProperty()->SetVerticalJustificationToBottom();
-    titleActor->GetTextProperty()->BoldOn();
-    titleActor->GetTextProperty()->ItalicOff();
-    titleActor->GetTextProperty()->SetColor
-    (
-        textColour[0],
-        textColour[1],
-        textColour[2]
-    );
-    titleActor->GetPositionCoordinate()->
-        SetCoordinateSystemToNormalizedViewport();
-
-    // How to use the standard scalar bar text
-    // sbar->SetTitle(scalarBar_.title_.c_str());
-    // sbar->GetTitleTextProperty()->SetColor
-    // (
-    //     textColour[0],
-    //     textColour[1],
-    //     textColour[2]
-    // );
-    // sbar->GetTitleTextProperty()->SetFontSize(scalarBar_.fontSize_);
-    // sbar->GetTitleTextProperty()->ShadowOff();
-    // sbar->GetTitleTextProperty()->BoldOn();
-    // sbar->GetTitleTextProperty()->ItalicOff();
-
-    sbar->GetLabelTextProperty()->SetColor
-    (
-        textColour[0],
-        textColour[1],
-        textColour[2]
-    );
-    sbar->GetLabelTextProperty()->SetFontSize(scalarBar_.fontSize_);
-    sbar->GetLabelTextProperty()->ShadowOff();
-    sbar->GetLabelTextProperty()->BoldOff();
-    sbar->GetLabelTextProperty()->ItalicOff();
-    sbar->SetLabelFormat(scalarBar_.labelFormat_.c_str());
-
-    sbar->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
-    sbar->GetPositionCoordinate()->SetValue
-    (
-        scalarBar_.position_.first(),
-        scalarBar_.position_.second()
-    );
-    if (scalarBar_.vertical_)
-    {
-        sbar->SetOrientationToVertical();
-        sbar->SetWidth(0.1);
-        sbar->SetHeight(0.75);
-        sbar->SetTextPositionToSucceedScalarBar();
-    }
-    else
+    // Add the scalar bar - only once!
+    if (renderer && Pstream::master())
     {
-        sbar->SetOrientationToHorizontal();
-
-        // Adjustments since not using scalarbar title property
-        sbar->SetWidth(0.75);
-        sbar->SetHeight(0.07);
-        sbar->SetBarRatio(0.5);
-        // sbar->SetHeight(0.1);
-        // sbar->SetTitleRatio(0.01);
-        sbar->SetTextPositionToPrecedeScalarBar();
+        scalarBar_.add(colours_["text"]->value(position), renderer, lut);
     }
-
-    titleActor->GetPositionCoordinate()->SetValue
-    (
-        scalarBar_.position_.first() + 0.5*sbar->GetWidth(),
-        scalarBar_.position_.second() + sbar->GetHeight()
-    );
-
-    // sbar->DrawFrameOn();
-    // sbar->DrawBackgroundOn();
-    // sbar->UseOpacityOff();
-    // sbar->VisibilityOff();
-    sbar->VisibilityOn();
-
-    renderer->AddActor(sbar);
-    renderer->AddActor2D(titleActor);
 }
 
 
@@ -250,9 +494,9 @@ setField
 (
     const scalar position,
     const word& colourFieldName,
-    vtkPolyDataMapper* mapper,
-    vtkRenderer* renderer,
-    vtkPolyData* pData
+    const FieldAssociation fieldAssociation,
+    vtkMapper* mapper,
+    vtkRenderer* renderer
 ) const
 {
     mapper->InterpolateScalarsBeforeMappingOn();
@@ -264,6 +508,7 @@ setField
             mapper->ScalarVisibilityOff();
             break;
         }
+
         case cbField:
         {
             // Create look-up table for colours
@@ -276,15 +521,15 @@ setField
             const char* fieldName = colourFieldName.c_str();
             mapper->SelectColorArray(fieldName);
 
-            // Set to use either point or cell data
-            // Note: if both point and cell data exists, preferentially
-            //       choosing point data.  This is often the case when using
-            //       glyphs
-            if (pData->GetPointData()->HasArray(fieldName))
+            // Use either point or cell data
+            // - if both point and cell data exists, preferentially choose
+            //   point data.  This is often the case when using glyphs.
+
+            if (fieldAssociation & FieldAssociation::POINT_DATA)
             {
                 mapper->SetScalarModeToUsePointFieldData();
             }
-            else if (pData->GetCellData()->HasArray(fieldName))
+            else if (fieldAssociation & FieldAssociation::CELL_DATA)
             {
                 mapper->SetScalarModeToUseCellFieldData();
             }
@@ -300,7 +545,7 @@ setField
             mapper->SetLookupTable(lut);
             mapper->ScalarVisibilityOn();
 
-            // Add the bar
+            // Add the scalar bar
             addScalarBar(position, renderer, lut);
             break;
         }
@@ -315,43 +560,20 @@ addGlyphs
 (
     const scalar position,
     const word& scaleFieldName,
+    const fieldSummary& scaleFieldInfo,
     const word& colourFieldName,
+    const fieldSummary& colourFieldInfo,
     const scalar maxGlyphLength,
+
     vtkPolyData* data,
     vtkActor* actor,
     vtkRenderer* renderer
 ) const
 {
-    auto glyph = vtkSmartPointer<vtkGlyph3D>::New();
-    auto glyphMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
-    glyphMapper->SetInputConnection(glyph->GetOutputPort());
-
-    glyph->SetInputData(data);
-    glyph->ScalingOn();
-
-    bool needPointData = false;
-
-    // Determine whether we have scalar or vector data
-    // and if we need to convert CellData -> PointData
-
-    label nComponents = -1;
-    const char* scaleFieldNameChar = scaleFieldName.c_str();
-    if (data->GetPointData()->HasArray(scaleFieldNameChar))
-    {
-        nComponents =
-            data->GetPointData()->GetArray(scaleFieldNameChar)
-                ->GetNumberOfComponents();
-    }
-    else if (data->GetCellData()->HasArray(scaleFieldNameChar))
-    {
-        // Need to convert CellData to PointData
-        needPointData = true;
+    // Determine whether we have CellData/PointData and (scalar/vector)
+    // or if we need to a cell->point data filter.
 
-        nComponents =
-            data->GetCellData()->GetArray(scaleFieldNameChar)
-                ->GetNumberOfComponents();
-    }
-    else
+    if (!scaleFieldInfo.exists())
     {
         WarningInFunction
             << "Cannot add glyphs. No such cell or point field: "
@@ -359,30 +581,46 @@ addGlyphs
         return;
     }
 
-
-    const bool ok = (nComponents == 1 || nComponents == 3);
-
-    if (!ok)
+    if (!scaleFieldInfo.isScalar() && !scaleFieldInfo.isVector())
     {
         WarningInFunction
             << "Glyphs can only be added to scalar or vector data. "
             << "Unable to process field " << scaleFieldName << endl;
         return;
     }
-    else if (needPointData)
+
+
+    // Setup glyphs
+
+    // The min/max data range for the input data (cell or point),
+    // which will be slightly less after using a cell->point filter
+    // (since it averages), but is still essentially OK.
+
+
+    auto glyph = vtkSmartPointer<vtkGlyph3D>::New();
+    glyph->ScalingOn();
+
+    auto glyphMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
+    glyphMapper->SetInputConnection(glyph->GetOutputPort());
+
+    vtkSmartPointer<vtkCellDataToPointData> cellToPoint;
+
+    // The data source is filtered or original (PointData)
+    if (!scaleFieldInfo.hasPointData() || !colourFieldInfo.hasPointData())
     {
-        auto cellToPoint = vtkSmartPointer<vtkCellDataToPointData>::New();
+        // CellData - Need a cell->point filter
+        cellToPoint = vtkSmartPointer<vtkCellDataToPointData>::New();
         cellToPoint->SetInputData(data);
-        cellToPoint->Update();
-        vtkDataSet* pds = cellToPoint->GetOutput();
-        vtkDataArray* pData = pds->GetPointData()->GetArray(scaleFieldNameChar);
 
-        // Store in main vtkPolyData
-        data->GetPointData()->AddArray(pData);
+        glyph->SetInputConnection(cellToPoint->GetOutputPort());
+    }
+    else
+    {
+        glyph->SetInputData(data);
     }
 
 
-    if (nComponents == 1)
+    if (scaleFieldInfo.nComponents_ == 1)
     {
         auto sphere = vtkSmartPointer<vtkSphereSource>::New();
         sphere->SetCenter(0, 0, 0);
@@ -396,12 +634,12 @@ addGlyphs
 
         if (maxGlyphLength > 0)
         {
-            // Can get range from point data:
-
-            // double range[2];
-            // vtkDataArray* values =
-            //     data->GetPointData()->GetScalars(scaleFieldNameChar);
-            // values->GetRange(range);
+            // Using range from the data:
+            // glyph->SetRange
+            // (
+            //     scaleFieldInfo.range_.first(),
+            //     scaleFieldInfo.range_.second()
+            // );
 
             // Set range according to user-supplied limits
             glyph->ClampingOn();
@@ -420,14 +658,14 @@ addGlyphs
         glyph->SetColorModeToColorByScalar();
         glyph->SetInputArrayToProcess
         (
-            0, // scalars
-            0,
-            0,
+            0, // index (0) = scalars
+            0, // port
+            0, // connection
             vtkDataObject::FIELD_ASSOCIATION_POINTS,
-            scaleFieldNameChar
+            scaleFieldName.c_str()
         );
     }
-    else if (nComponents == 3)
+    else if (scaleFieldInfo.nComponents_ == 3)
     {
         auto arrow = vtkSmartPointer<vtkArrowSource>::New();
         arrow->SetTipResolution(10);
@@ -440,24 +678,13 @@ addGlyphs
 
         if (maxGlyphLength > 0)
         {
-            vtkDataArray* values =
-                data->GetPointData()->GetVectors(scaleFieldNameChar);
-
-            double range[6];
-            values->GetRange(range);
-
-            // Attempt to set range for vectors...
-            // scalar x0 = sqrt(sqr(range_.first())/3.0);
-            // scalar x1 = sqrt(sqr(range_.second())/3.0);
-            // range[0] = x0;
-            // range[1] = x0;
-            // range[2] = x0;
-            // range[3] = x1;
-            // range[4] = x1;
-            // range[5] = x1;
-
+            // Set range according data limits
             glyph->ClampingOn();
-            glyph->SetRange(range);
+            glyph->SetRange
+            (
+                scaleFieldInfo.range_.first(),
+                scaleFieldInfo.range_.second()
+            );
             glyph->SetScaleFactor(maxGlyphLength);
         }
         else
@@ -470,20 +697,30 @@ addGlyphs
         glyph->SetColorModeToColorByVector();
         glyph->SetInputArrayToProcess
         (
-            1, // vectors
-            0,
-            0,
+            1, // index (1) = vectors
+            0, // port
+            0, // connection
             vtkDataObject::FIELD_ASSOCIATION_POINTS,
-            scaleFieldNameChar
+            scaleFieldName.c_str()
         );
     }
 
 
-    if (ok)
+    // Apply colouring etc.
+    // We already established PointData, which as either in the original,
+    // or generated with vtkCellDataToPointData filter.
+
     {
         glyph->Update();
 
-        setField(position, colourFieldName, glyphMapper, renderer, data);
+        setField
+        (
+            position,
+            colourFieldName,
+            FieldAssociation::POINT_DATA,  // Original or after filter
+            glyphMapper,
+            renderer
+        );
 
         glyphMapper->Update();
 
@@ -505,9 +742,11 @@ fieldVisualisationBase
 :
     colours_(colours),
     fieldName_(dict.get<word>("field")),
+    smooth_(dict.lookupOrDefault("smooth", false)),
     colourBy_(cbColour),
     colourMap_(cmRainbow),
-    range_()
+    range_(),
+    scalarBar_()
 {
     colourByTypeNames.readEntry("colourBy", dict, colourBy_);
 
@@ -515,26 +754,24 @@ fieldVisualisationBase
     {
         case cbColour:
         {
-            scalarBar_.visible_ = false;
+            scalarBar_.hide();
             break;
         }
+
         case cbField:
         {
             dict.readEntry("range", range_);
-
             colourMapTypeNames.readIfPresent("colourMap", dict, colourMap_);
 
-            const dictionary& sbDict = dict.subDict("scalarBar");
-            sbDict.readEntry("visible", scalarBar_.visible_);
+            const dictionary* sbar = dict.findDict("scalarBar");
 
-            if (scalarBar_.visible_)
+            if (sbar)
+            {
+                scalarBar_.read(*sbar);
+            }
+            else
             {
-                sbDict.readEntry("vertical", scalarBar_.vertical_);
-                sbDict.readEntry("position", scalarBar_.position_);
-                sbDict.readEntry("title", scalarBar_.title_);
-                sbDict.readEntry("fontSize", scalarBar_.fontSize_);
-                sbDict.readEntry("labelFormat", scalarBar_.labelFormat_);
-                sbDict.readEntry("numberOfLabels", scalarBar_.numberOfLabels_);
+                scalarBar_.hide();
             }
             break;
         }
diff --git a/src/runTimePostProcessing/fieldVisualisationBase.H b/src/runTimePostProcessing/fieldVisualisationBase.H
index 9e2629fcdaeab9118bcb4ad0d2abde57aa553875..b1a56af9eee67069d28a499e0995423852fe6f2b 100644
--- a/src/runTimePostProcessing/fieldVisualisationBase.H
+++ b/src/runTimePostProcessing/fieldVisualisationBase.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -27,6 +27,20 @@ Class
 Description
     Base class for scene objects
 
+    Dictionary controls - colour by field
+    \table
+        Property    | Description                           | Required | Default
+        colourBy    | Colouring type (color / field)        | yes |
+        range       | Lower/upper range to display          | yes |
+        smooth      | Request smoother output               | no  | false
+        colourMap   | Colour map for rendering              | no  | rainbow
+        scalarBar   | Scalar-bar sub-dictionary             | yes |
+    \endtable
+
+Colour maps include "coolToWarm" ("blueWhiteRed"), "coldAndHot",
+"fire", "rainbow", "greyscale" ("grayscale"), "xray". For historical
+reasons, the default is still "rainbow".
+
 SourceFiles
     fieldVisualisationBase.C
 
@@ -39,27 +53,35 @@ SourceFiles
 #include "Tuple2.H"
 #include "Enum.H"
 #include "vector.H"
+#include "MinMax.H"
 #include "HashPtrTable.H"
+#include "scalarBar.H"
 #include "Function1.H"
 
-// VTK includes
 #include "vtkSmartPointer.h"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward declarations
+// Forward Declarations
 class vtkActor;
+class vtkCompositeDataSet;
+class vtkCompositeDataGeometryFilter;
+class vtkCompositePolyDataMapper;
+class vtkDataSet;
+class vtkDataSetAlgorithm;
+class vtkFieldData;
 class vtkLookupTable;
 class vtkMapper;
 class vtkPolyData;
 class vtkPolyDataMapper;
 class vtkRenderer;
 
+
 namespace Foam
 {
 namespace functionObjects
 {
-// Forward declarations
+// Forward Declarations
 class runTimePostProcessing;
 
 namespace runTimePostPro
@@ -73,48 +95,110 @@ class fieldVisualisationBase
 {
 public:
 
-    // Public enumerations
+    // Public Enumerations
 
+        //- Colouring type
         enum colourByType
         {
-            cbColour,
-            cbField
+            cbColour,           //!< "colour" : Use specified colour
+            cbField             //!< "field" : Use named field
         };
 
+        //- Enumeration names for colourByType
         static const Enum<colourByType> colourByTypeNames;
 
+        //- Colour map enumerations
         enum colourMapType
         {
-            cmRainbow,
-            cmBlueWhiteRed,
-            cmFire,
-            cmGreyscale
+            cmCoolToWarm,       //!< ParaView "Cool To Warm" blue-white-read
+            cmBlueWhiteRed = cmCoolToWarm,
+            cmColdAndHot,       //!< ParaView "Cold and Hot"
+            cmFire,             //!< ParaView "Black-Body Radiation"
+            cmRainbow,          //!< "rainbow"
+            cmGreyscale,        //!< ParaView "Grayscale"
+            cmXray              //!< ParaView "X Ray"
         };
 
+        //- Enumeration names for colourMapType
         static const Enum<colourMapType> colourMapTypeNames;
 
 
-protected:
+        //- Enumeration of the data field associations
+        //  These values are used internally and do NOT correspond to the
+        //  vtkDataObject::FieldAssociations enumeration.
+        enum FieldAssociation
+        {
+            NO_DATA  = 0,           //!< No associated data
+            CELL_DATA = 0x1,        //!< Associated with cells (faces)
+            POINT_DATA = 0x2,       //!< Associated with points
+            CELL_POINT_DATA = 0x3   //!< Associated with cells and/or points
+        };
 
-    // Protected Data
 
-        struct scalarBar
+        //- General field characteristics.
+        //  For convenience, the interface is exposed but external use is
+        //  highly discouraged.
+        struct fieldSummary
         {
-            bool visible_;
-            bool vertical_;
-            Tuple2<scalar, scalar> position_;
-            string title_;
-            label fontSize_;
-            string labelFormat_;
-            label numberOfLabels_;
+            int nComponents_;
+            unsigned association_;
+            scalarMinMax range_;
+
+            //- Construct null
+            fieldSummary()
+            :
+                nComponents_(0),
+                association_(0u),
+                range_()
+            {}
+
+            //- Parallel reduction. A no-op if Pstream::parRun() is false
+            void reduce();
+
+            //- True if nComponents_ == 1
+            bool isScalar() const
+            {
+                return nComponents_ == 1;
+            }
+
+            //- True if nComponents_ == 3
+            bool isVector() const
+            {
+                return nComponents_ == 3;
+            }
+
+            //- True if association_ is non-zero
+            bool exists() const
+            {
+                return association_;
+            }
+
+            //- True if there is a POINT_DATA association
+            bool hasPointData() const
+            {
+                return (association_ & FieldAssociation::POINT_DATA);
+            }
+
+            InfoProxy<fieldSummary> info() const
+            {
+                return InfoProxy<fieldSummary>(*this);
+            }
         };
 
+
+protected:
+
+    // Protected Data
+
         //- Colours
         const HashPtrTable<Function1<vector>>& colours_;
 
         //- Field name
         word fieldName_;
 
+        //- Requested smoother fields (eg, interpolate cell -> point values)
+        bool smooth_;
+
         //- Colour by type
         colourByType colourBy_;
 
@@ -124,16 +208,67 @@ protected:
         //- Range of values
         Tuple2<scalar, scalar> range_;
 
-        //- Scalar bar
+        //- Scalar bar characteristics
         scalarBar scalarBar_;
 
 
     // Protected Member Functions
 
+        //- Query DataSet for field name and its field association
+        static fieldSummary queryFieldSummary
+        (
+            const word& fieldName,
+            vtkDataSet* dataset
+        );
+
+        //- Query composite DataSet for field name and its FieldAssociation
+        static fieldSummary queryFieldSummary
+        (
+            const word& fieldName,
+            vtkCompositeDataSet* data
+        );
+
+        //- Query DataSet for field name and its field association
+        static FieldAssociation queryFieldAssociation
+        (
+            const word& fieldName,
+            vtkDataSet* dataset
+        );
+
+        //- Query composite DataSet for field name and its FieldAssociation
+        static FieldAssociation queryFieldAssociation
+        (
+            const word& fieldName,
+            vtkCompositeDataSet* data
+        );
+
+
+        //- Add "mag(..)" field for filters that only accept scalars
+        static void addMagField
+        (
+            const word& fieldName,
+            vtkFieldData* fieldData
+        );
+
+        //- Add "mag(..)" field for filters that only accept scalars
+        static void addMagField
+        (
+            const word& fieldName,
+            vtkDataSet* dataset
+        );
+
+        //- Add "mag(..)" field for filters that only accept scalars
+        static void addMagField
+        (
+            const word& fieldName,
+            vtkCompositeDataSet* data
+        );
+
+
         //- Set the colour map
         void setColourMap(vtkLookupTable* lut) const;
 
-        //- Add scalar bar to renderer
+        //- Add scalar bar (if visible) to renderer
         void addScalarBar
         (
             const scalar position,
@@ -146,9 +281,9 @@ protected:
         (
             const scalar position,
             const word& colourFieldName,
-            vtkPolyDataMapper* mapper,
-            vtkRenderer* renderer,
-            vtkPolyData* pData
+            const FieldAssociation fieldAssociation,
+            vtkMapper* mapper,
+            vtkRenderer* renderer
         ) const;
 
         //- Add glyphs
@@ -156,13 +291,17 @@ protected:
         (
             const scalar position,
             const word& scaleFieldName,
+            const fieldSummary& scaleFieldInfo,
             const word& colourFieldName,
+            const fieldSummary& colourFieldInfo,
             const scalar maxGlyphLength,
+
             vtkPolyData* data,
             vtkActor* actor,
             vtkRenderer* renderer
         ) const;
 
+
         //- No copy construct
         fieldVisualisationBase(const fieldVisualisationBase&) = delete;
 
@@ -202,6 +341,19 @@ public:
 
 } // End namespace runTimePostPro
 } // End namespace functionObjects
+
+
+// Ostream
+Ostream& operator<<
+(
+    Ostream& os,
+    const InfoProxy
+    <
+        functionObjects::runTimePostPro::fieldVisualisationBase::fieldSummary
+    >& proxy
+);
+
+
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/runTimePostProcessing/functionObjectBase.C b/src/runTimePostProcessing/functionObjectBase.C
index 28d4fbffb3f432803048e8af85d5a1adcb2438f4..c44fecc56fee28aa6148cdfe6b40bd42a13fad6a 100644
--- a/src/runTimePostProcessing/functionObjectBase.C
+++ b/src/runTimePostProcessing/functionObjectBase.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,20 +25,6 @@ License
 
 #include "functionObjectBase.H"
 
-// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
-
-namespace Foam
-{
-namespace functionObjects
-{
-namespace runTimePostPro
-{
-    defineTypeNameAndDebug(functionObjectBase, 0);
-}
-}
-}
-
-
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
 Foam::fileName
@@ -69,7 +55,12 @@ bool Foam::functionObjects::runTimePostPro::functionObjectBase::removeFile
 {
     // Foam::rm() ignores empty names etc.
 
-    return Foam::rm(getFileName(keyword, subDictName));
+    if (Pstream::master())
+    {
+        return Foam::rm(getFileName(keyword, subDictName));
+    }
+
+    return false;
 }
 
 
@@ -85,6 +76,7 @@ Foam::functionObjects::runTimePostPro::functionObjectBase::functionObjectBase
     fieldVisualisationBase(dict, colours),
     state_(state),
     functionObjectName_(dict.get<word>("functionObject")),
+    liveObject_(dict.lookupOrDefault("liveObject", true)),
     clearObjects_(dict.lookupOrDefault("clearObjects", false))
 {}
 
diff --git a/src/runTimePostProcessing/functionObjectBase.H b/src/runTimePostProcessing/functionObjectBase.H
index 47647fc5e736b7393438cbfbb7046b03363d6659..abab1a26074c290d82de3cca9af2b062e1d44a2e 100644
--- a/src/runTimePostProcessing/functionObjectBase.H
+++ b/src/runTimePostProcessing/functionObjectBase.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2016-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -29,11 +29,15 @@ Description
 
     Dictionary controls
     \table
-        Property     | Description                      | Required    | Default
-        functionObject | The data source                | yes         |
-        clearObjects  | Remove file after use           | no          | no
+        Property     | Description                          | Required | Default
+        functionObject | The data source                    | yes |
+        clearObjects | Remove file after use                | no  | no
+        liveObject   | Prefer simulation data source        | no  | true
     \endtable
 
+    The "live" keyword indiates that from within the simulation (in memory)
+    is preferred over data from disk (for example).
+
 SourceFiles
     functionObjectBase.C
 
@@ -64,14 +68,17 @@ class functionObjectBase
 {
 protected:
 
-    // Protected data
+    // Protected Data
 
         //- Reference to the state
         const stateFunctionObject& state_;
 
-        //- The function object name which provides the source data
+        //- The function object name that provides the source data
         word functionObjectName_;
 
+        //- Flag to indicate "live" (simulation) data source should be used
+        bool liveObject_;
+
         //- Flag to indicate that source data should be cleared after use
         bool clearObjects_;
 
@@ -109,6 +116,7 @@ protected:
         //  \note does not change the stateFunctionObject
         bool removeFile(const word& keyword, const word& subDictName);
 
+
         //- No copy construct
         functionObjectBase(const functionObjectBase&) = delete;
 
@@ -118,10 +126,6 @@ protected:
 
 public:
 
-    //- Run-time type information
-    TypeName("functionObjectBase");
-
-
     // Constructors
 
         //- Construct from dictionary
diff --git a/src/runTimePostProcessing/functionObjectCloud.C b/src/runTimePostProcessing/functionObjectCloud.C
index e9c3540a183d9f62d48d2ec1ac6b0f9b15190705..54e71f0444cff104e7945e1f76f54075532e8ca0 100644
--- a/src/runTimePostProcessing/functionObjectCloud.C
+++ b/src/runTimePostProcessing/functionObjectCloud.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -31,11 +31,11 @@ License
 
 // VTK includes
 #include "vtkActor.h"
-#include "vtkRenderer.h"
-#include "vtkSmartPointer.h"
 #include "vtkPolyData.h"
 #include "vtkPolyDataMapper.h"
 #include "vtkProperty.h"
+#include "vtkRenderer.h"
+#include "vtkSmartPointer.h"
 
 // VTK Readers
 #include "vtkPolyDataReader.h"
@@ -49,13 +49,42 @@ namespace functionObjects
 {
 namespace runTimePostPro
 {
-    defineTypeNameAndDebug(functionObjectCloud, 0);
+    defineTypeName(functionObjectCloud);
     addToRunTimeSelectionTable(pointData, functionObjectCloud, dictionary);
 }
 }
 }
 
 
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace
+{
+
+static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
+{
+    // Very simple - we only support vtp files, which are expected to have
+    // the scaling and colouring fields.
+
+    vtkSmartPointer<vtkPolyData> dataset;
+
+    if (fName.ext() == "vtp")
+    {
+        auto reader = vtkSmartPointer<vtkXMLPolyDataReader>::New();
+
+        reader->SetFileName(fName.c_str());
+        reader->Update();
+        dataset = reader->GetOutput();
+
+        return dataset;
+    }
+
+    return dataset;
+}
+
+} // End anonymous namespace
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::functionObjects::runTimePostPro::functionObjectCloud::functionObjectCloud
@@ -71,9 +100,7 @@ Foam::functionObjects::runTimePostPro::functionObjectCloud::functionObjectCloud
     inputFileName_(),
     colourFieldName_(dict.get<word>("colourField")),
     actor_()
-{
-    actor_ = vtkSmartPointer<vtkActor>::New();
-}
+{}
 
 
 // * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
@@ -85,8 +112,8 @@ Foam::functionObjects::runTimePostPro::functionObjectCloud::
 
 // * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
 
-void Foam::functionObjects::runTimePostPro::functionObjectCloud::
-addGeometryToScene
+bool Foam::functionObjects::runTimePostPro::functionObjectCloud::
+addGeometryFromFile
 (
     const scalar position,
     vtkRenderer* renderer
@@ -94,72 +121,118 @@ addGeometryToScene
 {
     if (!visible_)
     {
-        return;
+        return false;
     }
 
+    vtkSmartPointer<vtkPolyData> polyData;
+
+    bool good = true;
+
     // The vtkCloud stores 'file' via the stateFunctionObject
     // (lookup by cloudName).
     // It only generates VTP format, which means there is a single file
     // containing all fields.
 
-    inputFileName_ = getFileName("file", cloudName_);
+    if (Pstream::master())
+    {
+        inputFileName_ = getFileName("file", cloudName_);
+
+        if (inputFileName_.size())
+        {
+            polyData = getPolyDataFile(inputFileName_);
+
+            if (!polyData || polyData->GetNumberOfPoints() == 0)
+            {
+                good = false;
+
+                WarningInFunction
+                    << "Could not read "<< inputFileName_ << nl
+                    << "Only VTK (.vtp) files are supported"
+                    << endl;
+            }
+            else
+            {
+                DebugInfo
+                    << "    Resolved cloud file "
+                    << inputFileName_ << endl;
+            }
+        }
+        else
+        {
+            good = false;
 
-    if (inputFileName_.empty())
+            WarningInFunction
+                << "Unable to find function object " << functionObjectName_
+                << " output for field " << fieldName_
+                << ". Cloud will not be processed"
+                << endl;
+        }
+    }
+    else
     {
-        WarningInFunction
-            << "Unable to find function object " << functionObjectName_
-            << " output for field " << fieldName_
-            << ". Cloud will not be processed"
-            << endl;
-        return;
+        inputFileName_.clear();
     }
 
+    reduce(good, andOp<bool>());
 
-    vtkSmartPointer<vtkPolyData> dataset;
-
-    if (inputFileName_.hasExt("vtp"))
+    if (!good)
     {
-        auto reader = vtkSmartPointer<vtkXMLPolyDataReader>::New();
-        reader->SetFileName(inputFileName_.c_str());
-        reader->Update();
-
-        dataset = reader->GetOutput();
+        return false;
     }
-    else
+
+    // Only render on master
+    if (!renderer || !Pstream::master())
     {
-        // Invalid name - ignore.
-        // Don't support VTK legacy format at all - it is too wasteful
-        // and cumbersome.
+        return true;
+    }
 
-        WarningInFunction
-            << "Could not read "<< inputFileName_ << nl
-            << "Only VTK (.vtp) files are supported"
-            << ". Cloud will not be processed"
-            << endl;
 
-        inputFileName_.clear();
-    }
+    // Rendering
 
+    actor_ = vtkSmartPointer<vtkActor>::New();
 
-    if (dataset)
     {
+        fieldSummary scaleFieldInfo =
+            queryFieldSummary(fieldName_, polyData);
+
+        fieldSummary colourFieldInfo =
+            queryFieldSummary(colourFieldName_, polyData);
+
+        // No reduction
+
         auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
 
         actor_->SetMapper(mapper);
 
+        /// dataset->Print(std::cout);
+
         addGlyphs
         (
             position,
-            fieldName_,
-            colourFieldName_,
+            fieldName_, scaleFieldInfo,         // scaling
+            colourFieldName_, colourFieldInfo,  // colour
             maxGlyphLength_,
-            dataset,
+            polyData,
             actor_,
             renderer
         );
 
         renderer->AddActor(actor_);
     }
+
+    return true;
+}
+
+
+void Foam::functionObjects::runTimePostPro::functionObjectCloud::
+addGeometryToScene
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    // File source
+    addGeometryFromFile(position, renderer);
 }
 
 
@@ -168,10 +241,16 @@ void Foam::functionObjects::runTimePostPro::functionObjectCloud::updateActors
     const scalar position
 )
 {
-    actor_->GetProperty()->SetOpacity(opacity(position));
+    if (actor_)
+    {
+        const vector colour = pointColour_->value(position);
+
+        vtkProperty* prop = actor_->GetProperty();
 
-    vector pc = pointColour_->value(position);
-    actor_->GetProperty()->SetColor(pc[0], pc[1], pc[2]);
+        prop->SetOpacity(opacity(position));
+
+        prop->SetColor(colour[0], colour[1], colour[2]);
+    }
 }
 
 
diff --git a/src/runTimePostProcessing/functionObjectCloud.H b/src/runTimePostProcessing/functionObjectCloud.H
index 514091c8246a12e1e5d0c4ba76390b4c9063d4d2..ba8d43a1689b30726bb033cea914ba136d000278 100644
--- a/src/runTimePostProcessing/functionObjectCloud.H
+++ b/src/runTimePostProcessing/functionObjectCloud.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,7 +25,17 @@ Class
     Foam::functionObjects::runTimePostPro::functionObjectCloud
 
 Description
-    Visualisation of cloud data from function object output
+    Visualisation of cloud data from function object output (file-based only).
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        type        | The point type: functionObjectCloud   | yes |
+        functionObject | The data source                    | yes |
+        cloud         | The cloud name                      | no  |
+        field         | The field for glyphs scaling        | no  |
+        colourField   | The field to display                | no  |
+    \endtable
 
 SourceFiles
     functionObjectCloud.C
@@ -85,7 +95,7 @@ protected:
 public:
 
     //- Run-time type information
-    TypeName("functionObjectCloud");
+    TypeNameNoDebug("functionObjectCloud");
 
 
     // Constructors
@@ -105,7 +115,14 @@ public:
 
     // Member Functions
 
-        //- Add tube(s) to scene
+        //- Add cloud to scene (using file source)
+        bool addGeometryFromFile
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Add cloud to scene
         virtual void addGeometryToScene
         (
             const scalar position,
diff --git a/src/runTimePostProcessing/functionObjectLine.C b/src/runTimePostProcessing/functionObjectLine.C
index 1f7d5ff5e212357d9604071b39597b4313d7a95a..e27dabcc972c71887bcdd7e57a0934a321266c8a 100644
--- a/src/runTimePostProcessing/functionObjectLine.C
+++ b/src/runTimePostProcessing/functionObjectLine.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -48,7 +48,7 @@ namespace functionObjects
 {
 namespace runTimePostPro
 {
-    defineTypeNameAndDebug(functionObjectLine, 0);
+    defineTypeName(functionObjectLine);
     addToRunTimeSelectionTable(pathline, functionObjectLine, dictionary);
 }
 }
@@ -65,7 +65,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
     // Not extremely elegant...
     vtkSmartPointer<vtkPolyData> dataset;
 
-    if (fName.ext() == "vtk")
+    if ("vtk" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkPolyDataReader>::New();
 
@@ -76,7 +76,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
         return dataset;
     }
 
-    if (fName.ext() == "vtp")
+    if ("vtp" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkXMLPolyDataReader>::New();
 
@@ -125,7 +125,8 @@ addGeometryToScene
     vtkRenderer* renderer
 )
 {
-    if (!visible_)
+    // Currently master-only
+    if (!visible_ || !renderer || !Pstream::master())
     {
         return;
     }
@@ -153,10 +154,19 @@ addGeometryToScene
         return;
     }
 
+    DebugInfo << "    Resolved lines " << fName << endl;
+
 
     auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
 
-    setField(position, fieldName_, mapper, renderer, polyData);
+    setField
+    (
+        position,
+        fieldName_,
+        queryFieldAssociation(fieldName_, polyData),
+        mapper,
+        renderer
+    );
 
     actor_->SetMapper(mapper);
 
diff --git a/src/runTimePostProcessing/functionObjectLine.H b/src/runTimePostProcessing/functionObjectLine.H
index fcb01506eb8947f6c88eb3543bb6f183cb14b697..63cc9619727fac28f6b342ba597409b4aab75194 100644
--- a/src/runTimePostProcessing/functionObjectLine.H
+++ b/src/runTimePostProcessing/functionObjectLine.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,7 +25,13 @@ Class
     Foam::functionObjects::runTimePostPro::functionObjectLine
 
 Description
-    Visualisation of line data from function object output
+    Visualisation of line data from function object output (file-based only).
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        type        | The line type: functionObjectLine     | yes |
+    \endtable
 
 SourceFiles
     functionObjectLine.C
@@ -76,7 +82,7 @@ protected:
 public:
 
     //- Run-time type information
-    TypeName("functionObjectLine");
+    TypeNameNoDebug("functionObjectLine");
 
 
     // Constructors
diff --git a/src/runTimePostProcessing/functionObjectSurface.C b/src/runTimePostProcessing/functionObjectSurface.C
index 15ed336ed2a596c0057f1f0f83d985a3e3422ddf..801305f8c788f8e7fd86cbe55a8e70173ddfea3b 100644
--- a/src/runTimePostProcessing/functionObjectSurface.C
+++ b/src/runTimePostProcessing/functionObjectSurface.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2016 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -30,6 +30,13 @@ License
 
 // VTK includes
 #include "vtkActor.h"
+#include "vtkCellData.h"
+#include "vtkCellDataToPointData.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkCompositePolyDataMapper.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPointData.h"
 #include "vtkPolyData.h"
 #include "vtkPolyDataMapper.h"
 #include "vtkProperty.h"
@@ -48,7 +55,7 @@ namespace functionObjects
 {
 namespace runTimePostPro
 {
-    defineTypeNameAndDebug(functionObjectSurface, 0);
+    defineTypeName(functionObjectSurface);
     addToRunTimeSelectionTable(surface, functionObjectSurface, dictionary);
 }
 }
@@ -65,7 +72,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
     // Not extremely elegant...
     vtkSmartPointer<vtkPolyData> dataset;
 
-    if (fName.ext() == "vtk")
+    if ("vtk" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkPolyDataReader>::New();
 
@@ -76,7 +83,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
         return dataset;
     }
 
-    if (fName.ext() == "vtp")
+    if ("vtp" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkXMLPolyDataReader>::New();
 
@@ -108,17 +115,10 @@ functionObjectSurface
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::functionObjects::runTimePostPro::functionObjectSurface::
-~functionObjectSurface()
-{}
-
-
 // * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
 
-void Foam::functionObjects::runTimePostPro::functionObjectSurface::
-addGeometryToScene
+bool Foam::functionObjects::runTimePostPro::functionObjectSurface::
+addGeometry
 (
     const scalar position,
     vtkRenderer* renderer
@@ -126,39 +126,291 @@ addGeometryToScene
 {
     if (!visible_)
     {
-        return;
+        return false;
     }
 
-    fileName fName = getFileName("file", fieldName_);
-    if (fName.empty())
+    DebugInfo << "    Resolve surface " << functionObjectName_ << endl;
+
+    const polySurface* surf =
+    (
+        geometryBase::parent_.storedObjects()
+       .cfindObject<polySurface>(functionObjectName_)
+    );
+
+    // Treat surface with no faces/points like a missing surface
+    surf = ((surf && surf->nPoints()) ? surf : nullptr);
+
+    bool hasSurface = surf;
+
+
+    // Retrieve the field association (CELL, POINT) for the given field
+
+    unsigned fieldAssociation(0u);
+    if (surf)
+    {
+        unsigned queried = surf->queryFieldAssociation(fieldName_);
+
+        if (queried & polySurface::FACE_DATA)
+        {
+            fieldAssociation |= FieldAssociation::CELL_DATA;
+        }
+        if (queried & polySurface::POINT_DATA)
+        {
+            fieldAssociation |= FieldAssociation::POINT_DATA;
+        }
+    }
+
+    // Reduce the information
+    if (Pstream::parRun())
+    {
+        if (!hasSurface)
+        {
+            // No geometry - set all field association bits ON to ensure
+            // it does not affect bitwise reduction.
+            fieldAssociation = (~0u);
+        }
+
+        reduce(hasSurface, orOp<bool>());
+        reduce(fieldAssociation, bitAndOp<unsigned>());
+    }
+
+    if (!hasSurface)
     {
         WarningInFunction
-            << "Unable to read file name from function object "
-            << functionObjectName_ << " for field " << fieldName_
-            << ". Surface will not be processed"
+            << "No functionObject surface, or has no faces: "
+            << functionObjectName_
             << endl;
-        return;
+
+        if (debug)
+        {
+            Info<< "    Available surfaces:" << nl
+                << geometryBase::parent_.storedObjects()
+                    .sortedNames<polySurface>() << endl;
+        }
+        return false;
     }
 
+    //// Pout<< "local surface = " << (surf ? surf->nFaces() : 0) << nl;
+
+
+    // Create a vtkMultiPieceDataSet with vtkPolyData on the leaves
+    vtkSmartPointer<vtkMultiPieceDataSet> multiPiece;
 
-    auto polyData = getPolyDataFile(fName);
+    // Requesting glyphs on the surface AND only have face data?
+    // - just use the faceCentres directly and attach fields as CellData
+    //   (not PointData).
 
-    if (!polyData || polyData->GetNumberOfPoints() == 0)
+    if
+    (
+        representation_ == rtGlyph
+     && (fieldAssociation == FieldAssociation::CELL_DATA)
+    )
     {
-        WarningInFunction
-            << "Could not read "<< fName << nl
-            << "Only VTK (.vtp, .vtk) files are supported"
-            << endl;
-        return;
+        multiPiece = gatherFaceCentres(surf);
+    }
+    else
+    {
+        multiPiece = gatherSurfacePieces(surf);
+    }
+
+
+    // Add the field (the information is consistent after last reduction).
+
+    // Need field(s) for glyphs or colourByField:
+
+    if (representation_ == rtGlyph || colourBy_ == cbField)
+    {
+        if (fieldAssociation == FieldAssociation::CELL_DATA)
+        {
+            addDimField<polySurfaceGeoMesh>
+            (
+                multiPiece,
+                surf,
+                fieldName_
+            );
+        }
+        else if (fieldAssociation & FieldAssociation::POINT_DATA)
+        {
+            addDimField<polySurfacePointGeoMesh>
+            (
+                multiPiece,
+                surf,
+                fieldName_
+            );
+        }
+    }
+
+
+    // Now have a multi-piece dataset that is one of the following:
+    //
+    // - one-piece per processor (OpenFOAM = parallel, VTK=parallel)
+    // - all pieces on master only (OpenFOAM = parallel, VTK=serial)
+
+    // Re-query field information - we may have stored it differently
+    // than the original source.
+
+    fieldSummary fieldInfo = queryFieldSummary(fieldName_, multiPiece);
+    fieldInfo.reduce();
+
+
+    // Not rendered on this processor?
+    // This is where we stop, but could also have an MPI barrier
+    if (!renderer)
+    {
+        return true;
+    }
+
+
+    // Rendering
+
+    {
+        auto polyData = vtkSmartPointer<vtkCompositeDataGeometryFilter>::New();
+
+        polyData->SetInputData(multiPiece);
+        polyData->Update();
+
+        if (representation_ == rtGlyph)
+        {
+            addGlyphs
+            (
+                position,
+                fieldName_, fieldInfo,  // scaling
+                fieldName_, fieldInfo,  // colouring
+                maxGlyphLength_,
+                polyData->GetOutput(),
+                surfaceActor_,
+                renderer
+            );
+        }
+        else
+        {
+            vtkSmartPointer<vtkCellDataToPointData> cellToPoint;
+
+            // CellData - Need a cell->point filter
+            if (smooth_ && !fieldInfo.hasPointData())
+            {
+                cellToPoint = vtkSmartPointer<vtkCellDataToPointData>::New();
+                cellToPoint->SetInputData(multiPiece);
+
+                polyData->SetInputConnection(cellToPoint->GetOutputPort());
+            }
+            else
+            {
+                polyData->SetInputData(multiPiece);
+            }
+            polyData->Update();
+
+
+            if (!smooth_)
+            {
+                addFeatureEdges(renderer, polyData);
+            }
+
+            auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
+            mapper->SetInputConnection(polyData->GetOutputPort());
+
+            setField
+            (
+                position,
+                fieldName_,
+                (
+                    smooth_
+                  ? FieldAssociation::POINT_DATA
+                  : FieldAssociation(fieldInfo.association_)
+                ),
+                mapper,
+                renderer
+            );
+
+            surfaceActor_->SetMapper(mapper);
+
+            setRepresentation(surfaceActor_);
+
+            renderer->AddActor(surfaceActor_);
+        }
+    }
+
+    return true;
+}
+
+
+bool Foam::functionObjects::runTimePostPro::functionObjectSurface::
+addGeometryFromFile
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (!visible_)
+    {
+        return false;
     }
 
+    vtkSmartPointer<vtkPolyData> polyData;
+
+    bool good = true;
+
+    // File reading is serial (master only)
+    if (Pstream::master())
+    {
+        fileName fName = getFileName("file", fieldName_);
+
+        if (fName.size())
+        {
+            polyData = getPolyDataFile(fName);
+
+            if (!polyData || polyData->GetNumberOfPoints() == 0)
+            {
+                good = false;
+
+                WarningInFunction
+                    << "Could not read "<< fName << nl
+                    << "Only VTK (.vtp, .vtk) files are supported"
+                    << endl;
+            }
+            else
+            {
+                DebugInfo << "    Resolved surface " << fName << endl;
+            }
+        }
+        else
+        {
+            good = false;
+
+            WarningInFunction
+                << "Unable to read file name from function object "
+                << functionObjectName_ << " for field " << fieldName_
+                << ". Surface will not be processed"
+                << endl;
+        }
+    }
+
+    reduce(good, andOp<bool>());
+
+    if (!good)
+    {
+        return false;
+    }
+
+    // Only render on master
+    if (!renderer || !Pstream::master())
+    {
+        return true;
+    }
+
+    fieldSummary fieldInfo = queryFieldSummary(fieldName_, polyData);
+    // No reduction (serial)
+
+
+    // Render
+
     if (representation_ == rtGlyph)
     {
         addGlyphs
         (
             position,
-            fieldName_,
-            fieldName_,
+            fieldName_, fieldInfo,  // scaling
+            fieldName_, fieldInfo,  // colouring
             maxGlyphLength_,
             polyData,
             surfaceActor_,
@@ -172,7 +424,14 @@ addGeometryToScene
         auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
         mapper->SetInputData(polyData);
 
-        setField(position, fieldName_, mapper, renderer, polyData);
+        setField
+        (
+            position,
+            fieldName_,
+            queryFieldAssociation(fieldName_, polyData),
+            mapper,
+            renderer
+        );
 
         surfaceActor_->SetMapper(mapper);
 
@@ -180,6 +439,44 @@ addGeometryToScene
 
         renderer->AddActor(surfaceActor_);
     }
+
+    return true;
+}
+
+
+void Foam::functionObjects::runTimePostPro::functionObjectSurface::
+addGeometryToScene
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (!visible_)
+    {
+        return;
+    }
+
+    if (liveObject_)
+    {
+        // Live source
+        if (addGeometry(position, renderer))
+        {
+            return;
+        }
+
+        WarningInFunction
+            << "No functionObject live source, or is empty: "
+            << functionObjectName_
+            << " ... attempting with file source"
+            << endl;
+    }
+    else
+    {
+        DebugInfo << "Using file source only" << nl;
+    }
+
+    // File source
+    addGeometryFromFile(position, renderer);
 }
 
 
@@ -187,6 +484,8 @@ bool Foam::functionObjects::runTimePostPro::functionObjectSurface::clear()
 {
     if (functionObjectBase::clear())
     {
+        // Even for a "live" data source we allow file cleanup
+        // (eg, from a previous run, etc)
         return removeFile("file", fieldName_);
     }
 
diff --git a/src/runTimePostProcessing/functionObjectSurface.H b/src/runTimePostProcessing/functionObjectSurface.H
index c15762ea20f32282e6a818e03c12ff067bbde06f..eb1288a624f753266841aca8ae398af32876b3fa 100644
--- a/src/runTimePostProcessing/functionObjectSurface.H
+++ b/src/runTimePostProcessing/functionObjectSurface.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,7 +25,22 @@ Class
     Foam::functionObjects::runTimePostPro::functionObjectSurface
 
 Description
-    Visualisation of surface data from function object output
+    Visualisation of surface data from function object output,
+    typically the result of a prior sampled surfaces operation.
+
+    Operates in a "live" mode, in which the previously sampled surfaces
+    are retrieved from the functionObject registry.
+    Or in the traditional file-based mode, in which the sampling is used
+    to generate a file and its name is retrieved from the functionObject
+    properties.
+
+    File-based import is restricted to "vtk" and "vtp" formats.
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        type        | The type: functionObjectSurface       | yes |
+    \endtable
 
 SourceFiles
     functionObjectfunctionObjectSurface.C
@@ -70,7 +85,7 @@ protected:
 public:
 
     //- Run-time type information
-    TypeName("functionObjectSurface");
+    TypeNameNoDebug("functionObjectSurface");
 
 
     // Constructors
@@ -85,11 +100,25 @@ public:
 
 
     //- Destructor
-    virtual ~functionObjectSurface();
+    virtual ~functionObjectSurface() = default;
 
 
     // Member Functions
 
+        //- Add functionObjectSurface to scene (using simulation source)
+        bool addGeometry
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Add functionObjectSurface to scene (using file source)
+        bool addGeometryFromFile
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
         //- Add functionObjectSurface(s) to scene
         virtual void addGeometryToScene
         (
diff --git a/src/runTimePostProcessing/geometryBase.C b/src/runTimePostProcessing/geometryBase.C
index 4f71bcb06dba51eb62e94267f504c3fde99fbaef..bac6e4fb06ec0ee7a7b93117d043068f89289e61 100644
--- a/src/runTimePostProcessing/geometryBase.C
+++ b/src/runTimePostProcessing/geometryBase.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -33,6 +33,18 @@ License
 
 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
 
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+
+    defineDebugSwitchWithName(geometryBase, "runTimePostPro::geometryBase", 0);
+}
+}
+}
+
 const Foam::Enum
 <
     Foam::functionObjects::runTimePostPro::geometryBase::renderModeType
@@ -87,7 +99,7 @@ Foam::functionObjects::runTimePostPro::geometryBase::geometryBase
 :
     parent_(parent),
     name_(dict.dictName()),
-    visible_(dict.get<bool>("visible")),
+    visible_(dict.lookupOrDefault("visible", true)),
     renderMode_
     (
         renderModeTypeNames.lookupOrDefault("renderMode", dict, rmGouraud)
@@ -121,6 +133,13 @@ Foam::functionObjects::runTimePostPro::geometryBase::parent() const
 }
 
 
+bool Foam::functionObjects::runTimePostPro::geometryBase::
+needsCollective() const
+{
+    return parent_.needsCollective();
+}
+
+
 const Foam::word&
 Foam::functionObjects::runTimePostPro::geometryBase::name() const
 {
diff --git a/src/runTimePostProcessing/geometryBase.H b/src/runTimePostProcessing/geometryBase.H
index 28aa26178189c3eedb8e3d35e9d3a4041b77086f..6fdb2c5ba5129ce7d55a46c23fde05c3c265acd0 100644
--- a/src/runTimePostProcessing/geometryBase.H
+++ b/src/runTimePostProcessing/geometryBase.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,12 +25,12 @@ Class
     Foam::functionObjects::runTimePostPro::geometryBase
 
 Description
-    Base class for surface, text handling
+    Base class for surface, text handling etc.
 
     Dictionary controls
     \table
         Property    | Description                          | Required | Default
-        visible     | Display the object                   | yes |
+        visible     | Display the object                   | no  | yes
         renderMode  | Shading (flat/gouraud/phong)         | no  | gouraud
         opacity     | Object opacity                       | no  | 1.0
     \endtable
@@ -51,7 +51,7 @@ SourceFiles
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward declarations
+// Forward Declarations (VTK)
 class vtkRenderer;
 class vtkActor;
 
@@ -60,7 +60,7 @@ namespace Foam
 namespace functionObjects
 {
 
-// Forward declarations
+// Forward Declarations
 class runTimePostProcessing;
 
 namespace runTimePostPro
@@ -73,11 +73,11 @@ namespace runTimePostPro
 
 class geometryBase
 {
-
 public:
 
-    // Public enumerations
+    // Public Enumerations
 
+        //- Surface shading types
         enum renderModeType
         {
             rmFlat,             //!< Flat shading
@@ -85,6 +85,7 @@ public:
             rmPhong             //!< Phong shading
         };
 
+        //- Names for surface shading types
         static const Enum<renderModeType> renderModeTypeNames;
 
 
@@ -95,7 +96,7 @@ protected:
         //- Reference to the parent function object
         const runTimePostProcessing& parent_;
 
-        //- Name
+        //- The surface name
         word name_;
 
         //- Visible flag
@@ -111,7 +112,7 @@ protected:
         const HashPtrTable<Function1<vector>>& colours_;
 
 
-    // Protected functions
+    // Protected Functions
 
         //- Initialise actor
         void initialiseActor(vtkActor* actor) const;
@@ -125,6 +126,9 @@ protected:
 
 public:
 
+    //- Debug switch
+    static int debug;
+
     // Constructors
 
         //- Construct from dictionary
@@ -147,6 +151,10 @@ public:
         //- Return the reference to the parent function object
         const runTimePostProcessing& parent() const;
 
+        //- May need to gather geometry parts to render on single-processor
+        //  True when OpenFOAM is running in parallel but VTK is not.
+        bool needsCollective() const;
+
         //- Return the name
         const word& name() const;
 
@@ -160,7 +168,7 @@ public:
         const HashPtrTable<Function1<vector>>& colours() const;
 
 
-    // Scene interaction
+    // Scene Interaction
 
         //- Add geometry to scene
         virtual void addGeometryToScene
@@ -172,7 +180,7 @@ public:
         //- Update the actors
         virtual void updateActors(const scalar position) = 0;
 
-        //- Clear files used to create the object(s)
+        //- Clear any files used to create the object(s)
         virtual bool clear() = 0;
 };
 
diff --git a/src/runTimePostProcessing/geometryCloud.C b/src/runTimePostProcessing/geometryCloud.C
new file mode 100644
index 0000000000000000000000000000000000000000..350c714f8bf5ac217048e866948dfacd2ab5918c
--- /dev/null
+++ b/src/runTimePostProcessing/geometryCloud.C
@@ -0,0 +1,263 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "geometryCloud.H"
+#include "cloud.H"
+#include "fvMesh.H"
+#include "runTimePostProcessing.H"
+#include "addToRunTimeSelectionTable.H"
+
+// VTK includes
+#include "vtkActor.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkCompositePolyDataMapper.h"
+#include "vtkPolyData.h"
+#include "vtkPolyDataMapper.h"
+#include "vtkProperty.h"
+#include "vtkRenderer.h"
+#include "vtkSmartPointer.h"
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+    defineTypeName(geometryCloud);
+    addToRunTimeSelectionTable(pointData, geometryCloud, dictionary);
+}
+}
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+bool Foam::functionObjects::runTimePostPro::geometryCloud::addCloudField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const objectRegistry& obrTmp,
+    const word& fieldName
+) const
+{
+    const regIOobject* ioptr = obrTmp.cfindObject<regIOobject>(fieldName);
+
+    return (multiPiece) &&
+    (
+        addCloudField<label>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addCloudField<scalar>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addCloudField<vector>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addCloudField<sphericalTensor>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addCloudField<symmTensor>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addCloudField<tensor>
+        (
+            multiPiece, ioptr, fieldName
+        )
+    );
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::geometryCloud::geometryCloud
+(
+    const runTimePostProcessing& parent,
+    const dictionary& dict,
+    const HashPtrTable<Function1<vector>>& colours
+)
+:
+    pointData(parent, dict, colours),
+    fieldVisualisationBase(dict, colours),
+    cloudName_(dict.get<word>("cloud")),
+    colourFieldName_(dict.get<word>("colourField")),
+    actor_()
+{}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::geometryCloud::
+~geometryCloud()
+{}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::runTimePostPro::geometryCloud::
+addGeometry
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (!visible_)
+    {
+        return false;
+    }
+
+
+    // This is similar (almost identical) to vtkCloud
+
+    const auto* objPtr = parent().mesh().cfindObject<cloud>(cloudName_);
+    if (!objPtr)
+    {
+        return false;
+    }
+
+    objectRegistry obrTmp
+    (
+        IOobject
+        (
+            "runTimePostPro::cloud::" + cloudName_,
+            parent().mesh().time().constant(),
+            parent().mesh(),
+            IOobject::NO_READ,
+            IOobject::NO_WRITE,
+            false
+        )
+    );
+
+    objPtr->writeObjects(obrTmp);
+
+    const auto* pointsPtr = obrTmp.findObject<vectorField>("position");
+
+    if (!pointsPtr)
+    {
+        // This should be impossible
+        return false;
+    }
+
+
+    // Create a vtkMultiPieceDataSet with vtkPolyData on the leaves
+    auto multiPiece = gatherCloud(obrTmp);
+
+    // Add in scaleField and colourField
+    addCloudField(multiPiece, obrTmp, fieldName_);
+    addCloudField(multiPiece, obrTmp, colourFieldName_);
+
+
+    // Not rendered on this processor?
+    // This is where we stop, but could also have an MPI barrier
+    if (!renderer)
+    {
+        return true;
+    }
+
+
+    // Rendering
+
+    actor_ = vtkSmartPointer<vtkActor>::New();
+
+    {
+        fieldSummary scaleFieldInfo =
+            queryFieldSummary(fieldName_, multiPiece);
+
+        fieldSummary colourFieldInfo =
+            queryFieldSummary(colourFieldName_, multiPiece);
+
+        auto polyData = vtkSmartPointer<vtkCompositeDataGeometryFilter>::New();
+
+        polyData->SetInputData(multiPiece);
+        polyData->Update();
+
+        auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
+
+        actor_->SetMapper(mapper);
+
+        /// dataset->Print(std::cout);
+
+        addGlyphs
+        (
+            position,
+            fieldName_, scaleFieldInfo,         // scaling
+            colourFieldName_, colourFieldInfo,  // colour
+            maxGlyphLength_,
+            polyData->GetOutput(),
+            actor_,
+            renderer
+        );
+
+        renderer->AddActor(actor_);
+    }
+
+    return true;
+}
+
+
+void Foam::functionObjects::runTimePostPro::geometryCloud::
+addGeometryToScene
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    // Live source
+    addGeometry(position, renderer);
+}
+
+
+void Foam::functionObjects::runTimePostPro::geometryCloud::updateActors
+(
+    const scalar position
+)
+{
+    if (actor_)
+    {
+        const vector colour = pointColour_->value(position);
+
+        vtkProperty* prop = actor_->GetProperty();
+
+        prop->SetOpacity(opacity(position));
+
+        prop->SetColor(colour[0], colour[1], colour[2]);
+    }
+}
+
+
+bool Foam::functionObjects::runTimePostPro::geometryCloud::clear()
+{
+    return false;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometryCloud.H b/src/runTimePostProcessing/geometryCloud.H
new file mode 100644
index 0000000000000000000000000000000000000000..a420307fe3c1aa2c243622af9ad0591f12a0219b
--- /dev/null
+++ b/src/runTimePostProcessing/geometryCloud.H
@@ -0,0 +1,201 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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::functionObjects::runTimePostPro::geometryCloud
+
+Description
+    Visualisation of cloud data from function object output (file-based only).
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        type        | The point type: geometryCloud         | yes |
+        cloud         | The cloud name                      | no  |
+        field         | The field for glyphs scaling        | no  |
+        colourField   | The field to display                | no  |
+    \endtable
+
+SourceFiles
+    geometryCloud.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_runTimePostPro_geometryCloud_H
+#define functionObjects_runTimePostPro_geometryCloud_H
+
+#include "pointData.H"
+#include "functionObjectBase.H"
+#include "IOField.H"
+
+#include "vtkSmartPointer.h"
+#include "vtkMultiPieceDataSet.h"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+
+/*---------------------------------------------------------------------------*\
+                     Class geometryCloud Declaration
+\*---------------------------------------------------------------------------*/
+
+class geometryCloud
+:
+    public pointData,
+    public fieldVisualisationBase
+{
+protected:
+
+    // Protected Data
+
+        //- Name of geometryCloud
+        word cloudName_;
+
+        //- Name of field to colour by
+        word colourFieldName_;
+
+        //- Actor
+        vtkSmartPointer<vtkActor> actor_;
+
+
+    // Protected Member Functions
+
+        //- Gather and convert cloud positions with vtkPolyData for the leaves.
+        //  If VTK is also running in parallel, each cloud is left
+        //  as a processor-local piece. Otherwise all processor-local
+        //  parts are gathered onto the master in their correponding
+        //  slots.
+        vtkSmartPointer<vtkMultiPieceDataSet>
+        gatherCloud(const objectRegistry& obrTmp) const;
+
+
+        //- Add field
+        template<class Type>
+        bool addField
+        (
+            vtkDataSet* piece,
+            const Field<Type>& fld,
+            const word& fieldName
+        ) const;
+
+        //- Add field
+        template<class Type>
+        bool addCloudField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const IOField<Type>* fldptr,
+            const word& fieldName
+        ) const;
+
+        //- Add field
+        template<class Type>
+        bool addCloudField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const regIOobject* fieldPtr,
+            const word& fieldName
+        ) const;
+
+        //- Add field
+        bool addCloudField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const objectRegistry& obrTmp,
+            const word& fieldName
+        ) const;
+
+
+        //- No copy construct
+        geometryCloud(const geometryCloud&) = delete;
+
+        //- No copy assignment
+        void operator=(const geometryCloud&) = delete;
+
+
+public:
+
+    //- Run-time type information
+    TypeNameNoDebug("geometryCloud");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        geometryCloud
+        (
+            const runTimePostProcessing& parent,
+            const dictionary& dict,
+            const HashPtrTable<Function1<vector>>& colours
+        );
+
+
+    //- Destructor
+    virtual ~geometryCloud();
+
+
+    // Member Functions
+
+        //- Add cloud to scene (from simulation)
+        bool addGeometry
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Add cloud to scene
+        virtual void addGeometryToScene
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Update actors
+        virtual void updateActors(const scalar position);
+
+        //- No-op
+        virtual bool clear();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace runTimePostPro
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "geometryCloudTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometryCloudGather.C b/src/runTimePostProcessing/geometryCloudGather.C
new file mode 100644
index 0000000000000000000000000000000000000000..c8eb4e0d47530add3c6c5900e484ed59ecba47da
--- /dev/null
+++ b/src/runTimePostProcessing/geometryCloudGather.C
@@ -0,0 +1,129 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "geometryCloud.H"
+#include "cloud.H"
+#include "runTimePostProcessing.H"
+
+#include "foamVtkTools.H"
+
+// VTK includes
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPolyData.h"
+#include "vtkSmartPointer.h"
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+vtkSmartPointer<vtkMultiPieceDataSet>
+Foam::functionObjects::runTimePostPro::geometryCloud::gatherCloud
+(
+    const objectRegistry& obrTmp
+) const
+{
+    auto multiPiece = vtkSmartPointer<vtkMultiPieceDataSet>::New();
+    multiPiece->SetNumberOfPieces(Pstream::nProcs());
+
+    const auto* pointsPtr = obrTmp.findObject<vectorField>("position");
+
+    if (!needsCollective())
+    {
+        // Simple case (serial-serial, parallel-parallel)
+
+        if (pointsPtr && pointsPtr->size())
+        {
+            multiPiece->SetPiece
+            (
+                Pstream::myProcNo(),
+                Foam::vtk::Tools::Vertices(*pointsPtr)
+            );
+        }
+    }
+    else if (Pstream::master())
+    {
+        // Gather pieces on master
+
+        if (pointsPtr && pointsPtr->size())
+        {
+            // Add myself
+
+            multiPiece->SetPiece
+            (
+                Pstream::myProcNo(),
+                Foam::vtk::Tools::Vertices(*pointsPtr)
+            );
+        }
+
+        // Receive points
+        pointField points;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            points.clear();
+
+            fromSlave >> points;
+
+            if (points.size())
+            {
+                multiPiece->SetPiece
+                (
+                    slave,
+                    Foam::vtk::Tools::Vertices(points)
+                );
+            }
+        }
+    }
+    else
+    {
+        // Slave - send points
+
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        if (pointsPtr)
+        {
+            toMaster << *pointsPtr;
+        }
+        else
+        {
+            toMaster << pointField();
+        }
+    }
+
+    return multiPiece;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometryCloudTemplates.C b/src/runTimePostProcessing/geometryCloudTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..b45cb40246df81adb328a9ed8dbcab1eadc3eea3
--- /dev/null
+++ b/src/runTimePostProcessing/geometryCloudTemplates.C
@@ -0,0 +1,168 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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 "foamVtkTools.H"
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class Type>
+bool Foam::functionObjects::runTimePostPro::geometryCloud::addField
+(
+    vtkDataSet* piece,
+    const Field<Type>& fld,
+    const word& fieldName
+) const
+{
+    if (!piece) return false;
+
+    auto vtkfield = Foam::vtk::Tools::convertFieldToVTK<Type>(fieldName, fld);
+
+    // Only has verts
+    piece->GetPointData()->AddArray(vtkfield);
+
+    return true;
+}
+
+
+template<class Type>
+bool Foam::functionObjects::runTimePostPro::geometryCloud::addCloudField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const IOField<Type>* fldptr,
+    const word& fieldName
+) const
+{
+    if (!multiPiece)
+    {
+        return false;
+    }
+
+    if (!needsCollective())
+    {
+        // Simple case (serial-serial, parallel-parallel)
+
+        return fldptr &&
+            addField<Type>
+            (
+                multiPiece->GetPiece(Pstream::myProcNo()),
+                *fldptr,
+                fieldName
+            );
+    }
+
+
+    // Gather fields
+    const bool ok = returnReduce((fldptr != nullptr), orOp<bool>());
+
+    if (!ok)
+    {
+        return false;
+    }
+
+    if (Pstream::master())
+    {
+        if (fldptr)
+        {
+            // My field data
+            addField<Type>
+            (
+                multiPiece->GetPiece(Pstream::myProcNo()),
+                *fldptr,
+                fieldName
+            );
+        }
+
+        // Receive field data
+        Field<Type> recv;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            recv.clear();
+
+            fromSlave
+                >> recv;
+
+            if (recv.size())
+            {
+                addField<Type>
+                (
+                    multiPiece->GetPiece(slave),
+                    recv,
+                    fieldName
+                );
+            }
+        }
+    }
+    else
+    {
+        // Slave - send field data
+
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        if (fldptr)
+        {
+            toMaster
+                << *fldptr;
+        }
+        else
+        {
+            toMaster
+                << List<Type>();
+        }
+    }
+
+    return ok;
+}
+
+
+template<class Type>
+bool Foam::functionObjects::runTimePostPro::geometryCloud::addCloudField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    return addCloudField<Type>
+    (
+        multiPiece,
+        dynamic_cast<const IOField<Type>*>(ioptr),
+        fieldName
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometryPatches.C b/src/runTimePostProcessing/geometryPatches.C
new file mode 100644
index 0000000000000000000000000000000000000000..c3e711b5afac86f7093d5fcc0e6ed1115723afa8
--- /dev/null
+++ b/src/runTimePostProcessing/geometryPatches.C
@@ -0,0 +1,315 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "geometryPatches.H"
+#include "fvMesh.H"
+#include "volFields.H"
+#include "emptyPolyPatch.H"
+#include "processorPolyPatch.H"
+#include "foamVtkTools.H"
+#include "runTimePostProcessing.H"
+#include "addToRunTimeSelectionTable.H"
+
+// VTK includes
+#include "vtkActor.h"
+#include "vtkCellArray.h"
+#include "vtkCellData.h"
+#include "vtkCellDataToPointData.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkCompositePolyDataMapper.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPointData.h"
+#include "vtkPolyData.h"
+#include "vtkPolyDataMapper.h"
+#include "vtkProperty.h"
+#include "vtkRenderer.h"
+#include "vtkSmartPointer.h"
+
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+    defineTypeName(geometryPatches);
+    addToRunTimeSelectionTable(surface, geometryPatches, dictionary);
+}
+}
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::geometryPatches::geometryPatches
+(
+    const runTimePostProcessing& parent,
+    const dictionary& dict,
+    const HashPtrTable<Function1<vector>>& colours
+)
+:
+    geometrySurface(parent, dict, colours, List<fileName>()),
+    fieldVisualisationBase(dict, colours),
+    selectPatches_(),
+    nearCellValue_(dict.lookupOrDefault("nearCellValue", false))
+{
+    dict.readEntry("patches", selectPatches_);
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+void Foam::functionObjects::runTimePostPro::geometryPatches::addGeometryToScene
+(
+    const scalar position,
+    vtkRenderer* renderer
+)
+{
+    if (!visible_ || selectPatches_.empty())
+    {
+        return;
+    }
+
+    const polyBoundaryMesh& patches = parent().mesh().boundaryMesh();
+
+    bitSet selectedPatchIds(patches.size());
+
+    for (const polyPatch& pp : patches)
+    {
+        if (isType<emptyPolyPatch>(pp) || pp.empty())
+        {
+            continue;
+        }
+        else if (isA<processorPolyPatch>(pp))
+        {
+            break; // No processor patches
+        }
+
+        if (selectPatches_.match(pp.name()))
+        {
+            selectedPatchIds.set(pp.index());
+        }
+    }
+
+
+    labelListList patchIds(Pstream::nProcs());
+    patchIds[Pstream::myProcNo()] = selectedPatchIds.sortedToc();
+
+    Pstream::gatherList(patchIds);
+    Pstream::scatterList(patchIds);
+
+    label nPieces = 0;
+    for (const labelList& ids : patchIds)
+    {
+        nPieces += ids.size();
+    }
+
+    /// Pout<< "add patches: " << selectPatches_ << nl
+    ///     << " = " << patchIds << " == " << nPieces << " total" << nl;
+
+    if (!nPieces)
+    {
+        WarningInFunction
+            << "No patches selected: " << flatOutput(selectPatches_)
+            << endl;
+        return;
+    }
+
+    DebugInfo << "    Add geometry patches" << nl;
+
+
+    // Create a vtkMultiPieceDataSet with vtkPolyData on the leaves
+
+    // When adding fields, only map scalar/vector fields.
+    // - this is easier and all we mostly ever need
+
+    vtkSmartPointer<vtkMultiPieceDataSet> multiPiece;
+
+    // Requesting glyphs on the surface - just use the faceCentres directly
+    // and attach fields as CellData (not PointData).
+
+    if (representation_ == rtGlyph)
+    {
+        multiPiece = gatherPatchFaceCentres(patchIds);
+    }
+    else
+    {
+        multiPiece = gatherPatchPieces(patchIds);
+    }
+
+
+    // Add (scalar/vector) field.
+    // - Need field(s) for glyphs or colourByField:
+
+    int nCmpt = 0;
+    if (representation_ == rtGlyph || colourBy_ == cbField)
+    {
+        if (!nCmpt)
+        {
+            nCmpt = addPatchField<scalar>
+            (
+                multiPiece,
+                patchIds,
+                parent().mesh().cfindObject<volScalarField>(fieldName_),
+                fieldName_
+            );
+        }
+        if (!nCmpt)
+        {
+            nCmpt = addPatchField<vector>
+            (
+                multiPiece,
+                patchIds,
+                parent().mesh().cfindObject<volVectorField>(fieldName_),
+                fieldName_
+            );
+        }
+    }
+
+    // Now have a multi-piece dataset with
+    // one piece per patch and processor.
+    //
+    // For VTK=parallel, these pieces reside on their original processors.
+    // For VTK=serial, they are master only
+
+    // Re-query actually field information, since we may have stored it
+    // somewhere slightly different than the original source.
+
+    fieldSummary fieldInfo = queryFieldSummary(fieldName_, multiPiece);
+    fieldInfo.reduce();
+
+
+    // Not rendering on this processor?
+    // This is where we stop, but could also have a MPI barrier
+    if (!renderer)
+    {
+        return;
+    }
+
+
+    // Rendering
+
+    {
+        auto polyData = vtkSmartPointer<vtkCompositeDataGeometryFilter>::New();
+
+        polyData->SetInputData(multiPiece);
+        polyData->Update();
+
+        if (representation_ == rtGlyph)
+        {
+            addGlyphs
+            (
+                position,
+                fieldName_, fieldInfo,  // scaling
+                fieldName_, fieldInfo,  // colouring
+                maxGlyphLength_,
+                polyData->GetOutput(),
+                surfaceActor_,
+                renderer
+            );
+        }
+        else
+        {
+            vtkSmartPointer<vtkCellDataToPointData> cellToPoint;
+
+            // CellData - Need a cell->point filter
+            if (smooth_ && !fieldInfo.hasPointData())
+            {
+                cellToPoint = vtkSmartPointer<vtkCellDataToPointData>::New();
+                cellToPoint->SetInputData(multiPiece);
+
+                polyData->SetInputConnection(cellToPoint->GetOutputPort());
+            }
+            else
+            {
+                polyData->SetInputData(multiPiece);
+            }
+            polyData->Update();
+
+
+            if (!smooth_)
+            {
+                addFeatureEdges(renderer, polyData);
+            }
+
+            auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
+            mapper->SetInputConnection(polyData->GetOutputPort());
+
+            setField
+            (
+                position,
+                fieldName_,
+                (
+                    smooth_
+                  ? FieldAssociation::POINT_DATA
+                  : FieldAssociation::CELL_DATA
+                ),
+                mapper,
+                renderer
+            );
+
+            surfaceActor_->SetMapper(mapper);
+
+            setRepresentation(surfaceActor_);
+
+            renderer->AddActor(surfaceActor_);
+        }
+    }
+}
+
+
+void Foam::functionObjects::runTimePostPro::geometryPatches::updateActors
+(
+    const scalar position
+)
+{
+    if (!visible_)
+    {
+        return;
+    }
+
+    surface::updateActors(position);
+
+    surfaceActor_->GetProperty()->SetOpacity(opacity(position));
+
+    vector sc = surfaceColour_->value(position);
+    surfaceActor_->GetProperty()->SetColor(sc[0], sc[1], sc[2]);
+
+    vector ec = edgeColour_->value(position);
+    surfaceActor_->GetProperty()->SetEdgeColor(ec[0], ec[1], ec[2]);
+}
+
+
+bool Foam::functionObjects::runTimePostPro::geometryPatches::clear()
+{
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometryPatches.H b/src/runTimePostProcessing/geometryPatches.H
new file mode 100644
index 0000000000000000000000000000000000000000..cb7a8de79c2c1f552d8a30e12bfcf35a0efc12ba
--- /dev/null
+++ b/src/runTimePostProcessing/geometryPatches.H
@@ -0,0 +1,171 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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::functionObjects::runTimePostPro::geometryPatches
+
+Description
+    Visualisation of OpenFOAM patches and fields.
+
+    \heading Basic Usage
+    \table
+        Property    | Description                           | Required | Default
+        type        | The surface type: patches             | yes |
+        patches     | Patches to display (wordRe list)      | yes |
+    \endtable
+
+SourceFiles
+    geometryPatches.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_runTimePostPro_geometryPatches_H
+#define functionObjects_runTimePostPro_geometryPatches_H
+
+#include "surface.H"
+#include "geometrySurface.H"
+#include "fieldVisualisationBase.H"
+#include "volFieldsFwd.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+
+/*---------------------------------------------------------------------------*\
+                       Class geometryPatches Declaration
+\*---------------------------------------------------------------------------*/
+
+class geometryPatches
+:
+    public geometrySurface,
+    public fieldVisualisationBase
+{
+protected:
+
+    // Protected Data
+
+        //- Requested names of patches to process
+        wordRes selectPatches_;
+
+        //- Use cell value on patches instead of patch value itself
+        bool nearCellValue_;
+
+
+    // Protected Member Functions
+
+        //- Gather and convert patches to multi-piece dataset with
+        //- vtkPolyData for each patch/processor.
+        //  For serial, the pieces are gathered to the master.
+        vtkSmartPointer<vtkMultiPieceDataSet>
+        gatherPatchPieces(const labelListList& patchIds) const;
+
+        //- Gather and convert patch face centres to multi-piece dataset with
+        //- vtkPolyData for each patch/processor.
+        //  For serial, the pieces are gathered to the master.
+        vtkSmartPointer<vtkMultiPieceDataSet>
+        gatherPatchFaceCentres(const labelListList& patchIds) const;
+
+
+    // Adding Fields - multi-piece
+
+        //- Add patch values.
+        //  For nCells == nPoints (eg, only has face centres) add as PointData.
+        //  \return 0 on failure to map and nCmpt (eg, 1=scalar, 3=vector)
+        //  on success.
+        template<class Type>
+        int addPatchField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const labelListList& patchIds,
+            const GeometricField<Type, fvPatchField, volMesh>* fldptr,
+            const word& fieldName
+        ) const;
+
+
+        //- No copy construct
+        geometryPatches(const geometryPatches&) = delete;
+
+        //- No copy assignment
+        void operator=(const geometryPatches&) = delete;
+
+
+public:
+
+    //- Run-time type information
+    TypeNameNoDebug("patches");
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        geometryPatches
+        (
+            const runTimePostProcessing& parent,
+            const dictionary& dict,
+            const HashPtrTable<Function1<vector>>& colours
+        );
+
+
+    //- Destructor
+    virtual ~geometryPatches() = default;
+
+
+    // Member Functions
+
+        //- Add geometry surface(s) to scene
+        virtual void addGeometryToScene
+        (
+            const scalar position,
+            vtkRenderer* renderer
+        );
+
+        //- Update actors
+        virtual void updateActors(const scalar position);
+
+        //- Clear files used to create the object(s)
+        virtual bool clear();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace runTimePostPro
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "geometryPatchesTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometryPatchesGather.C b/src/runTimePostProcessing/geometryPatchesGather.C
new file mode 100644
index 0000000000000000000000000000000000000000..ce03fa02bbda36d5a40ff9eaf10323f5de08b0ad
--- /dev/null
+++ b/src/runTimePostProcessing/geometryPatchesGather.C
@@ -0,0 +1,293 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "geometryPatches.H"
+#include "fvMesh.H"
+#include "volFields.H"
+#include "emptyPolyPatch.H"
+#include "processorPolyPatch.H"
+#include "foamVtkTools.H"
+#include "runTimePostProcessing.H"
+
+// VTK includes
+#include "vtkCellArray.h"
+#include "vtkCellData.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPointData.h"
+#include "vtkPolyData.h"
+#include "vtkSmartPointer.h"
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+vtkSmartPointer<vtkMultiPieceDataSet>
+Foam::functionObjects::runTimePostPro::geometryPatches::gatherPatchPieces
+(
+    const labelListList& patchIds
+) const
+{
+    const polyBoundaryMesh& patches = parent().mesh().boundaryMesh();
+
+    label nPieces = 0;
+    for (const labelList& ids : patchIds)
+    {
+        nPieces += ids.size();
+    }
+
+    auto multiPiece = vtkSmartPointer<vtkMultiPieceDataSet>::New();
+    multiPiece->SetNumberOfPieces(nPieces);
+
+    label pieceId = 0;
+
+    if (!needsCollective())
+    {
+        // Simple case
+
+        for (int proci=0; proci < Pstream::myProcNo(); ++proci)
+        {
+            pieceId += patchIds[proci].size();
+        }
+
+        for (const label patchId : patchIds[Pstream::myProcNo()])
+        {
+            const polyPatch& pp = patches[patchId];
+
+            multiPiece->SetPiece
+            (
+                pieceId,
+                Foam::vtk::Tools::Patch::mesh(pp)
+            );
+
+            ++pieceId;
+        }
+    }
+    else if (Pstream::master())
+    {
+        // Gather pieces on master
+
+        // Add myself
+        for (const label patchId : patchIds[Pstream::myProcNo()])
+        {
+            const polyPatch& pp = patches[patchId];
+
+            multiPiece->SetPiece
+            (
+                pieceId,
+                Foam::vtk::Tools::Patch::mesh(pp)
+            );
+
+            ++pieceId;
+        }
+
+        // Receive surfaces
+        pointField points;
+        faceList faces;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            const label nSlavePatches = patchIds[slave].size();
+
+            if (!nSlavePatches)
+            {
+                continue;
+            }
+
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            for (label recvi=0; recvi < nSlavePatches; ++recvi)
+            {
+                points.clear();
+                faces.clear();
+
+                fromSlave
+                    >> points >> faces;
+
+                multiPiece->SetPiece
+                (
+                    pieceId,
+                    Foam::vtk::Tools::Patch::mesh(points, faces)
+                );
+
+                ++pieceId;
+            }
+        }
+    }
+    else
+    {
+        // Slave - send surfaces
+        const labelList& slavePatchIds = patchIds[Pstream::myProcNo()];
+
+        if (slavePatchIds.size())
+        {
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            for (const label patchId : patchIds[Pstream::myProcNo()])
+            {
+                const polyPatch& pp = patches[patchId];
+
+                toMaster
+                    << pp.localPoints() << pp.localFaces();
+            }
+        }
+    }
+
+    return multiPiece;
+}
+
+
+vtkSmartPointer<vtkMultiPieceDataSet>
+Foam::functionObjects::runTimePostPro::geometryPatches::gatherPatchFaceCentres
+(
+    const labelListList& patchIds
+) const
+{
+    const polyBoundaryMesh& patches = parent().mesh().boundaryMesh();
+
+    label nPieces = 0;
+    for (const labelList& ids : patchIds)
+    {
+        nPieces += ids.size();
+    }
+
+    auto multiPiece = vtkSmartPointer<vtkMultiPieceDataSet>::New();
+    multiPiece->SetNumberOfPieces(nPieces);
+
+    label pieceId = 0;
+
+    if (!needsCollective())
+    {
+        // Simple case
+
+        for (int proci=0; proci < Pstream::myProcNo(); ++proci)
+        {
+            pieceId += patchIds[proci].size();
+        }
+
+        for (const label patchId : patchIds[Pstream::myProcNo()])
+        {
+            const polyPatch& pp = patches[patchId];
+
+            auto geom = vtkSmartPointer<vtkPolyData>::New();
+
+            geom->SetPoints(Foam::vtk::Tools::Patch::faceCentres(pp));
+            geom->SetVerts(Foam::vtk::Tools::identityVertices(pp.size()));
+
+            multiPiece->SetPiece(pieceId, geom);
+
+            ++pieceId;
+        }
+    }
+    else if (Pstream::master())
+    {
+        // Gather pieces (face centres) on master
+
+        // Add myself
+        for (const label patchId : patchIds[Pstream::myProcNo()])
+        {
+            const polyPatch& pp = patches[patchId];
+
+            auto geom = vtkSmartPointer<vtkPolyData>::New();
+
+            geom->SetPoints(Foam::vtk::Tools::Patch::faceCentres(pp));
+            geom->SetVerts(Foam::vtk::Tools::identityVertices(pp.size()));
+
+            multiPiece->SetPiece(pieceId, geom);
+
+            ++pieceId;
+        }
+
+        // Receive points
+        pointField points;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            const label nSlavePatches = patchIds[slave].size();
+
+            if (!nSlavePatches)
+            {
+                continue;
+            }
+
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            for (label recvi=0; recvi < nSlavePatches; ++recvi)
+            {
+                points.clear();
+
+                fromSlave >> points;
+
+                multiPiece->SetPiece
+                (
+                    pieceId,
+                    Foam::vtk::Tools::Vertices(points)
+                );
+
+                ++pieceId;
+            }
+        }
+    }
+    else
+    {
+        // Slave - send face centres
+
+        const labelList& slavePatchIds = patchIds[Pstream::myProcNo()];
+
+        if (slavePatchIds.size())
+        {
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            for (const label patchId : patchIds[Pstream::myProcNo()])
+            {
+                const polyPatch& pp = patches[patchId];
+
+                toMaster << pp.faceCentres();
+            }
+        }
+    }
+
+    return multiPiece;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometryPatchesTemplates.C b/src/runTimePostProcessing/geometryPatchesTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..ec8edea73f085941bd68013c44634c2860b3a070
--- /dev/null
+++ b/src/runTimePostProcessing/geometryPatchesTemplates.C
@@ -0,0 +1,221 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "geometryPatches.H"
+#include "fvMesh.H"
+#include "volFields.H"
+#include "foamVtkTools.H"
+
+// VTK includes
+#include "vtkCellData.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPointData.h"
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+int Foam::functionObjects::runTimePostPro::geometryPatches::addPatchField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const labelListList& patchIds,
+    const GeometricField<Type, fvPatchField, volMesh>* fldptr,
+    const word& fieldName
+) const
+{
+    if (!multiPiece || !fldptr)
+    {
+        return 0;
+    }
+
+    const int nCmpt(pTraits<Type>::nComponents);
+
+    const auto& bf = fldptr->boundaryField();
+
+    label pieceId = 0;
+
+    if (!needsCollective())
+    {
+        // Simple case (serial-serial, parallel-parallel)
+
+        for (int proci=0; proci < Pstream::myProcNo(); ++proci)
+        {
+            pieceId += patchIds[proci].size();
+        }
+
+        for (const label patchId : patchIds[Pstream::myProcNo()])
+        {
+            const auto& pf = bf[patchId];
+
+            auto vtkfield =
+            (
+                nearCellValue_
+              ? Foam::vtk::Tools::convertFieldToVTK<Type>
+                (
+                    fieldName,
+                    pf.patchInternalField()()
+                )
+              : Foam::vtk::Tools::convertFieldToVTK<Type>
+                (
+                    fieldName,
+                    pf
+                )
+            );
+
+            auto piece = multiPiece->GetPiece(pieceId);
+
+            if (piece->GetNumberOfCells() == piece->GetNumberOfPoints())
+            {
+                // Only has verts
+                piece->GetPointData()->AddArray(vtkfield);
+            }
+            else
+            {
+                piece->GetCellData()->AddArray(vtkfield);
+            }
+
+            ++pieceId;
+        }
+    }
+    else if (Pstream::master())
+    {
+        // Gather pieces on master
+
+        // Add myself
+        for (const label patchId : patchIds[Pstream::myProcNo()])
+        {
+            const auto& pf = bf[patchId];
+
+            auto vtkfield =
+            (
+                nearCellValue_
+              ? Foam::vtk::Tools::convertFieldToVTK<Type>
+                (
+                    fieldName,
+                    pf.patchInternalField()()
+                )
+              : Foam::vtk::Tools::convertFieldToVTK<Type>
+                (
+                    fieldName,
+                    pf
+                )
+            );
+
+            auto piece = multiPiece->GetPiece(pieceId);
+
+            if (piece->GetNumberOfCells() == piece->GetNumberOfPoints())
+            {
+                // Only has verts
+                piece->GetPointData()->AddArray(vtkfield);
+            }
+            else
+            {
+                piece->GetCellData()->AddArray(vtkfield);
+            }
+
+            ++pieceId;
+        }
+
+        // Receive field
+        Field<Type> recv;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            const label nSlavePatches = patchIds[slave].size();
+
+            if (!nSlavePatches)
+            {
+                continue;
+            }
+
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            for (label recvi=0; recvi < nSlavePatches; ++recvi)
+            {
+                recv.clear();
+
+                fromSlave >> recv;
+
+                auto vtkfield = Foam::vtk::Tools::convertFieldToVTK<Type>
+                (
+                    fieldName,
+                    recv
+                );
+
+                auto piece = multiPiece->GetPiece(pieceId);
+
+                if (piece->GetNumberOfCells() == piece->GetNumberOfPoints())
+                {
+                    // Only has verts
+                    piece->GetPointData()->AddArray(vtkfield);
+                }
+                else
+                {
+                    piece->GetCellData()->AddArray(vtkfield);
+                }
+
+                ++pieceId;
+            }
+        }
+    }
+    else
+    {
+        // Slave - send fields
+        const labelList& slavePatchIds = patchIds[Pstream::myProcNo()];
+
+        if (slavePatchIds.size())
+        {
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            for (const label patchId : patchIds[Pstream::myProcNo()])
+            {
+                const auto& pf = bf[patchId];
+
+                if (nearCellValue_)
+                {
+                    toMaster << pf.patchInternalField()();
+                }
+                else
+                {
+                    toMaster << pf;
+                }
+            }
+        }
+    }
+
+    return nCmpt;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/geometrySurface.C b/src/runTimePostProcessing/geometrySurface.C
index 59e1a3f3e1a0497f8651c07d0641ffcf913f73c4..ee6fc254d0171207cd73c2c96c19b43bb5f2ae99 100644
--- a/src/runTimePostProcessing/geometrySurface.C
+++ b/src/runTimePostProcessing/geometrySurface.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -75,7 +75,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
     // Not extremely elegant...
     vtkSmartPointer<vtkPolyData> dataset;
 
-    if (fName.ext() == "vtk")
+    if ("vtk" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkPolyDataReader>::New();
 
@@ -86,7 +86,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
         return dataset;
     }
 
-    if (fName.ext() == "vtp")
+    if ("vtp" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkXMLPolyDataReader>::New();
 
@@ -97,7 +97,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
         return dataset;
     }
 
-    if (fName.ext() == "obj")
+    if ("obj" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkOBJReader>::New();
 
@@ -108,7 +108,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
         return dataset;
     }
 
-    if (fName.ext() == "stl" || fName.ext() == "stlb")
+    if ("stl" == fName.ext() || "stlb" == fName.ext())
     {
         auto reader = vtkSmartPointer<vtkSTLReader>::New();
 
@@ -145,6 +145,12 @@ void Foam::functionObjects::runTimePostPro::geometrySurface::addGeometryToScene
     const fileName& fName
 ) const
 {
+    // Master-only, since that is where the files are.
+    if (!visible_ || !renderer || !Pstream::master())
+    {
+        return;
+    }
+
     if (representation_ == rtGlyph)
     {
         FatalErrorInFunction
@@ -152,9 +158,11 @@ void Foam::functionObjects::runTimePostPro::geometrySurface::addGeometryToScene
             << " object" << exit(FatalError);
     }
 
+    DebugInfo << "    Add geometry surface: " << fName << nl;
+
     auto surf = getPolyDataFile(fName);
 
-    if (!surf || surf->GetNumberOfPoints() == 0)
+    if (!surf || !surf->GetNumberOfPoints())
     {
         FatalErrorInFunction
             << "Could not read "<< fName << nl
diff --git a/src/runTimePostProcessing/geometrySurface.H b/src/runTimePostProcessing/geometrySurface.H
index d8eabebdb66b99463d3d9a3b12a4ba912a07023c..b3cf98ee07da89c7a21c04d0e3d80e02c23dd670 100644
--- a/src/runTimePostProcessing/geometrySurface.H
+++ b/src/runTimePostProcessing/geometrySurface.H
@@ -25,7 +25,18 @@ Class
     Foam::functionObjects::runTimePostPro::geometrySurface
 
 Description
-    Visualisation of surface geometry data
+    Read and visualize surface geometry files.
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        type        | The type: geometry                    | yes |
+        files       | The files to read                     | yes |
+    \endtable
+
+    Standard file types (vtk, vtp, obj, stl, stlb) are read with the
+    VTK-native readers. Other file types use the OpenFOAM surfMesh
+    readers and convert to VTK.
 
 SourceFiles
     geometrySurface.C
@@ -39,7 +50,7 @@ SourceFiles
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward declarations
+// Forward Declarations
 class vtkPolyData;
 
 namespace Foam
@@ -59,7 +70,7 @@ class geometrySurface
 {
 protected:
 
-    // Protected data
+    // Protected Data
 
         //- File names
         List<fileName> fileNames_;
@@ -124,7 +135,7 @@ public:
         //- Update actors
         virtual void updateActors(const scalar position);
 
-        //- Clear files used to create the object(s)
+        //- Clear files used to create the object(s) - no-op
         virtual bool clear();
 };
 
diff --git a/src/runTimePostProcessing/pathline.C b/src/runTimePostProcessing/pathline.C
index bf145a47e0153f47062ba837c44ac472aad2992e..1642b4496b4e74bb38319825ccc0925e32345baa 100644
--- a/src/runTimePostProcessing/pathline.C
+++ b/src/runTimePostProcessing/pathline.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -44,7 +44,7 @@ namespace functionObjects
 {
 namespace runTimePostPro
 {
-    defineTypeNameAndDebug(pathline, 0);
+    defineTypeName(pathline);
     defineRunTimeSelectionTable(pathline, dictionary);
 }
 }
@@ -92,7 +92,6 @@ void Foam::functionObjects::runTimePostPro::pathline::addLines
             mapper->SetInputData(data);
             mapper->Update();
             break;
-
         }
         case rtTube:
         {
@@ -105,9 +104,7 @@ void Foam::functionObjects::runTimePostPro::pathline::addLines
 
             mapper->SetInputConnection(tubes->GetOutputPort());
             mapper->Update();
-
             break;
-
         }
         case rtVector:
         {
@@ -131,7 +128,7 @@ Foam::functionObjects::runTimePostPro::pathline::pathline
     (
         representationTypeNames.get("representation", dict)
     ),
-    tubeRadius_(0.0),
+    tubeRadius_(0.001),
     lineColour_(nullptr)
 {
     if (dict.found("lineColour"))
@@ -163,7 +160,6 @@ Foam::functionObjects::runTimePostPro::pathline::pathline
             break;
         }
     }
-
 }
 
 
@@ -178,10 +174,7 @@ Foam::functionObjects::runTimePostPro::pathline::New
     const word& pathlineType
 )
 {
-    if (debug)
-    {
-        Info<< "Selecting pathline " << pathlineType << endl;
-    }
+    DebugInfo << "Selecting pathline " << pathlineType << endl;
 
     auto cstrIter = dictionaryConstructorTablePtr_->cfind(pathlineType);
 
diff --git a/src/runTimePostProcessing/pathline.H b/src/runTimePostProcessing/pathline.H
index 8673110eb526d99a4c2ec8bf7fa63addfe083fc6..44aca2e95c18483bc64dc610548001809df5a5c3 100644
--- a/src/runTimePostProcessing/pathline.H
+++ b/src/runTimePostProcessing/pathline.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -27,6 +27,14 @@ Class
 Description
     Visualisation of line data (eg, streamlines)
 
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        representation| none/line/tube/vector               | yes |
+        lineColour    | Override line colour                | no  |
+        tubeRadius    | Radius for tube representation      | yes |
+    \endtable
+
 SourceFiles
     pathline.C
 
@@ -63,16 +71,18 @@ class pathline
 {
 public:
 
-    // Public enumerations
+    // Public Enumerations
 
+        //- Line representations
         enum representationType
         {
-            rtNone,
-            rtLine,
-            rtTube,
-            rtVector
+            rtNone,     //!< "none"
+            rtLine,     //!< "line"
+            rtTube,     //!< "tube"
+            rtVector    //!< "vector"
         };
 
+        //- Names for line representations
         static const Enum<representationType> representationTypeNames;
 
 
@@ -110,7 +120,7 @@ protected:
 public:
 
     //- Run-time type information
-    TypeName("pathline");
+    TypeNameNoDebug("pathline");
 
 
     // Declare run-time constructor selection table
@@ -142,7 +152,7 @@ public:
 
     // Selectors
 
-        //- Return a reference to the selected RAS model
+        //- Return selected pathline
         static autoPtr<pathline> New
         (
             const runTimePostProcessing& parent,
diff --git a/src/runTimePostProcessing/pointData.C b/src/runTimePostProcessing/pointData.C
index a368f0eeed109ba51503c45717acabb299b95c89..6f013923ab818033e300b9a76d54da00be7b647c 100644
--- a/src/runTimePostProcessing/pointData.C
+++ b/src/runTimePostProcessing/pointData.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -33,8 +33,6 @@ License
 #include "vtkProperty.h"
 #include "vtkRenderer.h"
 #include "vtkSmartPointer.h"
-#include "vtkTubeFilter.h"
-#include "vtkLookupTable.h"
 
 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
 
@@ -44,7 +42,7 @@ namespace functionObjects
 {
 namespace runTimePostPro
 {
-    defineTypeNameAndDebug(pointData, 0);
+    defineTypeName(pointData);
     defineRunTimeSelectionTable(pointData, dictionary);
 }
 }
@@ -139,10 +137,7 @@ Foam::functionObjects::runTimePostPro::pointData::New
     const word& pointDataType
 )
 {
-    if (debug)
-    {
-        Info<< "Selecting pointData " << pointDataType << endl;
-    }
+    DebugInfo << "Selecting pointData " << pointDataType << endl;
 
     auto cstrIter = dictionaryConstructorTablePtr_->cfind(pointDataType);
 
diff --git a/src/runTimePostProcessing/pointData.H b/src/runTimePostProcessing/pointData.H
index d56240700da4bf034d77a1ddf7af62552c2d1f89..e4b9cbcfcccf62e83221d6b65aadccb383754296 100644
--- a/src/runTimePostProcessing/pointData.H
+++ b/src/runTimePostProcessing/pointData.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -27,6 +27,14 @@ Class
 Description
     Visualisation of point data
 
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        representation| sphere/vector                       | yes |
+        pointColour   | Override point colour               | no  |
+        maxGlyphLength | Limit for glyph representation     | yes |
+    \endtable
+
 SourceFiles
     pointData.C
 
@@ -41,7 +49,7 @@ SourceFiles
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward declarations
+// Forward Declarations
 class vtkActor;
 class vtkPolyData;
 class vtkPolyDataMapper;
@@ -63,20 +71,22 @@ class pointData
 {
 public:
 
-    // Public enumerations
+    // Public Enumerations
 
+        //- Point representation types
         enum representationType
         {
-            rtSphere,               //!< Sphere
-            rtVector                //!< Vector
+            rtSphere,           //!< "sphere"
+            rtVector            //!< "vector"
         };
 
+        //- Names for point representation types
         static const Enum<representationType> representationTypeNames;
 
 
 protected:
 
-    // Protected data
+    // Protected Data
 
         //- Representation type
         representationType representation_;
@@ -109,7 +119,7 @@ protected:
 public:
 
     //- Run-time type information
-    TypeName("pointData");
+    TypeNameNoDebug("pointData");
 
 
     // Declare run-time constructor selection table
@@ -141,7 +151,7 @@ public:
 
     // Selectors
 
-        //- Return a reference to the selected RAS model
+        //- Return selected pointData
         static autoPtr<pointData> New
         (
             const runTimePostProcessing& parent,
diff --git a/src/runTimePostProcessing/runTimePostProcessing.C b/src/runTimePostProcessing/runTimePostProcessing.C
index 12148d850ce4770833a92a190f3702999e53a3e6..f514b39b714ab220388aad3cb8fef649ec9fa2df 100644
--- a/src/runTimePostProcessing/runTimePostProcessing.C
+++ b/src/runTimePostProcessing/runTimePostProcessing.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -32,6 +32,7 @@ License
 #include "text.H"
 #include "Time.H"
 #include "sigFpe.H"
+#include "polySurfaceFields.H"
 #include "addToRunTimeSelectionTable.H"
 
 // VTK includes
@@ -41,6 +42,16 @@ License
 #include "vtkSmartPointer.h"
 #include "vtkLight.h"
 
+#ifdef FOAM_USING_VTK_MPI
+# include "vtkMPICommunicator.h"
+# include "vtkMPIController.h"
+#endif
+#include "vtkDummyController.h"
+
+#include "vtkSynchronizedRenderWindows.h"
+#include "vtkCompositedSynchronizedRenderers.h"
+
+
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
 namespace Foam
@@ -101,6 +112,168 @@ static void cleanup(PtrList<Type>& objects)
 } // End namespace Foam
 
 
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::functionObjects::runTimePostProcessing::render
+(
+    vtkMultiProcessController* controller
+)
+{
+    // Some feedback
+    if (controller)
+    {
+        Log << name() << " render (" << controller->GetNumberOfProcesses()
+            << " processes)" << endl;
+    }
+    else
+    {
+        Log << name() << " render" << endl;
+    }
+
+
+    // Normal rendering elements
+    vtkSmartPointer<vtkRenderer> renderer;
+    vtkSmartPointer<vtkRenderWindow> renderWindow;
+
+    // Multi-process synchronization
+    vtkSmartPointer<vtkSynchronizedRenderWindows> syncWindows;
+    vtkSmartPointer<vtkCompositedSynchronizedRenderers> syncRenderers;
+
+
+    // Disable any floating point trapping
+    // (some low-level rendering functionality does not like it)
+
+    sigFpe::ignore sigFpeHandling; //<- disable in local scope
+
+
+    // Initialise render window
+    if (controller || Pstream::master())
+    {
+        renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
+        renderer = vtkSmartPointer<vtkRenderer>::New();
+
+        renderWindow->OffScreenRenderingOn();
+        renderWindow->SetSize(output_.width_, output_.height_);
+
+        // Legacy rendering - was deprecated for 8.1.0
+        #if (VTK_MAJOR_VERSION < 8) || \
+            ((VTK_MAJOR_VERSION == 8) && (VTK_MINOR_VERSION < 2))
+        renderWindow->SetAAFrames(10);
+        #endif
+        renderWindow->SetAlphaBitPlanes(true);
+        renderWindow->SetMultiSamples(0);
+        // renderWindow->PolygonSmoothingOn();
+
+        renderWindow->AddRenderer(renderer);
+    }
+
+    // Synchronization
+    if (controller)
+    {
+        syncWindows =
+            vtkSmartPointer<vtkSynchronizedRenderWindows>::New();
+
+        syncRenderers =
+            vtkSmartPointer<vtkCompositedSynchronizedRenderers>::New();
+
+        syncWindows->SetRenderWindow(renderWindow);
+        syncWindows->SetParallelController(controller);
+        syncWindows->SetIdentifier(1);
+
+        // false = Call Render() manually on each process - don't use RMI
+        syncWindows->SetParallelRendering(true);
+
+        syncRenderers->SetRenderer(renderer);
+        syncRenderers->SetParallelController(controller);
+    }
+
+    // ---------------------
+
+    scene_.initialise(renderer, output_.name_);
+
+    addGeometryToScene(points_, 0, renderer);
+    addGeometryToScene(lines_, 0, renderer);
+    addGeometryToScene(surfaces_, 0, renderer);
+    addGeometryToScene(text_, 0, renderer);
+
+    while (scene_.loop(renderer))
+    {
+        const scalar position = scene_.position();
+
+        updateActors(text_, position);
+        updateActors(points_, position);
+        updateActors(lines_, position);
+        updateActors(surfaces_, position);
+    }
+
+    // Cleanup
+    cleanup(text_);
+    cleanup(points_);
+    cleanup(lines_);
+    cleanup(surfaces_);
+
+
+    // Instead of relying on the destructor, manually restore the previous
+    // SIGFPE state.
+    // This is only to avoid compiler complaints about unused variables.
+
+    sigFpeHandling.restore();
+}
+
+
+void Foam::functionObjects::runTimePostProcessing::render
+(
+    vtkMultiProcessController* controller,
+    void* processData
+)
+{
+    reinterpret_cast<runTimePostProcessing*>(processData)->render(controller);
+}
+
+
+void Foam::functionObjects::runTimePostProcessing::render()
+{
+    #ifdef FOAM_USING_VTK_MPI
+    if (parallel_)
+    {
+        // Create vtkMPIController if MPI is configured,
+        // vtkThreadedController otherwise.
+        auto ctrl = vtkSmartPointer<vtkMPIController>::New();
+        ctrl->Initialize(nullptr, nullptr, 1);
+
+        ctrl->SetSingleMethod(runTimePostProcessing::render, this);
+        ctrl->SingleMethodExecute();
+
+        ctrl->Finalize(1);
+    }
+    else
+    #endif
+    {
+        // Normally we would have a fallback controller like this:
+
+        // if (Pstream::master())
+        // {
+        //     auto ctrl = vtkSmartPointer<vtkDummyController>::New();
+        //     ctrl->Initialize(nullptr, nullptr, 1);
+        //
+        //     ctrl->SetSingleMethod(runTimePostProcessing::render, this);
+        //     ctrl->SingleMethodExecute();
+        //
+        //     ctrl->Finalize(1);
+        // }
+
+        // However, this would prevent us from doing any of our own MPI
+        // since this would only be spawned the master.
+
+        // Instead pass in nullptr for the controller and handling
+        // logic internally.
+
+        vtkDummyController* dummy = nullptr;
+        render(dummy);
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::functionObjects::runTimePostProcessing::runTimePostProcessing
@@ -111,6 +284,8 @@ Foam::functionObjects::runTimePostProcessing::runTimePostProcessing
 )
 :
     fvMeshFunctionObject(name, runTime, dict),
+    output_(),
+    parallel_(false),
     scene_(runTime, name),
     points_(),
     lines_(),
@@ -127,7 +302,20 @@ bool Foam::functionObjects::runTimePostProcessing::read(const dictionary& dict)
 {
     fvMeshFunctionObject::read(dict);
 
-    Info<< type() << " " << name() << ": reading post-processing data" << endl;
+    #ifdef FOAM_USING_VTK_MPI
+    parallel_ = (Pstream::parRun() && dict.lookupOrDefault("parallel", true));
+    #else
+    parallel_ = false;
+    #endif
+
+    Info<< type() << " " << name() << ": reading post-processing data ("
+        << (parallel_ ? "parallel" : "serial") << " rendering)" << endl;
+
+    if (dict.lookupOrDefault("debug", false))
+    {
+        runTimePostPro::geometryBase::debug = 1;
+        Info<< "    debugging on" << endl;
+    }
 
     scene_.read(dict);
 
@@ -177,68 +365,7 @@ bool Foam::functionObjects::runTimePostProcessing::execute()
 
 bool Foam::functionObjects::runTimePostProcessing::write()
 {
-    if (!Pstream::master())
-    {
-        return true;
-    }
-
-    Info<< type() << " " << name() <<  " output:" << nl
-        << "    Constructing scene" << endl;
-
-
-    // Disable any floating point trapping
-    // (some low-level rendering functionality does not like it)
-
-    sigFpe::ignore sigFpeHandling; //<- disable in local scope
-
-    // Initialise render window
-    auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
-    renderWindow->OffScreenRenderingOn();
-    renderWindow->SetSize(output_.width_, output_.height_);
-
-    // Legacy rendering - was deprecated for 8.1.0
-    #if (VTK_MAJOR_VERSION < 8) || \
-        ((VTK_MAJOR_VERSION == 8) && (VTK_MINOR_VERSION < 2))
-    renderWindow->SetAAFrames(10);
-    #endif
-    renderWindow->SetAlphaBitPlanes(true);
-    renderWindow->SetMultiSamples(0);
-//    renderWindow->PolygonSmoothingOn();
-
-    auto renderer = vtkSmartPointer<vtkRenderer>::New();
-    scene_.initialise(renderer, output_.name_);
-
-    renderWindow->AddRenderer(renderer);
-
-
-    addGeometryToScene(points_, 0, renderer);
-    addGeometryToScene(lines_, 0, renderer);
-    addGeometryToScene(surfaces_, 0, renderer);
-    addGeometryToScene(text_, 0, renderer);
-
-    while (scene_.loop(renderer))
-    {
-        const scalar position = scene_.position();
-
-        updateActors(text_, position);
-        updateActors(points_, position);
-        updateActors(lines_, position);
-        updateActors(surfaces_, position);
-    }
-
-    // Cleanup
-    cleanup(text_);
-    cleanup(points_);
-    cleanup(lines_);
-    cleanup(surfaces_);
-
-
-    // Instead of relying on the destructor, manually restore the previous
-    // SIGFPE state.
-    // This is only to avoid compiler complaints about unused variables.
-
-    sigFpeHandling.restore();
-
+    render();
     return true;
 }
 
diff --git a/src/runTimePostProcessing/runTimePostProcessing.H b/src/runTimePostProcessing/runTimePostProcessing.H
index 0e48344e82d345ef9467cc12a6040caab6b3b85a..0b13c01ab725a13b77e56db3444e51d1a44a0952 100644
--- a/src/runTimePostProcessing/runTimePostProcessing.H
+++ b/src/runTimePostProcessing/runTimePostProcessing.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -54,9 +54,54 @@ Description
     - Selection of colour maps
     .
 
-    Scene configuration is performed using standard OpenFOAM dictionaries, using
-    the main headings of: output=, camera, colours, points, lines,
-    surfaces and text.
+    Mandatory entries:
+    \verbatim
+    output
+    {
+        name        image;
+        width       800;
+        height      600;
+    }
+
+    camera
+    {
+        ...
+    }
+
+    colours
+    {
+        ...
+    }
+
+    text
+    {
+    }
+    \endverbatim
+
+    Optional entries:
+    \verbatim
+    points
+    {
+    }
+
+    lines
+    {
+        ...
+    }
+
+    surfaces
+    {
+        ...
+    }
+    \endverbatim
+
+    Dictionary controls
+    \table
+        Property    | Description                          | Required | Default
+        type        | Type-name: runTimePostProcessing     | yes |
+        debug       | Additional debug information         | no  | false
+        parallel    | Enable/disable VTK parallel          | no  | true
+    \endtable
 
 SourceFiles
     runTimePostProcessing.C
@@ -74,14 +119,20 @@ SourceFiles
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward declarations
-class vtkRenderer;
+// Forward Declarations
+class vtkMultiPieceDataSet;
+class vtkMultiProcessController;
 class vtkRenderWindow;
+class vtkRenderer;
+template<class T> class vtkSmartPointer;
 
 namespace Foam
 {
 namespace functionObjects
 {
+// Forward declarations
+class runTimePostProcessing;
+
 namespace runTimePostPro
 {
 // Forward declarations
@@ -99,11 +150,9 @@ class runTimePostProcessing
 :
     public fvMeshFunctionObject
 {
-private:
+    // Private Data
 
-    // Private data
-
-        // Output
+        //- Output information
         struct outputType
         {
             word name_;
@@ -114,6 +163,9 @@ private:
         //- Output instance
         outputType output_;
 
+        //- Parallel rendering
+        bool parallel_;
+
         //- Scene manager
         runTimePostPro::scene scene_;
 
@@ -136,6 +188,15 @@ private:
         template<class Type>
         void readObjects(const dictionary& dict, PtrList<Type>& objects) const;
 
+        //- Construct controller and dispatch to render
+        void render();
+
+        //- Static function for SetSingleMethod binding
+        static void render(vtkMultiProcessController* ctrl, void* data);
+
+        //- Render scene using given controller
+        void render(vtkMultiProcessController* ctrl);
+
 
 public:
 
@@ -160,11 +221,26 @@ public:
 
     // Member Functions
 
+        //- Parallel rendering
+        bool parallel() const
+        {
+            return parallel_;
+        }
+
+        //- May need to gather parts to render on single-processor
+        //  True when OpenFOAM is running in parallel but VTK is not.
+        bool needsCollective() const
+        {
+            return Pstream::parRun() && !parallel_;
+        }
+
+        //- Reference to the underlying OpenFOAM mesh
         const fvMesh& mesh() const
         {
             return mesh_;
         }
 
+
         //- Read the post-processing controls
         virtual bool read(const dictionary& dict);
 
diff --git a/src/runTimePostProcessing/runTimePostProcessingTemplates.C b/src/runTimePostProcessing/runTimePostProcessingTemplates.C
index 788787d5236e89da48f3bd2146e1eaa462097f1e..e408f01a891ac9c78431cd8d0401860644d37db2 100644
--- a/src/runTimePostProcessing/runTimePostProcessingTemplates.C
+++ b/src/runTimePostProcessing/runTimePostProcessingTemplates.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
diff --git a/src/runTimePostProcessing/scalarBar.C b/src/runTimePostProcessing/scalarBar.C
new file mode 100644
index 0000000000000000000000000000000000000000..cfd24195301f42a56743ec57dbba79494235d5e8
--- /dev/null
+++ b/src/runTimePostProcessing/scalarBar.C
@@ -0,0 +1,250 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "scalarBar.H"
+
+// #include "doubleVector.H"
+// #include "foamVtkTools.H"
+
+// VTK includes
+#include "vtkCoordinate.h"
+#include "vtkLookupTable.h"
+#include "vtkRenderer.h"
+#include "vtkScalarBarActor.h"
+#include "vtkSmartPointer.h"
+#include "vtkTextActor.h"
+#include "vtkTextProperty.h"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::scalarBar::scalarBar()
+{
+    clear();
+}
+
+
+void Foam::functionObjects::runTimePostPro::scalarBar::clear()
+{
+    visible_ = true;
+    vertical_ = true;
+    bold_ = true;
+    shadow_ = false;
+    italic_ = false;
+    titleHack_ = true;
+    position_ = {0.8, 0.1};
+    size_ = {0.1, 0.5};
+    title_ = "";
+    fontSize_ = 12;
+    titleSize_ = 0;   // 0 == Auto-sizing (factor 3)
+    nLabels_ = 5;
+    labelFormat_ = "%f";
+}
+
+
+void Foam::functionObjects::runTimePostPro::scalarBar::hide()
+{
+    visible_ = false;
+}
+
+
+void Foam::functionObjects::runTimePostPro::scalarBar::read
+(
+    const dictionary& dict
+)
+{
+    clear();
+
+    dict.readIfPresent("visible", visible_);
+
+    if (visible_)
+    {
+        dict.readIfPresent("vertical", vertical_);
+        dict.readIfPresent("bold", bold_);
+        dict.readIfPresent("italic", italic_);
+        dict.readIfPresent("shadow", shadow_);
+        dict.readIfPresent("titleHack", titleHack_);
+
+        if (vertical_)
+        {
+            size_ = { 0.1, 0.75 };
+        }
+        else
+        {
+            size_ = { 0.75, 0.1 };
+        }
+
+        dict.readEntry("position", position_);
+        dict.readIfPresent("size", size_);
+        dict.readEntry("title", title_);
+        dict.readIfPresent("fontSize", fontSize_);
+        dict.readIfPresent("titleSize", titleSize_);
+        dict.readIfPresent("labelFormat", labelFormat_);
+        dict.readIfPresent("numberOfLabels", nLabels_);
+    }
+}
+
+
+bool Foam::functionObjects::runTimePostPro::scalarBar::add
+(
+    const vector& textColour,
+    vtkRenderer* renderer,
+    vtkLookupTable* lut
+) const
+{
+    // Add scalar bar legend
+
+    if (!visible_ || !renderer)
+    {
+        return false;
+    }
+
+    auto sbar = vtkSmartPointer<vtkScalarBarActor>::New();
+    sbar->SetLookupTable(lut);
+    sbar->SetNumberOfLabels(nLabels_);
+    sbar->SetLabelFormat(labelFormat_.c_str());
+
+    /// const vector textColour = colours_["text"]->value(position);
+
+    // Work-around to supply our own scalarbar title
+    // - Default scalar bar title text is scales by the scalar bar box
+    //   dimensions so if the title is a long string, the text is shrunk to fit
+    //   Instead, suppress title and set the title using a vtkTextActor
+
+    vtkSmartPointer<vtkTextActor> titleActor;
+    vtkTextProperty* titleProp;
+
+    if (titleHack_)
+    {
+        // Place the scalar bar title ourselves
+        titleActor = vtkSmartPointer<vtkTextActor>::New();
+        titleActor->SetInput(title_.c_str());
+
+        titleProp = titleActor->GetTextProperty();
+        titleProp->SetJustificationToCentered();
+    }
+    else
+    {
+        // Use the standard scalar bar title
+        sbar->SetTitle(title_.c_str());
+        titleProp = sbar->GetTitleTextProperty();
+    }
+
+    titleProp->SetFontFamilyToArial();
+
+    // Title size was supplied by user (absolute size)
+    // or use preset factor (3) of label font size
+
+    if (titleSize_)
+    {
+        titleProp->SetFontSize(titleSize_);
+    }
+    else
+    {
+        // Auto = Factor 3 of fontSize
+        titleProp->SetFontSize(3*fontSize_);
+
+        // Or this??
+        // if (!titleHack_) titleProp->SetFontSize(fontSize_);
+    }
+
+    titleProp->SetJustificationToCentered();
+    titleProp->SetVerticalJustificationToBottom();
+    titleProp->SetBold(bold_);
+    titleProp->SetItalic(italic_);
+    titleProp->SetShadow(shadow_);
+
+    titleProp->SetColor(textColour[0], textColour[1], textColour[2]);
+
+
+    auto labProp = sbar->GetLabelTextProperty();
+
+    labProp->SetColor(textColour[0], textColour[1], textColour[2]);
+
+    labProp->SetFontSize(fontSize_);
+    labProp->ShadowOff();
+    labProp->BoldOff();
+    labProp->ItalicOff();
+
+    // Positioning
+    {
+        vtkCoordinate* coord = sbar->GetPositionCoordinate();
+
+        coord->SetCoordinateSystemToNormalizedViewport();
+        coord->SetValue(position_.first(), position_.second());
+    }
+
+    if (vertical_)
+    {
+        sbar->SetOrientationToVertical();
+        sbar->SetTextPositionToSucceedScalarBar();
+        sbar->SetWidth(size_.first());
+        sbar->SetHeight(size_.second());
+        // Standard is sbar->SetBarRatio(0.375);
+    }
+    else
+    {
+        sbar->SetOrientationToHorizontal();
+        sbar->SetTextPositionToPrecedeScalarBar();
+
+        // Adjustments since not using scalarbar title property
+        sbar->SetWidth(size_.first());
+        sbar->SetHeight(size_.second());
+        // sbar->SetBarRatio(0.5);
+        // Standard is sbar->SetBarRatio(0.375);
+        // sbar->SetTitleRatio(0.01);
+    }
+
+    if (titleActor)
+    {
+        vtkCoordinate* coord = titleActor->GetPositionCoordinate();
+
+        coord->SetCoordinateSystemToNormalizedViewport();
+
+        coord->SetValue
+        (
+            position_.first() + (0.5 * sbar->GetWidth()),
+            position_.second() + (1.01 * sbar->GetHeight())
+        );
+    }
+
+    // sbar->DrawFrameOn();
+    // sbar->DrawBackgroundOn();
+    // sbar->UseOpacityOff();
+    // sbar->VisibilityOff();
+    sbar->VisibilityOn();
+
+    renderer->AddActor(sbar);
+
+    if (titleActor)
+    {
+        renderer->AddActor2D(titleActor);
+    }
+
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/scalarBar.H b/src/runTimePostProcessing/scalarBar.H
new file mode 100644
index 0000000000000000000000000000000000000000..ce3280e274592dcd2e9f9468d8d445bb4414e66f
--- /dev/null
+++ b/src/runTimePostProcessing/scalarBar.H
@@ -0,0 +1,138 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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::functionObjects::runTimePostPro::scalarBar
+
+Description
+    Handling of scalar bar setup.
+
+    Dictionary controls - scalar-bar entries (when \c visible is true)
+    \table
+        Property    | Description                           | Required | Default
+        visible     | Display scalar bar                    | no  | true
+        title       | The title for the scalar bar          | yes |
+        position    | Viewport position (x y) of scalar bar | yes |
+        vertical    | Vertical scalar bar                   | no  | true
+        size        | Viewport size (x y) of scalar bar     | no  | auto
+        fontSize    | Label size                            | no  | 12
+        titleSize   | Title font size                       | no  | 0 == auto
+        labelFormat | Label format string (eg, "%f")        | no  | "%f"
+        numberOfLabels | Total number of labels             | no  | 5
+        bold        | Title in bold                         | no  | yes
+        italic      | Title in italic font                  | no  | no
+        shadow      | Title with shadowont                  | no  | no
+        titleHack   | Alternative placement strategy        | no  | yes
+    \endtable
+
+SourceFiles
+    scalarBar.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_runTimePostPro_scalarBar_H
+#define functionObjects_runTimePostPro_scalarBar_H
+
+#include "dictionary.H"
+#include "Tuple2.H"
+#include "vector.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Forward Declarations
+class vtkLookupTable;
+class vtkRenderer;
+
+
+namespace Foam
+{
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+
+/*---------------------------------------------------------------------------*\
+                          Class scalarBar Declaration
+\*---------------------------------------------------------------------------*/
+
+class scalarBar
+{
+    bool visible_;
+    bool vertical_;
+    bool bold_;
+    bool italic_;
+    bool shadow_;
+    bool titleHack_;
+
+    Tuple2<scalar, scalar> position_;
+
+    Tuple2<scalar, scalar> size_;
+
+    string title_;
+
+    label fontSize_;
+
+    label titleSize_;
+
+    label nLabels_;
+
+    string labelFormat_;
+
+
+public:
+
+    //- Construct with sensible defaults
+    scalarBar();
+
+    //- Reset to sensible defaults
+    void clear();
+
+    //- Make non-visible
+    void hide();
+
+    //- Read dictionary settings
+    void read(const dictionary& dict);
+
+    //- Add colour bar, when visible.
+    bool add
+    (
+        const vector& textColour,
+        vtkRenderer* renderer,
+        vtkLookupTable* lut
+    ) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace runTimePostPro
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/scene.C b/src/runTimePostProcessing/scene.C
index 918b609b192b1dc6a879435c74a8d907e3c9c8f0..edbb43c20681be3cbeb8c321e8cdbc0de8ba2624 100644
--- a/src/runTimePostProcessing/scene.C
+++ b/src/runTimePostProcessing/scene.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -135,6 +135,8 @@ void Foam::functionObjects::runTimePostPro::scene::setActorVisibility
     const bool visible
 ) const
 {
+    if (!renderer) return;
+
     vtkActorCollection *actors = renderer->GetActors();
     for (int i = 0; i < actors->GetNumberOfItems(); ++i)
     {
@@ -155,27 +157,30 @@ void Foam::functionObjects::runTimePostPro::scene::initialise
 
     outputName_ = outputName;
 
+    if (!renderer) return;
+
+
     // Set the background
-    const vector backgroundColour = colours_["background"]->value(position_);
-    renderer->SetBackground
-    (
-        backgroundColour.x(),
-        backgroundColour.y(),
-        backgroundColour.z()
-    );
+    const vector bgColour = colours_["background"]->value(position_);
+
+    renderer->SetBackground(bgColour.x(), bgColour.y(), bgColour.z());
 
     // Apply gradient background if "background2" defined
     if (colours_.found("background2"))
     {
+        const vector bg2Colour = colours_["background2"]->value(position_);
+
         renderer->GradientBackgroundOn();
-        vector backgroundColour2 = colours_["background2"]->value(position_);
+        renderer->SetBackground2(bg2Colour.x(), bg2Colour.y(), bg2Colour.z());
+    }
+    else if (Pstream::parRun())
+    {
+        // Oddly enough we seem a gradient background for parallel rendering,
+        // otherwise the colours look quite funny.
+        // Doesn't seem to matter if we use SetBackground2() though
 
-        renderer->SetBackground2
-        (
-            backgroundColour2.x(),
-            backgroundColour2.y(),
-            backgroundColour2.z()
-        );
+        renderer->GradientBackgroundOn();
+        renderer->SetBackground2(bgColour.x(), bgColour.y(), bgColour.z());
     }
 
     // Depth peeling
@@ -222,6 +227,8 @@ void Foam::functionObjects::runTimePostPro::scene::setCamera
     vtkRenderer* renderer
 ) const
 {
+    if (!renderer) return;
+
     vtkCamera* camera = renderer->GetActiveCamera();
 
     if (parallelProjection_)
@@ -353,14 +360,18 @@ bool Foam::functionObjects::runTimePostPro::scene::loop(vtkRenderer* renderer)
         return true;
     }
 
-    // Ensure that all objects can be seen without clipping
-    // Note: can only be done after all objects have been added!
-    renderer->ResetCameraClippingRange();
+    if (renderer)
+    {
+
+        // Ensure that all objects can be seen without clipping
+        // Note: can only be done after all objects have been added!
+        renderer->ResetCameraClippingRange();
 
-    // Save image from last iteration
-    saveImage(renderer->GetRenderWindow());
+        // Save image from last iteration
+        saveImage(renderer->GetRenderWindow());
+    }
 
-    currentFrameI_++;
+    ++currentFrameI_;
 
     position_ = startPosition_ + currentFrameI_*dPosition_;
 
@@ -388,16 +399,15 @@ void Foam::functionObjects::runTimePostPro::scene::saveImage
 
     const Time& runTime = obr_.time();
 
-    const fileName prefix
+    const fileName fName
     (
         runTime.globalPath()
       / functionObject::outputPrefix
       / name_
       / runTime.timeName()
+      / outputName_ + '.' + frameIndexStr() + ".png"
     );
 
-    mkDir(prefix);
-
     renderWindow->Render();
 
     // Set up off-screen rendering
@@ -413,15 +423,21 @@ void Foam::functionObjects::runTimePostPro::scene::saveImage
     windowToImageFilter->Update();
 
     // Save the image
-    auto writer = vtkSmartPointer<vtkPNGWriter>::New();
-    fileName fName(prefix/outputName_ + '.' + frameIndexStr() + ".png");
-    writer->SetFileName(fName.c_str());
-    writer->SetInputConnection(windowToImageFilter->GetOutputPort());
 
-    Info<< "    Generating image: " << fName << endl;
+    if (Pstream::master())
+    {
+        mkDir(fName.path());
+
+        auto writer = vtkSmartPointer<vtkPNGWriter>::New();
+        writer->SetFileName(fName.c_str());
+        writer->SetInputConnection(windowToImageFilter->GetOutputPort());
+
+        Info<< "    Generating image: " << runTime.relativePath(fName) << endl;
 
-    writer->Write();
+        writer->Write();
+    }
 }
 
 
+
 // ************************************************************************* //
diff --git a/src/runTimePostProcessing/scene.H b/src/runTimePostProcessing/scene.H
index a2b60ec076f58fb32004ce9227d0b316f3019fd9..57c4f2b056e2f2fc8061869e2bfa6be3e9c13f35 100644
--- a/src/runTimePostProcessing/scene.H
+++ b/src/runTimePostProcessing/scene.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -46,6 +46,10 @@ Usage
         viewAngle   20;
         zoom        1.1;
     }
+
+    colours
+    {
+    }
     \endverbatim
 
 SourceFiles
diff --git a/src/runTimePostProcessing/surface.C b/src/runTimePostProcessing/surface.C
index 15d4bf511c8ec99de645320dca3e5ac688e69f09..62f11d0e605828264677db842a86193e35d14561 100644
--- a/src/runTimePostProcessing/surface.C
+++ b/src/runTimePostProcessing/surface.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2016 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -27,9 +27,15 @@ License
 #include "surface.H"
 #include "runTimePostProcessing.H"
 
+#include "foamVtkTools.H"
+#include "polySurfaceFields.H"
+#include "polySurfacePointFields.H"
+
 // VTK includes
 #include "vtkActor.h"
+#include "vtkCompositeDataGeometryFilter.h"
 #include "vtkFeatureEdges.h"
+#include "vtkMultiPieceDataSet.h"
 #include "vtkPolyData.h"
 #include "vtkPolyDataMapper.h"
 #include "vtkProperty.h"
@@ -44,7 +50,7 @@ namespace functionObjects
 {
 namespace runTimePostPro
 {
-    defineTypeNameAndDebug(surface, 0);
+    defineTypeName(surface);
     defineRunTimeSelectionTable(surface, dictionary);
 }
 }
@@ -58,13 +64,30 @@ const Foam::Enum
 Foam::functionObjects::runTimePostPro::surface::representationTypeNames
 ({
     { representationType::rtNone, "none" },
+    { representationType::rtGlyph, "glyph" },
     { representationType::rtWireframe, "wireframe" },
     { representationType::rtSurface, "surface" },
     { representationType::rtSurfaceWithEdges, "surfaceWithEdges" },
-    { representationType::rtGlyph, "glyph" },
 });
 
 
+// * * * * * * * * * * * * * * * Specializations * * * * * * * * * * * * * * //
+
+// These need to shift elsewhere
+
+vtkCellData* Foam::vtk::Tools::GetCellData(vtkDataSet* dataset)
+{
+    if (dataset) return dataset->GetCellData();
+    return nullptr;
+}
+
+vtkPointData* Foam::vtk::Tools::GetPointData(vtkDataSet* dataset)
+{
+    if (dataset) return dataset->GetPointData();
+    return nullptr;
+}
+
+
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
 void Foam::functionObjects::runTimePostPro::surface::setRepresentation
@@ -83,7 +106,7 @@ void Foam::functionObjects::runTimePostPro::surface::setRepresentation
         }
         case rtWireframe:
         {
-            // note: colour is set using general SetColor, not SetEdgeColor
+            // Note: colour is set using general SetColor, not SetEdgeColor
             actor->GetProperty()->SetRepresentationToWireframe();
             break;
         }
@@ -106,21 +129,19 @@ void Foam::functionObjects::runTimePostPro::surface::setRepresentation
 void Foam::functionObjects::runTimePostPro::surface::addFeatureEdges
 (
     vtkRenderer* renderer,
-    vtkPolyData* data
+    vtkFeatureEdges* featureEdges
 ) const
 {
-    if (!featureEdges_)
+    if (!featureEdges)
     {
         return;
     }
 
-    auto featureEdges = vtkSmartPointer<vtkFeatureEdges>::New();
-    featureEdges->SetInputData(data);
     featureEdges->BoundaryEdgesOn();
     featureEdges->FeatureEdgesOn();
     featureEdges->ManifoldEdgesOff();
     featureEdges->NonManifoldEdgesOff();
-//    featureEdges->SetFeatureAngle(60);
+    /// featureEdges->SetFeatureAngle(60);
     featureEdges->ColoringOff();
     featureEdges->Update();
 
@@ -137,6 +158,38 @@ void Foam::functionObjects::runTimePostPro::surface::addFeatureEdges
 }
 
 
+void Foam::functionObjects::runTimePostPro::surface::addFeatureEdges
+(
+    vtkRenderer* renderer,
+    vtkPolyData* data
+) const
+{
+    if (featureEdges_)
+    {
+        auto featureEdges = vtkSmartPointer<vtkFeatureEdges>::New();
+        featureEdges->SetInputData(data);
+
+        addFeatureEdges(renderer, featureEdges);
+    }
+}
+
+
+void Foam::functionObjects::runTimePostPro::surface::addFeatureEdges
+(
+    vtkRenderer* renderer,
+    vtkCompositeDataGeometryFilter* input
+) const
+{
+    if (featureEdges_)
+    {
+        auto featureEdges = vtkSmartPointer<vtkFeatureEdges>::New();
+        featureEdges->SetInputConnection(input->GetOutputPort());
+
+        addFeatureEdges(renderer, featureEdges);
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::functionObjects::runTimePostPro::surface::surface
@@ -151,12 +204,12 @@ Foam::functionObjects::runTimePostPro::surface::surface
     (
         representationTypeNames.get("representation", dict)
     ),
-    featureEdges_(false),
+    featureEdges_(dict.lookupOrDefault("featureEdges", false)),
     surfaceColour_(nullptr),
     edgeColour_(nullptr),
     surfaceActor_(),
     edgeActor_(),
-    maxGlyphLength_(0.0)
+    maxGlyphLength_(0)
 {
     surfaceActor_ = vtkSmartPointer<vtkActor>::New();
     edgeActor_ = vtkSmartPointer<vtkActor>::New();
@@ -183,10 +236,6 @@ Foam::functionObjects::runTimePostPro::surface::surface
     {
         dict.readEntry("maxGlyphLength", maxGlyphLength_);
     }
-    else
-    {
-        dict.readEntry("featureEdges", featureEdges_);
-    }
 }
 
 
@@ -201,10 +250,7 @@ Foam::functionObjects::runTimePostPro::surface::New
     const word& surfaceType
 )
 {
-    if (debug)
-    {
-        Info<< "Selecting surface " << surfaceType << endl;
-    }
+    DebugInfo << "Selecting surface " << surfaceType << endl;
 
     auto cstrIter = dictionaryConstructorTablePtr_->cfind(surfaceType);
 
@@ -240,22 +286,14 @@ void Foam::functionObjects::runTimePostPro::surface::updateActors
         return;
     }
 
-    edgeActor_->GetProperty()->SetLineWidth(2);
-    edgeActor_->GetProperty()->SetOpacity(opacity(position));
+    vtkProperty* edgeProp = edgeActor_->GetProperty();
 
-    const vector colour = edgeColour_->value(position);
-    edgeActor_->GetProperty()->SetColor
-    (
-        colour[0],
-        colour[1],
-        colour[2]
-    );
-    edgeActor_->GetProperty()->SetEdgeColor
-    (
-        colour[0],
-        colour[1],
-        colour[2]
-    );
+    edgeProp->SetLineWidth(2);
+    edgeProp->SetOpacity(opacity(position));
+
+    const vector ec = edgeColour_->value(position);
+    edgeProp->SetColor(ec[0], ec[1], ec[2]);
+    edgeProp->SetEdgeColor(ec[0], ec[1], ec[2]);
 }
 
 
diff --git a/src/runTimePostProcessing/surface.H b/src/runTimePostProcessing/surface.H
index 3b70826da6fbca13392121319f0661391467f1f0..0725eb7e32d06fa8d42d644c67ba396ec70ead08 100644
--- a/src/runTimePostProcessing/surface.H
+++ b/src/runTimePostProcessing/surface.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,10 +25,23 @@ Class
     Foam::functionObjects::runTimePostPro::surface
 
 Description
-    Visualisation of surface data
+    Visualisation of surface data with additional routines for handling
+    parallel distributed data.
+
+    Dictionary controls
+    \table
+        Property    | Description                           | Required | Default
+        representation| none/glyph/wireframe/surface/surfaceWithEdges | yes |
+        surfaceColour | Override surface colour             | no  |
+        edgeColour    | Override edge colour                | no  |
+        featureEdges  | Display surface feature edges       | no  | false
+        maxGlyphLength | Limit for glyph representation     | yes | 0
+    \endtable
 
 SourceFiles
     surface.C
+    surfaceGather.C
+    surfaceTemplates.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -36,17 +49,80 @@ SourceFiles
 #define functionObjects_runTimePostPro_surface_H
 
 #include "geometryBase.H"
+#include "DimensionedField.H"
 #include "Enum.H"
 #include "runTimeSelectionTables.H"
+
 #include "vtkSmartPointer.h"
+#include "vtkMultiPieceDataSet.h"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward declarations
+// Forward Declarations
 class vtkActor;
 class vtkRenderer;
+class vtkCellData;
+class vtkCompositeDataGeometryFilter;
+class vtkFeatureEdges;
+class vtkPointData;
 class vtkPolyData;
 
+namespace Foam
+{
+// Forward Declarations
+class polySurface;
+class polySurfaceGeoMesh;
+class polySurfacePointGeoMesh;
+}
+
+
+// These need to shift elsewhere
+
+namespace Foam
+{
+namespace vtk
+{
+namespace Tools
+{
+
+//- Functional call with null-pointer check
+vtkCellData* GetCellData(vtkDataSet* dataset);
+
+//- Functional call with null-pointer check
+vtkPointData* GetPointData(vtkDataSet* dataset);
+
+
+//- Default field access is vtkCellData
+template<class Type>
+struct FieldAccess
+{
+    vtkCellData* operator()(vtkDataSet* dataset) const
+    {
+        return Tools::GetCellData(dataset);
+    }
+};
+
+
+// Specializations on OpenFOAM type
+
+//- PointAccess for point fields (on polySurfacePointGeoMesh)
+template<>
+struct FieldAccess<::Foam::polySurfacePointGeoMesh>
+{
+    vtkPointData* operator()(vtkDataSet* dataset) const
+    {
+        return Tools::GetPointData(dataset);
+    }
+};
+
+} // End namespace Tools
+} // End namespace vtk
+} // End namespace Foam
+
+
+// More
+
+
 namespace Foam
 {
 namespace functionObjects
@@ -66,21 +142,23 @@ public:
 
     // Public enumerations
 
+        //- Surface representation types
         enum representationType
         {
-            rtNone,
-            rtWireframe,
-            rtSurface,
-            rtSurfaceWithEdges,
-            rtGlyph
+            rtNone,             //!< "none"
+            rtGlyph,            //!< "glyph"
+            rtWireframe,        //!< "wireframe"
+            rtSurface,          //!< "surface"
+            rtSurfaceWithEdges  //!< "surfaceWithEdges"
         };
 
+        //- Names for surface representation types
         static const Enum<representationType> representationTypeNames;
 
 
 protected:
 
-    // Protected data
+    // Protected Data
 
         //- Representation type
         representationType representation_;
@@ -109,6 +187,13 @@ protected:
         //- Set the representation
         void setRepresentation(vtkActor* actor) const;
 
+        //- Add feature edges to scene
+        void addFeatureEdges
+        (
+            vtkRenderer* renderer,
+            vtkFeatureEdges* featureEdges
+        ) const;
+
         //- Add feature edges to scene
         void addFeatureEdges
         (
@@ -116,6 +201,110 @@ protected:
             vtkPolyData* data
         ) const;
 
+        //- Add feature edges to scene
+        void addFeatureEdges
+        (
+            vtkRenderer* renderer,
+            vtkCompositeDataGeometryFilter* input
+        ) const;
+
+
+        //- Gather and convert polySurface to multi-piece dataset with
+        //- vtkPolyData for the leaves.
+        //  If VTK is also running in parallel, each surface is left
+        //  as a processor-local piece. Otherwise all processor-local
+        //  surfaces are gathered onto the master in their correponding
+        //  slots.
+        vtkSmartPointer<vtkMultiPieceDataSet>
+        gatherSurfacePieces(const polySurface* surf) const;
+
+        //- Gather and convert polySurface to multi-piece dataset with
+        //- vtkPolyData for the leaves.
+        //  If VTK is also running in parallel, each surface is left
+        //  as a processor-local piece. Otherwise all processor-local
+        //  surfaces are gathered onto the master in their correponding
+        //  slots.
+        vtkSmartPointer<vtkMultiPieceDataSet>
+        gatherFaceCentres(const polySurface* surf) const;
+
+
+    // Adding Fields - single-piece
+
+        //- Add field of Type to piece as VTK field data in GeoMeshType slot.
+        //  GeoMeshType distinguishes between vtkCellData and vtkPointData
+        template<class Type, class GeoMeshType>
+        bool addField
+        (
+            vtkDataSet* piece,       //!< The VTK piece (null protected)
+            const Field<Type>& fld,  //!< The field values to add
+            const word& fieldName    //!< The field name to use
+        ) const;
+
+        //- Attempt cast of regIOobject to DimensionedField\<Type\> and
+        //- add to piece as VTK field data in GeoMeshType slot.
+        //  GeoMeshType distinguishes between vtkCellData and vtkPointData
+        template<class Type, class GeoMeshType>
+        bool addDimField
+        (
+            vtkDataSet* piece,       //!< The VTK piece (null protected)
+            const regIOobject* ioptr, //!< The field values to add
+            const word& fieldName    //!< The field name to use
+        ) const;
+
+        //- Attempt cast of regIOobject to standard DimensionedField types
+        //- and add to piece when possible
+        template<class GeoMeshType>
+        bool addDimField
+        (
+            vtkDataSet* piece,       //!< The VTK piece (null protected)
+            const regIOobject* ioptr, //!< The field values to add
+            const word& fieldName    //!< The field name to use
+        ) const;
+
+
+    // Adding Fields - multi-piece
+
+        //- Add DimensionedField of Type to multi-piece as VTK field data in
+        //- GeoMeshType slot (CELL | POINT).
+        template<class Type, class GeoMeshType>
+        bool addDimField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const DimensionedField<Type, GeoMeshType>* fldptr,
+            const word& fieldName
+        ) const;
+
+        //- Attempt cast of regIOobject to DimensionedField\<Type\> and
+        //- add in multi-piece as VTK field data in
+        //- GeoMeshType slot (CELL | POINT).
+        template<class Type, class GeoMeshType>
+        bool addDimField
+        (
+            vtkMultiPieceDataSet* multiPiece, //!< The VTK pieces
+            const regIOobject* ioptr, //!< The field values to add
+            const word& fieldName    //!< The field name to use
+        ) const;
+
+        //- Attempt cast of regIOobject to standard DimensionedField types
+        //- and add when possible in GeoMeshType slot (CELL | POINT).
+        template<class GeoMeshType>
+        bool addDimField
+        (
+            vtkMultiPieceDataSet* multiPiece, //!< The VTK pieces
+            const regIOobject* ioptr, //!< The field values to add
+            const word& fieldName    //!< The field name to use
+        ) const;
+
+        //- Add using regIOobject information obtained from surface
+        template<class GeoMeshType>
+        bool addDimField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const polySurface* surf,
+            const word& fieldName
+        ) const;
+
+
         //- No copy construct
         surface(const surface&) = delete;
 
@@ -126,7 +315,7 @@ protected:
 public:
 
     //- Run-time type information
-    TypeName("surface");
+    TypeNameNoDebug("surface");
 
 
     // Declare run-time constructor selection table
@@ -158,7 +347,7 @@ public:
 
     // Selectors
 
-        //- Return a reference to the selected RAS model
+        //- Return selected surface
         static autoPtr<surface> New
         (
             const runTimePostProcessing& parent,
@@ -187,6 +376,12 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+#ifdef NoRepository
+    #include "surfaceTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
 #endif
 
 // ************************************************************************* //
diff --git a/src/runTimePostProcessing/surfaceGather.C b/src/runTimePostProcessing/surfaceGather.C
new file mode 100644
index 0000000000000000000000000000000000000000..218e3e13f3c257aeedd6ba9154f0a56a0f8248b5
--- /dev/null
+++ b/src/runTimePostProcessing/surfaceGather.C
@@ -0,0 +1,222 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "surface.H"
+#include "runTimePostProcessing.H"
+
+#include "foamVtkTools.H"
+#include "polySurface.H"
+
+// VTK includes
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPolyData.h"
+#include "vtkSmartPointer.h"
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+vtkSmartPointer<vtkMultiPieceDataSet>
+Foam::functionObjects::runTimePostPro::surface::gatherSurfacePieces
+(
+    const polySurface* surf
+) const
+{
+    auto multiPiece = vtkSmartPointer<vtkMultiPieceDataSet>::New();
+    multiPiece->SetNumberOfPieces(Pstream::nProcs());
+
+    if (!needsCollective())
+    {
+        // Simple case (serial-serial, parallel-parallel)
+
+        if (surf)
+        {
+            multiPiece->SetPiece
+            (
+                Pstream::myProcNo(),
+                Foam::vtk::Tools::Patch::mesh(*surf)
+            );
+        }
+    }
+    else if (Pstream::master())
+    {
+        // Gather pieces on master
+
+        if (surf)
+        {
+            // Add myself
+
+            multiPiece->SetPiece
+            (
+                Pstream::myProcNo(),
+                Foam::vtk::Tools::Patch::mesh(*surf)
+            );
+        }
+
+        // Receive surfaces
+        pointField points;
+        faceList faces;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            points.clear();
+            faces.clear();
+
+            fromSlave >> points >> faces;
+
+            if (points.size())
+            {
+                multiPiece->SetPiece
+                (
+                    slave,
+                    Foam::vtk::Tools::Patch::mesh(points, faces)
+                );
+            }
+        }
+    }
+    else
+    {
+        // Slave - send surfaces
+
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        if (surf)
+        {
+            toMaster
+                << surf->points() << surf->faces();
+        }
+        else
+        {
+            toMaster
+                << pointField() << faceList();
+        }
+    }
+
+    return multiPiece;
+}
+
+
+vtkSmartPointer<vtkMultiPieceDataSet>
+Foam::functionObjects::runTimePostPro::surface::gatherFaceCentres
+(
+    const polySurface* surf
+) const
+{
+    auto multiPiece = vtkSmartPointer<vtkMultiPieceDataSet>::New();
+    multiPiece->SetNumberOfPieces(Pstream::nProcs());
+
+    if (!needsCollective())
+    {
+        // Simple case
+
+        if (surf)
+        {
+            auto dataset = vtkSmartPointer<vtkPolyData>::New();
+
+            auto geom = vtkSmartPointer<vtkPolyData>::New();
+
+            geom->SetPoints(Foam::vtk::Tools::Patch::faceCentres(*surf));
+            geom->SetVerts(Foam::vtk::Tools::identityVertices(surf->nFaces()));
+
+            multiPiece->SetPiece(Pstream::myProcNo(), geom);
+        }
+    }
+    else if (Pstream::master())
+    {
+        // Gather pieces (face centres) on master
+
+        if (surf)
+        {
+            // Add myself
+
+            auto geom = vtkSmartPointer<vtkPolyData>::New();
+
+            geom->SetPoints(Foam::vtk::Tools::Patch::faceCentres(*surf));
+            geom->SetVerts(Foam::vtk::Tools::identityVertices(surf->nFaces()));
+
+            multiPiece->SetPiece(Pstream::myProcNo(), geom);
+        }
+
+        // Receive points
+        pointField points;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            points.clear();
+
+            fromSlave >> points;
+
+            if (points.size())
+            {
+                multiPiece->SetPiece
+                (
+                    slave,
+                    Foam::vtk::Tools::Vertices(points)
+                );
+            }
+        }
+    }
+    else
+    {
+        // Slave - send face centres
+
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        if (surf)
+        {
+            toMaster << surf->faceCentres();
+        }
+        else
+        {
+            toMaster << pointField();
+        }
+    }
+
+    return multiPiece;
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/surfaceTemplates.C b/src/runTimePostProcessing/surfaceTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..eaf1498e8cbd4f858529782c85d6a32d66e344c2
--- /dev/null
+++ b/src/runTimePostProcessing/surfaceTemplates.C
@@ -0,0 +1,289 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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 "foamVtkTools.H"
+#include "polySurfaceFields.H"
+#include "polySurfacePointFields.H"
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+template<class Type, class GeoMeshType>
+bool Foam::functionObjects::runTimePostPro::surface::addField
+(
+    vtkDataSet* piece,
+    const Field<Type>& fld,
+    const word& fieldName
+) const
+{
+    if (!piece) return false;
+
+    auto vtkfield = Foam::vtk::Tools::convertFieldToVTK<Type>(fieldName, fld);
+
+    if (piece->GetNumberOfCells() == piece->GetNumberOfPoints())
+    {
+        // Only has verts
+        piece->GetPointData()->AddArray(vtkfield);
+    }
+    else
+    {
+        Foam::vtk::Tools::FieldAccess<GeoMeshType>()(piece)->AddArray(vtkfield);
+    }
+
+    return true;
+}
+
+
+template<class Type, class GeoMeshType>
+bool Foam::functionObjects::runTimePostPro::surface::addDimField
+(
+    vtkDataSet* piece,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    const auto* fldptr =
+        dynamic_cast<const DimensionedField<Type, GeoMeshType>*>(ioptr);
+
+    if (fldptr)
+    {
+        return addField<Type, GeoMeshType>
+        (
+            piece,
+            fldptr->field(),
+            fieldName
+        );
+    }
+
+    return false;
+}
+
+
+template<class GeoMeshType>
+bool Foam::functionObjects::runTimePostPro::surface::addDimField
+(
+    vtkDataSet* piece,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    return (piece && ioptr) &&
+    (
+        addDimField<scalar, GeoMeshType>
+        (
+            piece, ioptr, fieldName
+        )
+     || addDimField<vector, GeoMeshType>
+        (
+            piece, ioptr, fieldName
+        )
+     || addDimField<sphericalTensor, GeoMeshType>
+        (
+            piece, ioptr, fieldName
+        )
+     || addDimField<symmTensor, GeoMeshType>
+        (
+            piece, ioptr, fieldName
+        )
+     || addDimField<tensor, GeoMeshType>
+        (
+            piece, ioptr, fieldName
+        )
+    );
+}
+
+
+template<class Type, class GeoMeshType>
+bool Foam::functionObjects::runTimePostPro::surface::addDimField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const DimensionedField<Type, GeoMeshType>* fldptr,
+    const word& fieldName
+) const
+{
+    if (!multiPiece)
+    {
+        return false;
+    }
+
+    if (!needsCollective())
+    {
+        // Simple case (serial-serial, parallel-parallel)
+
+        return fldptr &&
+            addField<Type, GeoMeshType>
+            (
+                multiPiece->GetPiece(Pstream::myProcNo()),
+                fldptr->field(),
+                fieldName
+            );
+    }
+
+
+    // Gather fields
+    const bool ok = returnReduce((fldptr != nullptr), orOp<bool>());
+
+    if (!ok)
+    {
+        return false;
+    }
+
+    if (Pstream::master())
+    {
+        if (fldptr)
+        {
+            // My field data
+            addField<Type, GeoMeshType>
+            (
+                multiPiece->GetPiece(Pstream::myProcNo()),
+                fldptr->field(),
+                fieldName
+            );
+        }
+
+        // Receive field data
+        Field<Type> recv;
+
+        for
+        (
+            int slave=Pstream::firstSlave();
+            slave<=Pstream::lastSlave();
+            ++slave
+        )
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            recv.clear();
+
+            fromSlave
+                >> recv;
+
+            if (recv.size())
+            {
+                addField<Type, GeoMeshType>
+                (
+                    multiPiece->GetPiece(slave),
+                    recv,
+                    fieldName
+                );
+            }
+        }
+    }
+    else
+    {
+        // Slave - send field data
+
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        if (fldptr)
+        {
+            toMaster
+                << fldptr->field();
+        }
+        else
+        {
+            toMaster
+                << List<Type>();
+        }
+    }
+
+    return ok;
+}
+
+
+template<class Type, class GeoMeshType>
+bool Foam::functionObjects::runTimePostPro::surface::addDimField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    return addDimField<Type, GeoMeshType>
+    (
+        multiPiece,
+        dynamic_cast<const DimensionedField<Type, GeoMeshType>*>(ioptr),
+        fieldName
+    );
+}
+
+
+template<class GeoMeshType>
+bool Foam::functionObjects::runTimePostPro::surface::addDimField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    return (multiPiece) &&
+    (
+        addDimField<scalar, GeoMeshType>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addDimField<vector, GeoMeshType>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addDimField<sphericalTensor, GeoMeshType>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addDimField<symmTensor, GeoMeshType>
+        (
+            multiPiece, ioptr, fieldName
+        )
+     || addDimField<tensor, GeoMeshType>
+        (
+            multiPiece, ioptr, fieldName
+        )
+    );
+}
+
+
+template<class GeoMeshType>
+bool Foam::functionObjects::runTimePostPro::surface::addDimField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const polySurface* surf,
+    const word& fieldName
+) const
+{
+    const regIOobject* ioptr =
+    (
+        surf
+      ? surf->findFieldObject<GeoMeshType>(fieldName)
+      : nullptr
+    );
+
+    return addDimField<GeoMeshType>(multiPiece, ioptr, fieldName);
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/text.C b/src/runTimePostProcessing/text.C
index 80dcf6f611b705b896136899751b03c5f96ddb6e..688e77486b207ac8eca21cf9b7d5f581c4ae5599 100644
--- a/src/runTimePostProcessing/text.C
+++ b/src/runTimePostProcessing/text.C
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -25,10 +25,12 @@ License
 
 // OpenFOAM includes
 #include "text.H"
+#include "stringOps.H"
 #include "fvMesh.H"
 #include "runTimePostProcessing.H"
 
 // VTK includes
+#include "vtkCoordinate.h"
 #include "vtkRenderer.h"
 #include "vtkSmartPointer.h"
 #include "vtkTextActor.h"
@@ -60,7 +62,7 @@ Foam::functionObjects::runTimePostPro::text::text
 :
     geometryBase(parent, dict, colours),
     string_(dict.get<string>("string")),
-    position_(),
+    positions_(),
     size_(dict.get<scalar>("size")),
     colour_(nullptr),
     halign_
@@ -72,7 +74,20 @@ Foam::functionObjects::runTimePostPro::text::text
     shadow_(dict.lookupOrDefault("shadow", false)),
     timeStamp_(dict.lookupOrDefault("timeStamp", false))
 {
-    dict.readEntry("position", position_);
+    if (!dict.readIfPresent("positions", positions_))
+    {
+        positions_.resize(1);
+        dict.readEntry("position", positions_.first());
+    }
+
+    // Additional safety
+    if (positions_.empty())
+    {
+        positions_.resize(1);
+        positions_.first() = {0, 0};
+    }
+
+    stringOps::inplaceExpand(string_, dict, true, true);
 
     if (dict.found("colour"))
     {
@@ -99,12 +114,13 @@ void Foam::functionObjects::runTimePostPro::text::addGeometryToScene
     vtkRenderer* renderer
 )
 {
-    if (!visible_)
+    if (!visible_ || !renderer || !Pstream::master())
     {
+        // Add text on master only!
         return;
     }
 
-    auto actor = vtkSmartPointer<vtkTextActor>::New();
+    DebugInfo << "    Add text: " << string_ << nl;
 
     // Concatenate string with timeStamp if true
     string str = string_;
@@ -112,31 +128,40 @@ void Foam::functionObjects::runTimePostPro::text::addGeometryToScene
     {
         str += " " + geometryBase::parent_.mesh().time().timeName();
     }
-    actor->SetInput(str.c_str());
 
-    vtkTextProperty* prop = actor->GetTextProperty();
+    const vector textColour = colour_->value(position);
 
-    prop->SetFontFamilyToArial();
-    prop->SetFontSize(size_);
-    prop->SetJustification(int(halign_));
-    prop->SetVerticalJustificationToBottom();
-    prop->SetBold(bold_);
-    prop->SetItalic(italic_);
-    prop->SetShadow(shadow_);
+    const scalar textOpacity = opacity(position);
 
-    const vector colour = colour_->value(position);
+    for (const auto& textPosition : positions_)
+    {
+        auto actor = vtkSmartPointer<vtkTextActor>::New();
 
-    prop->SetColor(colour[0], colour[1], colour[2]);
-    prop->SetOpacity(opacity(position));
+        actor->SetInput(str.c_str());
 
-    actor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
-    actor->GetPositionCoordinate()->SetValue
-    (
-        position_.first(),
-        position_.second()
-    );
+        vtkTextProperty* prop = actor->GetTextProperty();
+
+        prop->SetFontFamilyToArial();
+        prop->SetFontSize(size_);
+        prop->SetJustification(int(halign_));
+        prop->SetVerticalJustificationToBottom();
+        prop->SetBold(bold_);
+        prop->SetItalic(italic_);
+        prop->SetShadow(shadow_);
+
+        prop->SetColor(textColour[0], textColour[1], textColour[2]);
+        prop->SetOpacity(textOpacity);
 
-    renderer->AddActor2D(actor);
+        // Positioning
+        {
+            vtkCoordinate* coord = actor->GetPositionCoordinate();
+
+            coord->SetCoordinateSystemToNormalizedViewport();
+            coord->SetValue(textPosition.first(), textPosition.second());
+        }
+
+        renderer->AddActor2D(actor);
+    }
 }
 
 
diff --git a/src/runTimePostProcessing/text.H b/src/runTimePostProcessing/text.H
index 6434cb107d4047a07fb273dca73ad9e893a7a347..f298d4cff59518b0a646b18f1bba9cdef64d77ef 100644
--- a/src/runTimePostProcessing/text.H
+++ b/src/runTimePostProcessing/text.H
@@ -2,7 +2,7 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2015-2018 OpenCFD Ltd.
+    \\  /    A nd           | Copyright (C) 2015-2019 OpenCFD Ltd.
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
 License
@@ -39,7 +39,6 @@ Description
         // Optional entry
 
         shadow      false;
-        visible     yes;
 
         // Optionally override default colour
         // colour    (0 1 1);
@@ -54,6 +53,7 @@ Description
         Property    | Description                          | Required | Default
         string      | Text to display                      | yes |
         position    | The (x y) viewport position          | yes |
+        positions   | Multiple (x y) viewport positions    | no  |
         size        | The font size in points              | yes |
         halign      | Text justification (left/centre/ right) | no | left
         bold        | Use bold font                        | yes |
@@ -66,10 +66,14 @@ Description
     Inherited controls
     \table
         Property    | Description                          | Required | Default
-        visible     | Display the object                   | yes |
+        visible     | Display the object                   | no  | true
         opacity     | Object opacity                       | no  | 1.0
     \endtable
 
+Note
+    The string text is expanded on input using stringOps::inplaceExpand()
+    to expand dictionary and environment variable entries.
+
 SourceFiles
     text.C
 
@@ -83,7 +87,7 @@ SourceFiles
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-// Forward declarations
+// Forward Declarations
 class vtkRenderer;
 
 namespace Foam
@@ -94,7 +98,7 @@ namespace runTimePostPro
 {
 
 /*---------------------------------------------------------------------------*\
-                          Class text Declaration
+                            Class text Declaration
 \*---------------------------------------------------------------------------*/
 
 class text
@@ -103,9 +107,9 @@ class text
 {
 public:
 
-    // Public enumerations
+    // Public Enumerations
 
-        //- Horizontal alignment type
+        //- Horizontal alignment type. Values to match VTK definitions
         enum halignType
         {
             LEFT   = 0,   //!< Left-justified text - default ("left")
@@ -124,13 +128,13 @@ protected:
         //- Text
         string string_;
 
-        //- Position
-        Tuple2<scalar, scalar> position_;
+        //- Position(s)
+        List<Tuple2<scalar, scalar>> positions_;
 
-        //- Size
+        //- Font size
         scalar size_;
 
-        //- Colour
+        //- Text colour
         autoPtr<Function1<vector>> colour_;
 
         //- Horizontal alignment
@@ -177,7 +181,7 @@ public:
 
     // Member Functions
 
-        //- Add surface(s) to scene
+        //- Add text to scene
         virtual void addGeometryToScene
         (
             const scalar position,
@@ -187,7 +191,7 @@ public:
         //- Update actors
         virtual void updateActors(const scalar position);
 
-        //- Clear files used to create the object(s)
+        //- Clear files used to create the object(s) - no-op
         virtual bool clear();
 };
 
diff --git a/src/runTimePostProcessing/volumeFilter.C b/src/runTimePostProcessing/volumeFilter.C
new file mode 100644
index 0000000000000000000000000000000000000000..9a3caf7f8b4fbfb60dfe7d64e0d274bf383debbc
--- /dev/null
+++ b/src/runTimePostProcessing/volumeFilter.C
@@ -0,0 +1,144 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "volumeFilter.H"
+#include "runTimePostProcessing.H"
+
+// VTK includes
+#include "vtkCellData.h"
+#include "vtkCellDataToPointData.h"
+#include "vtkCompositeDataGeometryFilter.h"
+#include "vtkCompositeDataSet.h"
+#include "vtkCompositePolyDataMapper.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPointData.h"
+#include "vtkPolyData.h"
+#include "vtkPolyDataMapper.h"
+#include "vtkSmartPointer.h"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::functionObjects::runTimePostPro::volumeFilter::volumeFilter
+(
+    const runTimePostProcessing& parent,
+    const dictionary& dict,
+    const HashPtrTable<Function1<vector>>& colours
+)
+:
+    surface(parent, dict, colours)
+{}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+vtkSmartPointer<vtkMultiPieceDataSet>
+Foam::functionObjects::runTimePostPro::volumeFilter::mesh
+(
+    Foam::vtk::vtuAdaptor& adaptor
+) const
+{
+    auto multiPiece = vtkSmartPointer<vtkMultiPieceDataSet>::New();
+
+    multiPiece->SetNumberOfPieces(Pstream::nProcs());
+    multiPiece->SetPiece
+    (
+        Pstream::myProcNo(),
+        adaptor.internal(parent().mesh())
+    );
+
+    return multiPiece;
+}
+
+
+bool Foam::functionObjects::runTimePostPro::volumeFilter::addDimField
+(
+    vtkDataSet* piece,
+    const vtk::vtuAdaptor& adaptor,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    return (piece && ioptr) &&
+    (
+        addDimField<scalar>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<vector>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<sphericalTensor>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<symmTensor>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<tensor>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+    );
+}
+
+
+int Foam::functionObjects::runTimePostPro::volumeFilter::addDimField
+(
+    vtkMultiPieceDataSet* piece,
+    const vtk::vtuAdaptor& adaptor,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    return (piece && ioptr) &&
+    (
+        addDimField<scalar>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<vector>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<sphericalTensor>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<symmTensor>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+     || addDimField<tensor>
+        (
+            piece, adaptor, ioptr, fieldName
+        )
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/volumeFilter.H b/src/runTimePostProcessing/volumeFilter.H
new file mode 100644
index 0000000000000000000000000000000000000000..d75a1a7a51e8173e30d9d63c6fe6b48753ce7867
--- /dev/null
+++ b/src/runTimePostProcessing/volumeFilter.H
@@ -0,0 +1,162 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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::functionObjects::runTimePostPro::volumeFilter
+
+Description
+    Visualisation of OpenFOAM volume fields as surface data using
+    a VTK filter cascade.
+
+Note
+    Since this filter includes an OpenFOAM/VTK adaptor level,
+    it is ill-suited to mismatches in data parallelization.
+    If OpenFOAM is running in parallel but VTK is not, it would be rather
+    expensive to collect all the data on the master node for this filter.
+    That approach is acceptable for smaller amounts of data, but do not
+    allow for volume meshes.
+
+SourceFiles
+    volumeFilter.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_runTimePostPro_volumeFilter_H
+#define functionObjects_runTimePostPro_volumeFilter_H
+
+#include "runTimePostProcessing.H"
+#include "surface.H"
+#include "foamVtkVtuAdaptor.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+class vtkDataSet;
+class vtkMultiPieceDataSet;
+template<class T> class vtkSmartPointer;
+
+namespace Foam
+{
+namespace vtk
+{
+class vtuAdaptor;
+}
+
+namespace functionObjects
+{
+namespace runTimePostPro
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class volumeFilter Declaration
+\*---------------------------------------------------------------------------*/
+
+class volumeFilter
+:
+    public surface
+{
+protected:
+
+    // Protected Member Functions
+
+        //- Return a vtu mesh with addressing information stored in adaptor
+        vtkSmartPointer<vtkMultiPieceDataSet> mesh
+        (
+            Foam::vtk::vtuAdaptor& adaptor
+        ) const;
+
+
+        bool addDimField
+        (
+            vtkDataSet* piece,
+            const vtk::vtuAdaptor& adaptor,
+            const regIOobject* ioptr,
+            const word& fieldName
+        ) const;
+
+        int addDimField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const vtk::vtuAdaptor& adaptor,
+            const regIOobject* ioptr,
+            const word& fieldName
+        ) const;
+
+        template<class Type>
+        bool addDimField
+        (
+            vtkDataSet* piece,
+            const vtk::vtuAdaptor& adaptor,
+            const regIOobject* ioptr,
+            const word& fieldName
+        ) const;
+
+        template<class Type>
+        int addDimField
+        (
+            vtkMultiPieceDataSet* multiPiece,
+            const vtk::vtuAdaptor& adaptor,
+            const regIOobject* ioptr,
+            const word& fieldName
+        ) const;
+
+
+        //- No copy construct
+        volumeFilter(const volumeFilter&) = delete;
+
+        //- No copy assignment
+        void operator=(const volumeFilter&) = delete;
+
+
+public:
+
+    // Constructors
+
+        //- Construct from dictionary
+        volumeFilter
+        (
+            const runTimePostProcessing& parent,
+            const dictionary& dict,
+            const HashPtrTable<Function1<vector>>& colours
+        );
+
+
+    //- Destructor
+    virtual ~volumeFilter() = default;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace runTimePostPro
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "volumeFilterTemplates.C"
+#endif
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/runTimePostProcessing/volumeFilterTemplates.C b/src/runTimePostProcessing/volumeFilterTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..5d5c6594c4d1cc7edca7fdbb3670174ff264f5ae
--- /dev/null
+++ b/src/runTimePostProcessing/volumeFilterTemplates.C
@@ -0,0 +1,120 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2019 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// OpenFOAM includes
+#include "volumeFilter.H"
+#include "fvMesh.H"
+#include "volFields.H"
+#include "foamVtkTools.H"
+
+// VTK includes
+#include "vtkCellData.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPointData.h"
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+bool Foam::functionObjects::runTimePostPro::volumeFilter::addDimField
+(
+    vtkDataSet* piece,
+    const vtk::vtuAdaptor& adaptor,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    vtkSmartPointer<vtkFloatArray> vtkdata;
+
+    const auto* dimptr =
+        dynamic_cast<const DimensionedField<Type, volMesh>*>(ioptr);
+
+    if (dimptr && !vtkdata)
+    {
+        vtkdata = adaptor.convertField(*dimptr);
+    }
+
+    const auto* volptr =
+        dynamic_cast<const GeometricField<Type, fvPatchField, volMesh>*>(ioptr);
+
+    if (volptr && !vtkdata)
+    {
+        vtkdata = adaptor.convertField(volptr->internalField());
+    }
+
+    if (vtkdata)
+    {
+        piece->GetCellData()->AddArray(vtkdata);
+        return true;
+    }
+
+    return false;
+}
+
+
+template<class Type>
+int Foam::functionObjects::runTimePostPro::volumeFilter::addDimField
+(
+    vtkMultiPieceDataSet* multiPiece,
+    const vtk::vtuAdaptor& adaptor,
+    const regIOobject* ioptr,
+    const word& fieldName
+) const
+{
+    if (!multiPiece)
+    {
+        return 0;
+    }
+
+    const int nCmpt(pTraits<Type>::nComponents);
+
+    if (!needsCollective())
+    {
+        // Simple case (serial-serial, parallel-parallel)
+
+        auto piece = multiPiece->GetPiece(Pstream::myProcNo());
+
+        if
+        (
+            addDimField<Type>
+            (
+                piece,
+                adaptor,
+                ioptr,
+                fieldName
+            )
+        )
+        {
+            return nCmpt;
+        }
+    }
+    else
+    {
+    }
+
+    return 0;
+}
+
+
+// ************************************************************************* //