From b5676cd627c85b4017a53524067b4f8ffed7d5a0 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Mon, 10 Feb 2020 15:21:05 +0100
Subject: [PATCH] ENH: overhaul ensight handling (#1579)

- includes restructuring and simplification of low-level ensight part
  handling and refactor of backends to improve code reuse.

foamToEnsight
-------------

  * new cellZone support.
    This was previously only possible via a separate foamToEnsightParts
    utility that was not parallelized.

  * support for point fields.

  * `-nearCellValue` option (as per foamToVTK)

  * support data indexing using values from the time index

foamToEnsightParts
------------------
  * now redundant and removed.

ensightOutputSurface (new class)
--------------------------------
  * a lightweight wrapper for point/face references that is tailored
    for the ensightSurfaceWriter. It uses compact face/point information
    and is serial only, since this is the format requirements from the
    surfaceWriter class.

ensightMesh (revised class)
---------------------------
  * now only holds a polyMesh reference, which removes its dependency
    on finiteVolume and allows it to be relocated under fileFormats
    instead of conversion.

Removed classes: ensightParts, ensighPartFaces, ensightPartCells

- these were used by foamToEnsightParts, but not needed anymore.
---
 .../dataConversion/foamToEnsight/Make/options |   2 -
 .../foamToEnsight/checkMeshMoving.H           |  10 +-
 .../foamToEnsight/convertLagrangian.H         |  12 +-
 .../foamToEnsight/convertVolumeFields.H       |  30 +-
 .../foamToEnsight/foamToEnsight.C             | 302 +++++--
 .../getTimeIndex.H                            |   2 +-
 .../dataConversion/foamToEnsight/readFields.H | 104 ++-
 .../foamToEnsight/writeDimFields.H            |  76 +-
 .../writePointFields.H}                       |  71 +-
 .../foamToEnsight/writeVolFields.H            |  68 +-
 .../foamToEnsightParts/Make/files             |   4 -
 .../foamToEnsightParts/Make/options           |  15 -
 .../foamToEnsightParts/checkMeshMoving.H      |  45 -
 .../foamToEnsightParts/convertLagrangian.H    | 138 ---
 .../foamToEnsightParts/findCloudFields.H      |  94 ---
 .../foamToEnsightParts/foamToEnsightParts.C   | 370 --------
 .../foamToEnsightParts/moveMesh.H             |  19 -
 .../foamToEnsightParts/readFields.H           | 101 ---
 .../foamToEnsightParts/writeDimFields.H       | 125 ---
 src/conversion/Make/files                     |   4 -
 src/conversion/ensight/mesh/ensightMesh.C     | 404 ---------
 src/conversion/ensight/mesh/ensightMesh.H     | 503 -----------
 src/conversion/ensight/mesh/ensightMeshIO.C   | 794 ------------------
 .../ensight/mesh/ensightMeshOptions.C         | 183 ----
 .../ensight/output/ensightOutputVolField.H    |  78 +-
 .../output/ensightOutputVolFieldTemplates.C   | 404 ++++-----
 src/fileFormats/Make/files                    |  20 +-
 src/fileFormats/ensight/mesh/ensightMesh.C    | 479 +++++++++++
 src/fileFormats/ensight/mesh/ensightMesh.H    | 342 ++++++++
 .../ensight/mesh/ensightMeshI.H               |  66 +-
 .../ensight/mesh/ensightMeshOptions.C         | 340 ++++++++
 .../ensight/output/ensightOutput.C            | 490 +++++++++++
 .../ensight/output/ensightOutput.H            | 258 +++++-
 .../ensight/output/ensightOutputTemplates.C   | 304 +++----
 .../ensight/part/{ => cells}/ensightCells.C   | 154 ++--
 .../ensight/part/cells/ensightCells.H         | 279 ++++++
 .../ensight/part/cells/ensightCellsAddr.C     | 238 ++++++
 .../ensight/part/{ => cells}/ensightCellsI.H  |  59 +-
 .../ensight/part/cells/ensightCellsIO.C       | 322 +++++++
 src/fileFormats/ensight/part/ensightCells.H   | 215 -----
 src/fileFormats/ensight/part/ensightFaces.H   | 222 -----
 src/fileFormats/ensight/part/ensightPart.H    | 183 ----
 .../ensight/part/ensightPartCells.C           | 344 --------
 .../ensight/part/ensightPartCells.H           | 180 ----
 .../ensight/part/ensightPartFaces.C           | 302 -------
 .../ensight/part/ensightPartFaces.H           | 202 -----
 src/fileFormats/ensight/part/ensightParts.C   | 134 ---
 src/fileFormats/ensight/part/ensightParts.H   | 120 ---
 .../ensight/part/{ => faces}/ensightFaces.C   | 241 +++---
 .../ensight/part/faces/ensightFaces.H         | 264 ++++++
 .../ensight/part/faces/ensightFacesAddr.C     |  96 +++
 .../ensight/part/{ => faces}/ensightFacesI.H  |  68 +-
 .../ensight/part/faces/ensightFacesIO.C       | 138 +++
 .../ensight/part/{ => part}/ensightPart.C     |  47 +-
 .../ensight/part/part/ensightPart.H           | 214 +++++
 .../part/surface/ensightOutputSurface.C       |  62 +-
 .../part/surface/ensightOutputSurface.H       | 137 +++
 .../surface/ensightOutputSurfaceTemplates.C   |  98 ++-
 .../utilities/ensightWrite/ensightWrite.C     |   4 +-
 .../ensightWrite/ensightWriteTemplates.C      |   4 +-
 .../writers/ensight/ensightSurfaceWriter.C    |   2 +-
 .../ensight/ensightSurfaceWriterCollated.C    |  41 +-
 .../ensight/ensightSurfaceWriterUncollated.C  |  44 +-
 63 files changed, 4853 insertions(+), 5818 deletions(-)
 rename applications/utilities/postProcessing/dataConversion/{foamToEnsightParts => foamToEnsight}/getTimeIndex.H (97%)
 rename applications/utilities/postProcessing/dataConversion/{foamToEnsightParts/writeVolFields.H => foamToEnsight/writePointFields.H} (64%)
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/files
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/options
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/checkMeshMoving.H
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertLagrangian.H
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findCloudFields.H
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/moveMesh.H
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.H
 delete mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeDimFields.H
 delete mode 100644 src/conversion/ensight/mesh/ensightMesh.C
 delete mode 100644 src/conversion/ensight/mesh/ensightMesh.H
 delete mode 100644 src/conversion/ensight/mesh/ensightMeshIO.C
 delete mode 100644 src/conversion/ensight/mesh/ensightMeshOptions.C
 create mode 100644 src/fileFormats/ensight/mesh/ensightMesh.C
 create mode 100644 src/fileFormats/ensight/mesh/ensightMesh.H
 rename src/{conversion => fileFormats}/ensight/mesh/ensightMeshI.H (55%)
 create mode 100644 src/fileFormats/ensight/mesh/ensightMeshOptions.C
 create mode 100644 src/fileFormats/ensight/output/ensightOutput.C
 rename src/fileFormats/ensight/part/{ => cells}/ensightCells.C (68%)
 create mode 100644 src/fileFormats/ensight/part/cells/ensightCells.H
 create mode 100644 src/fileFormats/ensight/part/cells/ensightCellsAddr.C
 rename src/fileFormats/ensight/part/{ => cells}/ensightCellsI.H (54%)
 create mode 100644 src/fileFormats/ensight/part/cells/ensightCellsIO.C
 delete mode 100644 src/fileFormats/ensight/part/ensightCells.H
 delete mode 100644 src/fileFormats/ensight/part/ensightFaces.H
 delete mode 100644 src/fileFormats/ensight/part/ensightPart.H
 delete mode 100644 src/fileFormats/ensight/part/ensightPartCells.C
 delete mode 100644 src/fileFormats/ensight/part/ensightPartCells.H
 delete mode 100644 src/fileFormats/ensight/part/ensightPartFaces.C
 delete mode 100644 src/fileFormats/ensight/part/ensightPartFaces.H
 delete mode 100644 src/fileFormats/ensight/part/ensightParts.C
 delete mode 100644 src/fileFormats/ensight/part/ensightParts.H
 rename src/fileFormats/ensight/part/{ => faces}/ensightFaces.C (52%)
 create mode 100644 src/fileFormats/ensight/part/faces/ensightFaces.H
 create mode 100644 src/fileFormats/ensight/part/faces/ensightFacesAddr.C
 rename src/fileFormats/ensight/part/{ => faces}/ensightFacesI.H (52%)
 create mode 100644 src/fileFormats/ensight/part/faces/ensightFacesIO.C
 rename src/fileFormats/ensight/part/{ => part}/ensightPart.C (73%)
 create mode 100644 src/fileFormats/ensight/part/part/ensightPart.H
 rename applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertVolumeFields.H => src/fileFormats/ensight/part/surface/ensightOutputSurface.C (59%)
 create mode 100644 src/fileFormats/ensight/part/surface/ensightOutputSurface.H
 rename applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.C => src/fileFormats/ensight/part/surface/ensightOutputSurfaceTemplates.C (54%)

diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/Make/options b/applications/utilities/postProcessing/dataConversion/foamToEnsight/Make/options
index 49c30cf7750..2c48a253240 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/Make/options
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/Make/options
@@ -3,13 +3,11 @@ EXE_INC = \
     -I$(LIB_SRC)/fileFormats/lnInclude \
     -I$(LIB_SRC)/meshTools/lnInclude \
     -I$(LIB_SRC)/conversion/lnInclude \
-    -I$(LIB_SRC)/dynamicMesh/lnInclude \
     -I$(LIB_SRC)/lagrangian/intermediate/lnInclude
 
 EXE_LIBS = \
     -lfiniteVolume \
     -lfileFormats \
-    -ldynamicMesh \
     -lconversion \
     -llagrangianIntermediate \
     -lgenericPatchFields
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/checkMeshMoving.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/checkMeshMoving.H
index 4849c951287..bb1b2dd4463 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/checkMeshMoving.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/checkMeshMoving.H
@@ -1,6 +1,6 @@
 // Check for "points" in any of the result directories
 
-bool meshMoving = false;
+bool hasMovingMesh = false;
 
 if (timeDirs.size() > 1 && Pstream::master())
 {
@@ -12,7 +12,7 @@ if (timeDirs.size() > 1 && Pstream::master())
     {
         const word& timeName = inst.name();
 
-        meshMoving =
+        hasMovingMesh =
         (
             timeName != mesh.pointsInstance()
          && IOobject
@@ -27,13 +27,13 @@ if (timeDirs.size() > 1 && Pstream::master())
             ).typeHeaderOk<pointIOField>(true, false)
         );
 
-        if (meshMoving)
+        if (hasMovingMesh)
         {
             break;
         }
     }
 
-    if (meshMoving)
+    if (hasMovingMesh)
     {
         Info<< "found. Writing meshes for every timestep." << endl;
     }
@@ -43,4 +43,4 @@ if (timeDirs.size() > 1 && Pstream::master())
     }
 }
 
-reduce(meshMoving, orOp<bool>());
+reduce(hasMovingMesh, orOp<bool>());
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertLagrangian.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertLagrangian.H
index b0478d844c4..143f9f226f7 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertLagrangian.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertLagrangian.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,9 +31,8 @@ Description
 // Cloud field data output
 if (doLagrangian)
 {
-    forAll(cloudNames, cloudNo)
+    for (const word& cloudName : cloudNames)
     {
-        const word& cloudName = cloudNames[cloudNo];
         const HashTable<word>& theseCloudFields = cloudFields[cloudName];
 
         fileNameList currentCloudDirs
@@ -48,12 +47,7 @@ if (doLagrangian)
         Info<< "Write " << cloudName << " (";
 
         const bool cloudExists =
-            returnReduce
-            (
-                currentCloudDirs.found(cloudName),
-                orOp<bool>()
-            );
-
+            returnReduce(currentCloudDirs.found(cloudName), orOp<bool>());
 
         {
             autoPtr<ensightFile> os = ensCase.newCloud(cloudName);
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H
index 5490eec6d7a..a831662117e 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -35,25 +35,19 @@ Description
 {
     Info<< "Write volume field (";
 
-    writeAllVolFields
-    (
-        ensCase,
-        ensMesh,
-        meshProxy,
-        objects,
-        nodeValues
-    );
-
-    writeAllDimFields
-    (
-        ensCase,
-        ensMesh,
-        meshProxy,
-        objects,
-        nodeValues
-    );
+    writeAllVolFields(ensCase, ensMesh, objects, nearCellValue);
+    writeAllDimFields(ensCase, ensMesh, objects);
 
     Info<< " )" << nl;
+
+    // PointData
+    // - only construct pointMesh on request (it constructs edge addressing)
+    if (!noPointValues)
+    {
+        Info<< "Write point field (";
+        writeAllPointFields(ensCase, ensMesh, objects);
+        Info<< " )" << nl;
+    }
 }
 
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C b/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
index c440c358252..27cf45716f7 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2018 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,7 +32,11 @@ Group
 
 Description
     Translate OpenFOAM data to EnSight format.
-    An Ensight part is created for the internalMesh and for each patch.
+    An Ensight part is created for cellZones (unzoned cells are "internalMesh")
+    and patches.
+
+    - Handles volume fields, dimensioned fields, point fields
+    - Handles mesh topology changes.
 
 Usage
     \b foamToEnsight [OPTION]
@@ -51,36 +55,66 @@ Usage
         The quoting is required to avoid shell expansions and to pass the
         information as a single argument.
 
+      - \par -nearCellValue
+        Use zero-gradient cell values on patches
+
+      - \par -nodeValues
+        Force interpolation of values to nodes
+
       - \par -no-boundary
-        Suppress writing any patches.
+        Suppress output for all boundary patches
 
       - \par -no-internal
-        Suppress writing the internal mesh.
+        Suppress output for internal (volume) mesh
+
+      - \par -no-cellZones
+        Suppress cellZone handling
 
       - \par -no-lagrangian
         Suppress writing lagrangian positions and fields.
 
-      - \par -patches patch or patch list
-        Specify particular patches to write.
+      - \par -no-mesh
+        Suppress writing the geometry. Can be useful for converting partial
+        results for a static geometry.
 
-      - \par -faceZones zone or zone list
-        Specify faceZones to write, with wildcards
-
-      - \par -cellZone zoneName
-        Specify single cellZone to write (not lagrangian)
+      - \par -no-point-data
+        Suppress conversion of pointFields. No interpolated PointData.
 
       - \par -noZero
         Exclude the often incomplete initial conditions.
 
+      - \par -index \<start\>
+        Starting index for the Ensight \c data/######## files.
+
+      - \par -index-by-time
+        Use time index from the uniform/time file.
+
       - \par -name \<subdir\>
         Define sub-directory name to use for Ensight data (default: "EnSight")
 
       - \par -width \<n\>
         Width of Ensight data subdir (default: 8)
 
-Note
-    Writes to \a EnSight directory to avoid collisions with
-    foamToEnsightParts
+      - \par -cellZones NAME | LIST
+        Specify single zone or multiple cell zones (name or regex) to write
+
+      - \par -faceZones NAME | LIST
+        Specify single zone or multiple face zones (name or regex) to write
+
+      - \par -patches NAME | LIST
+        Specify single patch or multiple patches (name or regex) to write
+        For example,
+        \verbatim
+          -patches top
+          -patches '( front \".*back\" )'
+        \endverbatim
+
+      - \par -excludePatches NAME | LIST
+        Specify single or multiple patches (name or regex) not to convert.
+        For example,
+        \verbatim
+          -excludePatches '( inlet_1 inlet_2 "proc.*" )'
+        \endverbatim
 
 \*---------------------------------------------------------------------------*/
 
@@ -93,6 +127,7 @@ Note
 #include "HashOps.H"
 
 #include "fvc.H"
+#include "fvMesh.H"
 #include "fieldTypes.H"
 #include "volFields.H"
 #include "scalarIOField.H"
@@ -104,26 +139,18 @@ Note
 #include "ensightMesh.H"
 #include "ensightOutputCloud.H"
 #include "ensightOutputVolField.H"
-#include "fvMeshSubsetProxy.H"
 
 // local files
 #include "readFields.H"
 #include "writeVolFields.H"
 #include "writeDimFields.H"
+#include "writePointFields.H"
 
 #include "memInfo.H"
 
-using namespace Foam;
-
-//- Get internal field and make it a zero-gradient volume field with subsetting
-template<class GeoField>
-tmp<GeoField>
-getZeroGradInternalField(IOobject& io, const fvMeshSubsetProxy& proxy)
-{
-    auto tfield = tmp<typename GeoField::Internal>::New(io, proxy.baseMesh());
-    return proxy.interpolateInternal(tfield);
-}
+#undef foamToEnsight_useTimeIndex
 
+using namespace Foam;
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -131,8 +158,8 @@ int main(int argc, char *argv[])
 {
     argList::addNote
     (
-        "Translate OpenFOAM data to Ensight format with a part for"
-        " the internalMesh and for each patch."
+        "Translate OpenFOAM data to Ensight format with parts for the "
+        " cellZones, any unzoned cells and patches"
     );
     timeSelector::addOptions();
 
@@ -147,10 +174,42 @@ int main(int argc, char *argv[])
         "ascii",
         "Write in ASCII format instead of 'C Binary'"
     );
+    argList::addOption
+    (
+        "index",
+        "start",
+        "Starting index for Ensight data/ files."
+        , true  // mark as an advanced option
+    );
+    argList::addBoolOption
+    (
+        "index-by-time",
+        "Use time index from the uniform/time file "
+        , true  // mark as an advanced option
+    );
+    argList::addOption
+    (
+        "name",
+        "subdir",
+        "Sub-directory name for Ensight output (default: 'EnSight')"
+    );
+    argList::addOption
+    (
+        "width",
+        "n",
+        "Width of Ensight data subdir"
+    );
+    argList::addBoolOption
+    (
+        "nearCellValue",
+        "Use zero-gradient cell values on patches"
+        , true  // mark as an advanced option
+    );
     argList::addBoolOption
     (
         "nodeValues",
-        "Write values at nodes"
+        "Force interpolation of values to nodes"
+        , true  // mark as an advanced option
     );
     argList::addBoolOption
     (
@@ -165,12 +224,37 @@ int main(int argc, char *argv[])
         "Suppress writing the internal mesh"
     );
     argList::addBoolOption
+    (
+        "no-cellZones",
+        "Suppress writing any cellZones"
+    );
+    argList::addBoolOption
     (
         "no-lagrangian",  // noLagrangian
         "Suppress writing lagrangian positions and fields"
     );
     argList::addOptionCompat("no-lagrangian", {"noLagrangian", 1806});
 
+    argList::addBoolOption
+    (
+        "no-point-data",
+        "Suppress conversion of pointFields and disables -nodeValues"
+    );
+    argList::addBoolOption
+    (
+        "no-mesh", // noMesh
+        "Suppress writing the geometry."
+        " Can be useful for converting partial results for a static geometry"
+        , true  // mark as an advanced option
+    );
+
+    // Future?
+    // argList::addBoolOption
+    // (
+    //     "one-boundary",  // allPatches
+    //     "Combine all patches into a single part"
+    // );
+
     argList::addOption
     (
         "patches",
@@ -179,6 +263,14 @@ int main(int argc, char *argv[])
         "Eg, 'inlet' or '(outlet \"inlet.*\")'"
     );
     argList::addOption
+    (
+        "excludePatches",
+        "wordRes",
+        "Specify single patch or multiple patches to exclude from writing."
+        " Eg, 'outlet' or '( inlet \".*Wall\" )'"
+        , true  // mark as an advanced option
+    );
+    argList::addOption
     (
         "faceZones",
         "wordRes",
@@ -194,22 +286,13 @@ int main(int argc, char *argv[])
     );
     argList::addOption
     (
-        "cellZone",
-        "word",
-        "Specify cellZone to write"
-    );
-    argList::addOption
-    (
-        "name",
-        "subdir",
-        "Sub-directory name for Ensight output (default: 'EnSight')"
-    );
-    argList::addOption
-    (
-        "width",
-        "n",
-        "Width of Ensight data subdir"
+        "cellZones",
+        "wordRes",
+        "Specify single or multiple cellZones to write\n"
+        "Eg, 'cells' or '( slice \"mfp-.*\" )'."
     );
+    argList::addOptionCompat("cellZone", {"cellZones", 1912});
+
 
     #include "setRootCase.H"
 
@@ -221,8 +304,6 @@ int main(int argc, char *argv[])
       : IOstream::BINARY
     );
 
-    const bool nodeValues = args.found("nodeValues");
-
     cpuTime timer;
     memInfo mem;
     Info<< "Initial memory " << mem.update().size() << " kB" << endl;
@@ -239,12 +320,44 @@ int main(int argc, char *argv[])
         regionPrefix = regionName;
     }
 
+
+    //
+    // Configuration
+    //
+    const bool doBoundary    = !args.found("no-boundary");
+    const bool doInternal    = !args.found("no-internal");
+    const bool doCellZones   = !args.found("no-cellZones");
+    const bool doLagrangian  = !args.found("no-lagrangian");
+    const bool noPointValues = args.found("no-point-data");
+    const bool nearCellValue = args.found("nearCellValue") && doBoundary;
+
+    // Control for numbering iterations
+    const bool doConsecutive = !args.found("index-by-time");
+    label indexingNumber = args.getOrDefault("index", 0);
+
+
+    // Write the geometry, unless otherwise specified
+    bool doGeometry = !args.found("no-mesh");
+
+    if (nearCellValue)
+    {
+        Info<< "Using neighbouring cell value instead of patch value"
+            << nl << endl;
+    }
+    if (noPointValues)
+    {
+        Info<< "Point fields and interpolated point data"
+            << " disabled with the '-no-point-data' option"
+            << nl;
+    }
+
     //
     // General (case) output options
     //
     ensightCase::options caseOpts(format);
 
-    caseOpts.nodeValues(args.found("nodeValues"));
+    // Forced point interpolation?
+    caseOpts.nodeValues(args.found("nodeValues") && !noPointValues);
     caseOpts.width(args.get<label>("width", 8));
     caseOpts.overwrite(true); // remove existing output directory
 
@@ -262,22 +375,32 @@ int main(int argc, char *argv[])
     }
 
 
-    //
-    // Output configuration (geometry related)
-    //
-    ensightMesh::options writeOpts(format);
-    writeOpts.useBoundaryMesh(!args.found("no-boundary"));
-    writeOpts.useInternalMesh(!args.found("no-internal"));
-    const bool doLagrangian = !args.found("no-lagrangian");
+    ensightMesh::options writeOpts;
+    writeOpts.useBoundaryMesh(doBoundary);
+    writeOpts.useInternalMesh(doInternal);
+    writeOpts.useCellZones(doCellZones);
 
     if (args.found("patches"))
     {
         writeOpts.patchSelection(args.getList<wordRe>("patches"));
     }
+    if (args.found("excludePatches"))
+    {
+        writeOpts.patchExclude(args.getList<wordRe>("excludePatches"));
+    }
+
     if (args.found("faceZones"))
     {
         writeOpts.faceZoneSelection(args.getList<wordRe>("faceZones"));
     }
+    if (args.found("cellZones"))
+    {
+        writeOpts.cellZoneSelection(args.getList<wordRe>("cellZones"));
+    }
+
+    // Report the setup
+    writeOpts.print(Info);
+
 
     //
     // Output configuration (field related)
@@ -286,22 +409,11 @@ int main(int argc, char *argv[])
     wordRes fieldPatterns;
     args.readListIfPresent<wordRe>("fields", fieldPatterns);
 
-    word cellZoneName;
-    if (args.readIfPresent("cellZone", cellZoneName))
-    {
-        Info<< "Converting cellZone " << cellZoneName
-            << " only, with new outside faces as \"oldInternalFaces\"."
-            << nl;
-    }
-
-    // Ignored (unproxied) if cellZoneName is empty
-    fvMeshSubsetProxy meshProxy(mesh, fvMeshSubsetProxy::ZONE, cellZoneName);
-
     // New ensight case file, initialize header etc.
     ensightCase ensCase(outputDir, args.globalCaseName(), caseOpts);
 
-    // Construct the Ensight mesh
-    ensightMesh ensMesh(meshProxy.mesh(), writeOpts);
+    // Construct ensight mesh
+    ensightMesh ensMesh(mesh, writeOpts);
 
     if (Pstream::master())
     {
@@ -335,50 +447,64 @@ int main(int argc, char *argv[])
         // Remove "*_0" restart fields
         objects.prune_0();
 
-        // Only retain volume and dimensioned fields.
-        objects.filterClasses
-        (
-            [](const word& clsName){
-                return
-                (
-                    fieldTypes::volume.found(clsName)
-                 || fieldTypes::internal.found(clsName)
-                );
-            }
-        );
+        if (noPointValues)
+        {
+            // Prune point fields unless specifically requested
+            objects.filterClasses
+            (
+                [](const word& clsName)
+                {
+                    return fieldTypes::point.found(clsName);
+                },
+                true // prune
+            );
+        }
 
         wordList objectNames(objects.sortedNames());
 
         // Check availability for all times...
-        checkData(meshProxy.baseMesh(), timeDirs, objectNames);
+        checkData(mesh, timeDirs, objectNames);
 
         testedObjectNames = objectNames;
     }
 
+    if (hasMovingMesh && !doGeometry)
+    {
+        Info<< "has moving mesh: ignoring '-no-mesh' option" << endl;
+        doGeometry = true;
+    }
 
-    forAll(timeDirs, timeIndex)
+    forAll(timeDirs, timei)
     {
-        runTime.setTime(timeDirs[timeIndex], timeIndex);
-        ensCase.nextTime(timeDirs[timeIndex]);
+        runTime.setTime(timeDirs[timei], timei);
+
+        // Index for the Ensight case
+        #include "getTimeIndex.H"
+
+        ensCase.setTime(timeDirs[timei], timeIndex);
 
         Info<< "Time [" << timeIndex << "] = " << runTime.timeName() << nl;
 
         polyMesh::readUpdateState meshState = mesh.readUpdate();
-        if (meshState != polyMesh::UNCHANGED)
+        const bool moving = (meshState != polyMesh::UNCHANGED);
+
+        if (moving)
         {
-            meshProxy.correct();
             ensMesh.expire();
             ensMesh.correct();
         }
 
-        if (timeIndex == 0 || meshMoving)
+        if (timei == 0 || moving)
         {
-            autoPtr<ensightGeoFile> os = ensCase.newGeometry(meshMoving);
-            ensMesh.write(os);
+            if (doGeometry)
+            {
+                autoPtr<ensightGeoFile> os = ensCase.newGeometry(hasMovingMesh);
+                ensMesh.write(os);
+            }
         }
 
         // Objects at this time
-        IOobjectList objects(meshProxy.baseMesh(), runTime.timeName());
+        IOobjectList objects(mesh, runTime.timeName());
 
         // Restrict to objects that are available for all times
         objects.filterObjects(testedObjectNames);
@@ -386,7 +512,7 @@ int main(int argc, char *argv[])
         // Volume, internal, point fields
         #include "convertVolumeFields.H"
 
-        // Write lagrangian data
+        // Lagrangian fields
         #include "convertLagrangian.H"
 
         Info<< "Wrote in "
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/getTimeIndex.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/getTimeIndex.H
similarity index 97%
rename from applications/utilities/postProcessing/dataConversion/foamToEnsightParts/getTimeIndex.H
rename to applications/utilities/postProcessing/dataConversion/foamToEnsight/getTimeIndex.H
index b83ba15cef6..aeb83110064 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/getTimeIndex.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/getTimeIndex.H
@@ -3,7 +3,7 @@
 
 label timeIndex = 0;
 {
-    if (optIndex)
+    if (doConsecutive)
     {
         timeIndex = indexingNumber++;
     }
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H
index d51ce30412d..8f378f9cb92 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,7 +27,8 @@ InNamespace
     Foam
 
 Description
-    Read fields from disk for foamToEnsight
+    Helper routines for reading a field or fields,
+    for foamToEnsight
 
 SourceFiles
     readFields.C
@@ -39,39 +40,114 @@ SourceFiles
 
 #include "instantList.H"
 #include "IOobjectList.H"
-#include "fvMeshSubsetProxy.H"
+#include "zeroGradientFvPatchFields.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-//- Get the field and subset it, or return nullptr
+//- Get the field or return nullptr
 template<class GeoField>
-tmp<GeoField> getField(const IOobject* io, const fvMeshSubsetProxy& proxy)
+tmp<GeoField> getField
+(
+    const IOobject* io,
+    const typename GeoField::Mesh& mesh
+)
 {
     if (io)
     {
-        auto tfield = tmp<GeoField>::New(*io, proxy.baseMesh());
-        return proxy.interpolate(tfield);
+        return tmp<GeoField>::New(*io, mesh);
     }
 
     return nullptr;
 }
 
 
-//- Get internal field and make it a zero-gradient volume field with subsetting
+//- Get the named field from the objects, or return nullptr.
 template<class GeoField>
-tmp<GeoField>
-getZeroGradField(const IOobject* io, const fvMeshSubsetProxy& proxy)
+tmp<GeoField> getField
+(
+    const typename GeoField::Mesh& mesh,
+    const IOobjectList& objects,
+    const word& fieldName
+)
 {
-    if (io)
+    // Can do something with syncPar on failure ...
+
+    return getField<GeoField>(objects.findObject(fieldName), mesh);
+}
+
+
+//- Convert an internal field to zero-gradient volume field
+template<class Type>
+tmp<GeometricField<Type, fvPatchField, volMesh>>
+makeZeroGradientField
+(
+    const tmp
+    <
+        typename GeometricField<Type, fvPatchField, volMesh>::Internal
+    >& tdf
+)
+{
+    if (tdf.valid())
+    {
+        auto& df = tdf.ref();
+
+        auto tfield = GeometricField<Type, fvPatchField, volMesh>::New
+        (
+            df.name(),
+            df.mesh(),
+            df.dimensions(),
+            std::move(df.field()),
+            zeroGradientFvPatchScalarField::typeName
+        );
+
+        tfield.ref().oriented() = df.oriented();
+        tfield.ref().correctBoundaryConditions();
+
+        tdf.clear();
+
+        return tfield;
+    }
+
+    tdf.clear();
+
+    return nullptr;
+}
+
+
+//- Convert a volume field to zero-gradient volume field
+template<class Type>
+tmp<GeometricField<Type, fvPatchField, volMesh>>
+makeZeroGradientField
+(
+    const tmp<GeometricField<Type, fvPatchField, volMesh>>& tdf
+)
+{
+    if (tdf.valid())
     {
-        auto tfield =
-            tmp<typename GeoField::Internal>::New(*io, proxy.baseMesh());
-        return proxy.interpolateInternal(tfield);
+        auto& df = tdf.ref();
+
+        auto tfield = GeometricField<Type, fvPatchField, volMesh>::New
+        (
+            df.name(),
+            df.mesh(),
+            df.dimensions(),
+            std::move(df.primitiveFieldRef(false)),  // No update accessTime
+            zeroGradientFvPatchScalarField::typeName
+        );
+
+        tfield.ref().oriented() = df.oriented();
+        tfield.ref().correctBoundaryConditions();
+
+        tdf.clear();
+
+        return tfield;
     }
 
+    tdf.clear();
+
     return nullptr;
 }
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeDimFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeDimFields.H
index ae4d9c35506..6d5e335198c 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeDimFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeDimFields.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,12 +30,11 @@ Description
     Read dimensioned fields from disk and write with ensightMesh
 
 SourceFiles
-    writeDimFields.H
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef writeDimFields_H
-#define writeDimFields_H
+#ifndef ensight_writeDimFields_H
+#define ensight_writeDimFields_H
 
 #include "writeVolFields.H"
 
@@ -45,39 +44,59 @@ namespace Foam
 {
 
 template<class Type>
-label writeDimFields
+bool writeDimField
 (
     ensightCase& ensCase,
     const ensightMesh& ensMesh,
-    const fvMeshSubsetProxy& proxy,
-    const IOobjectList& objects,
-    const bool nodeValues
+    const tmp<DimensionedField<Type, volMesh>>& tdf
 )
 {
-    typedef GeometricField<Type, fvPatchField, volMesh> GeoField;
+    if (!tdf.valid())
+    {
+        return false;
+    }
+
+    auto tfield = makeZeroGradientField<Type>(tdf);
+
+    // Now a volField with zero-gradient boundaries
+
+    return writeVolField<Type>
+    (
+        ensCase,
+        ensMesh,
+        tfield,
+        false  // No nearCellValue, we already have zero-gradient
+    );
+}
+
 
+template<class Type>
+label writeDimFields
+(
+    ensightCase& ensCase,
+    const ensightMesh& ensMesh,
+    const IOobjectList& objects
+)
+{
     typedef typename
         GeometricField
         <
             Type, fvPatchField, volMesh
         >::Internal DimField;
 
+    const fvMesh& mesh = dynamicCast<const fvMesh>(ensMesh.mesh());
 
     label count = 0;
 
     for (const word& fieldName : objects.sortedNames<DimField>())
     {
-        const IOobject* io = objects.findObject(fieldName);
-
         if
         (
-            writeVolField<Type>
+            writeDimField<Type>
             (
                 ensCase,
                 ensMesh,
-                proxy,
-                getZeroGradField<GeoField>(io, proxy),
-                nodeValues
+                getField<DimField>(objects.findObject(fieldName), mesh)
             )
         )
         {
@@ -94,29 +113,26 @@ label writeAllDimFields
 (
     ensightCase& ensCase,
     const ensightMesh& ensMesh,
-    const fvMeshSubsetProxy& proxy,
-    const IOobjectList& objects,
-    const bool nodeValues
+    const IOobjectList& objects
 )
 {
-    #undef  foamToEnsight_WRITE_FIELD
-    #define foamToEnsight_WRITE_FIELD(PrimitiveType)    \
+    #undef  ensight_WRITE_FIELD
+    #define ensight_WRITE_FIELD(PrimitiveType)          \
         writeDimFields<PrimitiveType>                   \
         (                                               \
-            ensCase, ensMesh,                           \
-            proxy,                                      \
-            objects,                                    \
-            nodeValues                                  \
+            ensCase,                                    \
+            ensMesh,                                    \
+            objects                                     \
         )
 
     label count = 0;
-    count += foamToEnsight_WRITE_FIELD(scalar);
-    count += foamToEnsight_WRITE_FIELD(vector);
-    count += foamToEnsight_WRITE_FIELD(sphericalTensor);
-    count += foamToEnsight_WRITE_FIELD(symmTensor);
-    count += foamToEnsight_WRITE_FIELD(tensor);
+    count += ensight_WRITE_FIELD(scalar);
+    count += ensight_WRITE_FIELD(vector);
+    count += ensight_WRITE_FIELD(sphericalTensor);
+    count += ensight_WRITE_FIELD(symmTensor);
+    count += ensight_WRITE_FIELD(tensor);
 
-    #undef foamToEnsight_WRITE_FIELD
+    #undef ensight_WRITE_FIELD
     return count;
 }
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeVolFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writePointFields.H
similarity index 64%
rename from applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeVolFields.H
rename to applications/utilities/postProcessing/dataConversion/foamToEnsight/writePointFields.H
index dbad2846351..450c93611bc 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeVolFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writePointFields.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,17 +27,18 @@ InNamespace
     Foam
 
 Description
-    Read volume fields from disk and write with ensightParts
+    Read point fields from disk
+    and write as ensight data
 
 SourceFiles
-    writeVolFields.H
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef writeVolFields_H
-#define writeVolFields_H
+#ifndef ensight_writePointFields_H
+#define ensight_writePointFields_H
 
 #include "readFields.H"
+#include "ensightMesh.H"
 #include "fvMesh.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -46,12 +47,11 @@ namespace Foam
 {
 
 template<class Type>
-bool writeVolField
+bool writePointField
 (
     ensightCase& ensCase,
-    const ensightParts& ensParts,
-    const fvMesh& mesh,
-    const tmp<GeometricField<Type, fvPatchField, volMesh>>& tfield
+    const ensightMesh& ensMesh,
+    const tmp<GeometricField<Type, pointPatchField, pointMesh>>& tfield
 )
 {
     if (!tfield.valid())
@@ -61,14 +61,14 @@ bool writeVolField
 
     const auto& field = tfield();
 
-    autoPtr<ensightFile> os = ensCase.newData<Type>(field.name());
+    // PointData = true
+    autoPtr<ensightFile> os = ensCase.newData<Type>(field.name(), true);
 
-    // Currently serial only
-    bool wrote = ensightOutput::Serial::writeVolField<Type>
+    bool wrote = ensightOutput::writePointField<Type>
     (
+        os.ref(),
         field,
-        ensParts,
-        os.ref()
+        ensMesh
     );
 
     tfield.clear();
@@ -77,15 +77,16 @@ bool writeVolField
 
 
 template<class Type>
-label writeVolFields
+label writePointFields
 (
     ensightCase& ensCase,
-    const ensightParts& ensParts,
-    const fvMesh& mesh,
+    const ensightMesh& ensMesh,
     const IOobjectList& objects
 )
 {
-    typedef GeometricField<Type, fvPatchField, volMesh> GeoField;
+    typedef GeometricField<Type, pointPatchField, pointMesh> GeoField;
+
+    const pointMesh& ptMesh = pointMesh::New(ensMesh.mesh());
 
     label count = 0;
 
@@ -93,12 +94,11 @@ label writeVolFields
     {
         if
         (
-            writeVolField<Type>
+            writePointField<Type>
             (
                 ensCase,
-                ensParts,
-                mesh,
-                getField<GeoField>(objects.findObject(fieldName), mesh)
+                ensMesh,
+                getField<GeoField>(ptMesh, objects, fieldName)
             )
         )
         {
@@ -111,31 +111,30 @@ label writeVolFields
 }
 
 
-label writeAllVolFields
+label writeAllPointFields
 (
     ensightCase& ensCase,
-    const ensightParts& ensParts,
-    const fvMesh& mesh,
+    const ensightMesh& ensMesh,
     const IOobjectList& objects
 )
 {
-    #undef  foamToEnsight_WRITE_FIELD
-    #define foamToEnsight_WRITE_FIELD(PrimitiveType)    \
-        writeVolFields<PrimitiveType>                   \
+    #undef  ensight_WRITE_FIELD
+    #define ensight_WRITE_FIELD(PrimitiveType)          \
+        writePointFields<PrimitiveType>                 \
         (                                               \
-            ensCase, ensParts,                          \
-            mesh,                                       \
+            ensCase,                                    \
+            ensMesh,                                    \
             objects                                     \
         )
 
     label count = 0;
-    count += foamToEnsight_WRITE_FIELD(scalar);
-    count += foamToEnsight_WRITE_FIELD(vector);
-    count += foamToEnsight_WRITE_FIELD(sphericalTensor);
-    count += foamToEnsight_WRITE_FIELD(symmTensor);
-    count += foamToEnsight_WRITE_FIELD(tensor);
+    count += ensight_WRITE_FIELD(scalar);
+    count += ensight_WRITE_FIELD(vector);
+    count += ensight_WRITE_FIELD(sphericalTensor);
+    count += ensight_WRITE_FIELD(symmTensor);
+    count += ensight_WRITE_FIELD(tensor);
 
-    #undef foamToEnsight_WRITE_FIELD
+    #undef ensight_WRITE_FIELD
     return count;
 }
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H
index d0b6655c5fe..5415a006c18 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,15 +30,14 @@ Description
     Read volume fields from disk and write with ensightMesh
 
 SourceFiles
-    writeVolFields.H
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef writeVolFields_H
-#define writeVolFields_H
+#ifndef ensight_writeVolFields_H
+#define ensight_writeVolFields_H
 
 #include "readFields.H"
-#include "fvMeshSubsetProxy.H"
+#include "fvMesh.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -50,25 +49,41 @@ bool writeVolField
 (
     ensightCase& ensCase,
     const ensightMesh& ensMesh,
-    const fvMeshSubsetProxy& proxy,
     const tmp<GeometricField<Type, fvPatchField, volMesh>>& tfield,
-    const bool nodeValues
+    const bool nearCellValue = false
 )
 {
     if (!tfield.valid())
     {
         return false;
     }
+    else if (nearCellValue)
+    {
+        auto tzgrad = makeZeroGradientField<Type>(tfield);
+
+        // Recursive call
+        return writeVolField
+        (
+            ensCase,
+            ensMesh,
+            tzgrad,
+            false  // No nearCellValue, we already have zero-gradient
+        );
+    }
 
     const auto& field = tfield();
 
-    autoPtr<ensightFile> os = ensCase.newData<Type>(field.name());
+    // Forced use of node values?
+    const bool nodeValues = ensCase.nodeValues();
+
+    autoPtr<ensightFile> os =
+        ensCase.newData<Type>(field.name(), nodeValues);
 
     bool wrote = ensightOutput::writeVolField<Type>
     (
+        os.ref(),
         field,
         ensMesh,
-        os.ref(),
         nodeValues
     );
 
@@ -82,13 +97,14 @@ label writeVolFields
 (
     ensightCase& ensCase,
     const ensightMesh& ensMesh,
-    const fvMeshSubsetProxy& proxy,
     const IOobjectList& objects,
-    const bool nodeValues
+    const bool nearCellValue = false
 )
 {
     typedef GeometricField<Type, fvPatchField, volMesh> GeoField;
 
+    const fvMesh& mesh = dynamicCast<const fvMesh>(ensMesh.mesh());
+
     label count = 0;
 
     for (const word& fieldName : objects.sortedNames<GeoField>())
@@ -99,9 +115,8 @@ label writeVolFields
             (
                 ensCase,
                 ensMesh,
-                proxy,
-                getField<GeoField>(objects.findObject(fieldName), proxy),
-                nodeValues
+                getField<GeoField>(objects.findObject(fieldName), mesh),
+                nearCellValue
             )
         )
         {
@@ -118,29 +133,28 @@ label writeAllVolFields
 (
     ensightCase& ensCase,
     const ensightMesh& ensMesh,
-    const fvMeshSubsetProxy& proxy,
     const IOobjectList& objects,
-    const bool nodeValues
+    const bool nearCellValue = false
 )
 {
-    #undef  foamToEnsight_WRITE_FIELD
-    #define foamToEnsight_WRITE_FIELD(PrimitiveType)    \
+    #undef  ensight_WRITE_FIELD
+    #define ensight_WRITE_FIELD(PrimitiveType)          \
         writeVolFields<PrimitiveType>                   \
         (                                               \
-            ensCase, ensMesh,                           \
-            proxy,                                      \
+            ensCase,                                    \
+            ensMesh,                                    \
             objects,                                    \
-            nodeValues                                  \
+            nearCellValue                               \
         )
 
     label count = 0;
-    count += foamToEnsight_WRITE_FIELD(scalar);
-    count += foamToEnsight_WRITE_FIELD(vector);
-    count += foamToEnsight_WRITE_FIELD(sphericalTensor);
-    count += foamToEnsight_WRITE_FIELD(symmTensor);
-    count += foamToEnsight_WRITE_FIELD(tensor);
+    count += ensight_WRITE_FIELD(scalar);
+    count += ensight_WRITE_FIELD(vector);
+    count += ensight_WRITE_FIELD(sphericalTensor);
+    count += ensight_WRITE_FIELD(symmTensor);
+    count += ensight_WRITE_FIELD(tensor);
 
-    #undef foamToEnsight_WRITE_FIELD
+    #undef ensight_WRITE_FIELD
     return count;
 }
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/files b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/files
deleted file mode 100644
index 2dcb81ea73a..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/files
+++ /dev/null
@@ -1,4 +0,0 @@
-foamToEnsightParts.C
-readFields.C
-
-EXE = $(FOAM_APPBIN)/foamToEnsightParts
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/options b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/options
deleted file mode 100644
index 91f83bb8b00..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/Make/options
+++ /dev/null
@@ -1,15 +0,0 @@
-EXE_INC = \
-    -I$(LIB_SRC)/finiteVolume/lnInclude \
-    -I$(LIB_SRC)/fileFormats/lnInclude \
-    -I$(LIB_SRC)/meshTools/lnInclude \
-    -I$(LIB_SRC)/conversion/lnInclude \
-    -I$(LIB_SRC)/dynamicMesh/lnInclude \
-    -I$(LIB_SRC)/lagrangian/intermediate/lnInclude
-
-EXE_LIBS = \
-    -lfiniteVolume \
-    -lfileFormats \
-    -lconversion \
-    -ldynamicMesh \
-    -llagrangianIntermediate \
-    -lgenericPatchFields
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/checkMeshMoving.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/checkMeshMoving.H
deleted file mode 100644
index 8155d920cbc..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/checkMeshMoving.H
+++ /dev/null
@@ -1,45 +0,0 @@
-// check for "points" in all of the result directories
-// - could restrict to the selected times
-
-bool meshMoving = false;
-
-if (timeDirs.size() > 1 && Pstream::master())
-{
-    // We already loaded a mesh (usually from constant).
-    // See if any other "polyMesh/points" files exist too.
-
-    Info<< "Search for moving mesh ... " << flush;
-    forAll(timeDirs, timeI)
-    {
-        meshMoving =
-        (
-            IOobject
-            (
-                "points",
-                timeDirs[timeI].name(),
-                polyMesh::meshSubDir,
-                mesh,
-                IOobject::NO_READ,
-                IOobject::NO_WRITE,
-                false  // no register
-            ).typeHeaderOk<pointIOField>(true, false)
-        );
-
-        if (meshMoving)
-        {
-            break;
-        }
-    }
-
-    if (meshMoving)
-    {
-        Info<< "found." << nl
-            << "    Writing meshes for every timestep." << endl;
-    }
-    else
-    {
-        Info<< "none detected." << endl;
-    }
-}
-
-reduce(meshMoving, orOp<bool>());
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertLagrangian.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertLagrangian.H
deleted file mode 100644
index 8eb554150e1..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertLagrangian.H
+++ /dev/null
@@ -1,138 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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/>.
-
-Description
-    Code chunk for post-processing conversion of cloud(s) to Ensight
-
-\*---------------------------------------------------------------------------*/
-
-// Cloud field data output
-if (doLagrangian)
-{
-    forAll(cloudNames, cloudNo)
-    {
-        const word& cloudName = cloudNames[cloudNo];
-        const HashTable<word>& theseCloudFields = cloudFields[cloudName];
-
-        fileNameList currentCloudDirs
-        (
-            readDir
-            (
-                runTime.timePath()/regionPrefix/cloud::prefix,
-                fileName::DIRECTORY
-            )
-        );
-
-        Info<< "Write " << cloudName << " (";
-
-        const bool cloudExists =
-            returnReduce
-            (
-                currentCloudDirs.found(cloudName),
-                orOp<bool>()
-            );
-
-        {
-            autoPtr<ensightFile> os = ensCase.newCloud(cloudName);
-
-            ensightOutput::writeCloudPositions
-            (
-                mesh,
-                cloudName,
-                cloudExists,
-                os
-            );
-
-            Info<< " positions";
-            if (!cloudExists)
-            {
-                Info<< "{0}"; // report empty field
-            }
-        }
-
-        forAllConstIters(theseCloudFields, fieldIter)
-        {
-            const word& fieldName = fieldIter.key();
-            const word& fieldType = fieldIter.val();
-
-            IOobject fieldObject
-            (
-                fieldName,
-                mesh.time().timeName(),
-                cloud::prefix/cloudName,
-                mesh,
-                IOobject::MUST_READ
-            );
-
-            bool fieldExists = cloudExists; // No field without positions
-            if (cloudExists)
-            {
-                // Want MUST_READ (globally) and valid=false (locally),
-                // but that combination does not work.
-                // So check the header and sync globally
-
-                fieldExists =
-                    fieldObject.typeHeaderOk<IOField<scalar>>(false);
-
-                reduce(fieldExists, orOp<bool>());
-            }
-
-            bool wrote = false;
-            if (fieldType == scalarIOField::typeName)
-            {
-                autoPtr<ensightFile> os =
-                    ensCase.newCloudData<scalar>(cloudName, fieldName);
-
-                wrote = ensightOutput::writeCloudField<scalar>
-                (
-                    fieldObject, fieldExists, os
-                );
-            }
-            else if (fieldType == vectorIOField::typeName)
-            {
-                autoPtr<ensightFile> os =
-                    ensCase.newCloudData<vector>(cloudName, fieldName);
-
-                wrote = ensightOutput::writeCloudField<vector>
-                (
-                    fieldObject, fieldExists, os
-                );
-            }
-
-            if (wrote)
-            {
-                Info<< ' ' << fieldName;
-                if (!fieldExists)
-                {
-                    Info<< "{0}"; // report empty field
-                }
-            }
-        }
-        Info<< " )" << nl;
-    }
-}
-
-
-// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findCloudFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findCloudFields.H
deleted file mode 100644
index a8a56c33a8a..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/findCloudFields.H
+++ /dev/null
@@ -1,94 +0,0 @@
-// check all time directories for the following:
-
-// The fields for each cloud:
-HashTable<HashTable<word>> cloudFields;
-
-// Identify if lagrangian data exist at any time step.
-if (timeDirs.size() && doLagrangian)
-{
-    const fileName& baseDir = mesh.time().path();
-    const fileName cloudPrefix(regionPrefix/cloud::prefix);
-
-    Info<< "Searching for lagrangian ... " << flush;
-
-    for (const instant& inst : timeDirs)
-    {
-        const word& timeName = inst.name();
-
-        // DO NOT USE -->>  runTime.setTime(timeDirs[timeI], timeI);  <<--
-        // It incurs a large overhead when done so frequently.
-
-        fileNameList cloudDirs
-        (
-            readDir
-            (
-                baseDir/timeName/cloudPrefix,
-                fileName::DIRECTORY
-            )
-        );
-
-        for (fileName& cloudDir : cloudDirs)
-        {
-            const word cloudName(std::move(cloudDir));
-
-            IOobjectList cloudObjs
-            (
-                mesh,
-                timeName,
-                cloudPrefix/cloudName
-            );
-
-            // Clouds require "coordinates".
-            // The "positions" are for v1706 and lower.
-            // - detect and remove since these are treated specially
-
-            bool isCloud = false;
-            if (cloudObjs.erase("coordinates"))
-            {
-                isCloud = true;
-            }
-            if (cloudObjs.erase("positions"))
-            {
-                isCloud = true;
-            }
-
-            if (isCloud)
-            {
-                // Save the cloud fields on a per cloud basis
-                auto& fieldsPerCloud = cloudFields(cloudName);
-
-                forAllConstIters(cloudObjs, fieldIter)
-                {
-                    const IOobject* io = *fieldIter;
-
-                    // Field name/type
-                    fieldsPerCloud.insert(io->name(), io->headerClassName());
-                }
-            }
-        }
-    }
-
-    if (Pstream::parRun())
-    {
-        Pstream::mapCombineGather(cloudFields, HashTableOps::plusEqOp<word>());
-        Pstream::mapCombineScatter(cloudFields);
-    }
-
-    if (cloudFields.empty())
-    {
-        Info<< "none detected." << endl;
-    }
-}
-
-
-// Sorted list of cloud names
-const wordList cloudNames(cloudFields.sortedToc());
-
-if (cloudNames.size())
-{
-    // Complete the echo information - as flatOutput
-    cloudNames.writeList(Info) << endl;
-}
-
-
-// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
deleted file mode 100644
index 4b221f34943..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
+++ /dev/null
@@ -1,370 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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/>.
-
-Application
-    foamToEnsightParts
-
-Group
-    grpPostProcessingUtilities
-
-Description
-    Translate OpenFOAM data to Ensight format with an Ensight part
-    for each cellZone and patch.
-
-Usage
-    \b foamToEnsightParts [OPTION]
-
-    Options:
-      - \par -ascii
-        Write Ensight data in ASCII format instead of "C Binary"
-
-      - \par -fields \<fields\>
-        Specify single or multiple fields to write (all by default)
-        For example,
-        \verbatim
-          -fields T
-          -fields '(p T U \"alpha.*\")'
-        \endverbatim
-        The quoting is required to avoid shell expansions and to pass the
-        information as a single argument.
-
-      - \par -noZero
-        Exclude the often incomplete initial conditions.
-
-      - \par -index \<start\>
-        Ignore the time index contained in the time file and use a
-        simple indexing when creating the \c Ensight/data/######## files.
-
-      - \par -no-lagrangian
-        Suppress writing lagrangian positions and fields.
-
-      - \par -no-mesh
-        Suppress writing the geometry. Can be useful for converting partial
-        results for a static geometry.
-
-      - \par -noZero
-        Exclude the often incomplete initial conditions.
-
-      - \par -name \<subdir\>
-        Define sub-directory name to use for Ensight data (default: "Ensight")
-
-      - \par -width \<n\>
-        Width of Ensight data subdir
-
-Note
-    - no parallel data.
-    - writes to \a Ensight directory to avoid collisions with foamToEnsight.
-
-\*---------------------------------------------------------------------------*/
-
-#include "argList.H"
-#include "timeSelector.H"
-#include "IOobjectList.H"
-#include "IOmanip.H"
-#include "OFstream.H"
-#include "PstreamCombineReduceOps.H"
-#include "HashOps.H"
-
-#include "fieldTypes.H"
-#include "volFields.H"
-#include "scalarIOField.H"
-#include "vectorIOField.H"
-
-// file-format/conversion
-#include "ensightCase.H"
-#include "ensightGeoFile.H"
-#include "ensightParts.H"
-#include "ensightOutputCloud.H"
-#include "ensightOutputVolField.H"
-#include "fvMeshSubsetProxy.H"
-
-// local files
-#include "readFields.H"
-#include "writeVolFields.H"
-#include "writeDimFields.H"
-
-#include "memInfo.H"
-
-using namespace Foam;
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-int main(int argc, char *argv[])
-{
-    argList::addNote
-    (
-        "Translate OpenFOAM data to Ensight format with an Ensight part"
-        " for each cellZone and patch."
-    );
-
-    // Enable -constant
-    // Probably don't need -withZero though, since the fields are vetted
-    // afterwards anyhow
-    timeSelector::addOptions(true, false); // constant(true), zero(false)
-    #include "addRegionOption.H"
-
-    argList::noParallel();
-    argList::addBoolOption
-    (
-        "ascii",
-        "Write in ASCII format instead of 'C Binary'"
-    );
-    argList::addOption
-    (
-        "index",
-        "start",
-        "Ignore the time index contained in the uniform/time file"
-        " and use simple indexing when creating files"
-    );
-    argList::addBoolOption
-    (
-        "no-lagrangian", // noLagrangian
-        "Suppress writing lagrangian positions and fields"
-    );
-    argList::addOptionCompat("no-lagrangian", {"noLagrangian", 1806});
-
-    argList::addBoolOption
-    (
-        "no-mesh", // noMesh
-        "Suppress writing the geometry."
-        " Can be useful for converting partial results for a static geometry"
-    );
-    argList::addOptionCompat("no-mesh", {"noMesh", 1806});
-
-    argList::addOption
-    (
-        "fields",
-        "wordRes",
-        "Specify single or multiple fields to write (all by default)\n"
-        "Eg, 'T' or '( \"U.*\" )'"
-    );
-
-    argList::addOption
-    (
-        "name",
-        "subdir",
-        "Sub-directory name for Ensight output (default: 'Ensight')"
-    );
-    argList::addOption
-    (
-        "width",
-        "n",
-        "Width of Ensight data subdir"
-    );
-
-    #include "setRootCase.H"
-
-    // Default to binary output, unless otherwise specified
-    const IOstream::streamFormat format =
-    (
-        args.found("ascii")
-      ? IOstream::ASCII
-      : IOstream::BINARY
-    );
-
-    cpuTime timer;
-    memInfo mem;
-    Info<< "Initial memory " << mem.update().size() << " kB" << endl;
-
-    #include "createTime.H"
-
-    instantList timeDirs = timeSelector::select0(runTime, args);
-
-    #include "createNamedMesh.H"
-
-    fileName regionPrefix; // Mesh instance (region0 gets filtered out)
-    if (regionName != polyMesh::defaultRegion)
-    {
-        regionPrefix = regionName;
-    }
-
-    //
-    // general (case) output options
-    //
-    ensightCase::options caseOpts(format);
-
-    caseOpts.width(args.get<label>("width", 8));
-    caseOpts.overwrite(false); // leave existing output directory
-
-    // Can also have separate directory for lagrangian
-    // caseOpts.separateCloud(true);
-
-    // Define sub-directory name to use for EnSight data.
-    // The path to the ensight directory is at case level only
-    // - For parallel cases, data only written from master
-    fileName ensightDir = args.get<word>("name", "Ensight");
-    if (!ensightDir.isAbsolute())
-    {
-        ensightDir = args.globalPath()/ensightDir;
-    }
-
-    //
-    // Open new ensight case file, initialize header etc.
-    //
-    ensightCase ensCase
-    (
-        ensightDir,
-        "Ensight",  // args.globalCaseName(),
-        caseOpts
-    );
-
-
-    //
-    // Output configuration
-    //
-
-    // Control for renumbering iterations
-    label indexingNumber = 0;
-    const bool optIndex = args.readIfPresent("index", indexingNumber);
-    const bool doLagrangian = !args.found("no-lagrangian");
-
-    // Write the geometry, unless otherwise specified
-    bool doGeometry = !args.found("no-mesh");
-
-    //
-    // Output configuration (field related)
-    //
-
-    wordRes fieldPatterns;
-    args.readListIfPresent<wordRe>("fields", fieldPatterns);
-
-
-    // Construct the list of ensight parts for the entire mesh
-    ensightParts ensParts(mesh);
-
-    // Write summary information
-    if (Pstream::master())
-    {
-        Info<< "Converting " << timeDirs.size() << " time steps" << endl;
-
-        OFstream info(ensCase.path()/"partsInfo");
-
-        info
-            << "// summary of ensight parts" << nl << nl;
-        ensParts.writeSummary(info);
-    }
-
-    #include "checkMeshMoving.H"
-    #include "findCloudFields.H"
-
-    Info<< "Startup in "
-        << timer.cpuTimeIncrement() << " s, "
-        << mem.update().size() << " kB" << nl << endl;
-
-
-    // Initially all possible objects that are available at the final time
-    wordHashSet testedObjectNames;
-    {
-        IOobjectList objects(mesh, timeDirs.last().name());
-
-        if (!fieldPatterns.empty())
-        {
-            objects.filterObjects(fieldPatterns);
-        }
-
-        // Remove "*_0" restart fields
-        objects.prune_0();
-
-        // Only retain volume and dimensioned fields.
-        objects.filterClasses
-        (
-            [](const word& clsName){
-                return
-                (
-                    fieldTypes::volume.found(clsName)
-                 || fieldTypes::internal.found(clsName)
-                );
-            }
-        );
-
-        wordList objectNames(objects.sortedNames());
-
-        // Check availability for all times...
-        checkData(mesh, timeDirs, objectNames);
-
-        testedObjectNames = objectNames;
-    }
-
-    if (meshMoving && !doGeometry)
-    {
-        Info<< "mesh is moving: ignoring '-no-mesh' option" << endl;
-        doGeometry = true;
-    }
-
-
-    forAll(timeDirs, timeI)
-    {
-        runTime.setTime(timeDirs[timeI], timeI);
-
-        #include "getTimeIndex.H"
-        #include "moveMesh.H"
-
-        ensCase.setTime(timeDirs[timeI], timeIndex);
-
-        Info<< "Time [" << timeIndex << "] = " << runTime.timeName() << nl;
-
-        if (timeI == 0 || mesh.moving())
-        {
-            if (mesh.moving())
-            {
-                ensParts.recalculate(mesh);
-            }
-
-            if (doGeometry)
-            {
-                autoPtr<ensightGeoFile> os = ensCase.newGeometry(meshMoving);
-                ensParts.write(os.ref());
-            }
-        }
-
-        // Objects at this time
-        IOobjectList objects(mesh, runTime.timeName());
-
-        // Restrict to objects that are available for all times
-        objects.filterObjects(testedObjectNames);
-
-        // Volume, internal fields
-        #include "convertVolumeFields.H"
-
-        // Lagrangian fields
-        #include "convertLagrangian.H"
-
-        Info<< "Wrote in "
-            << timer.cpuTimeIncrement() << " s, "
-            << mem.update().size() << " kB" << endl;
-    }
-
-    ensCase.write();
-
-    Info<< "\nEnd: "
-        << timer.elapsedCpuTime() << " s, "
-        << mem.update().peak() << " kB (peak)\n" << endl;
-
-    return 0;
-}
-
-
-// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/moveMesh.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/moveMesh.H
deleted file mode 100644
index 8e3f53ce966..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/moveMesh.H
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    IOobject io
-    (
-        "points",
-        runTime.timeName(),
-        polyMesh::meshSubDir,
-        mesh,
-        IOobject::NO_READ,
-        IOobject::NO_WRITE,
-        false  // no register
-    );
-
-    if (io.typeHeaderOk<pointIOField>(true, false))
-    {
-        // Read new points
-        io.readOpt() = IOobject::MUST_READ;
-        mesh.movePoints(pointIOField(io));
-    }
-}
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.H
deleted file mode 100644
index fe8288bd1fc..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.H
+++ /dev/null
@@ -1,101 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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/>.
-
-InNamespace
-    Foam
-
-Description
-    Read fields from disk for foamToEnsight
-
-SourceFiles
-    readFields.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef readFields_H
-#define readFields_H
-
-#include "instantList.H"
-#include "IOobjectList.H"
-#include "fvMesh.H"
-#include "fvMeshSubsetProxy.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-//- Get the field and subset it, or return nullptr
-template<class GeoField>
-tmp<GeoField> getField(const IOobject* io, const fvMesh& mesh)
-{
-    if (io)
-    {
-        auto tfield = tmp<GeoField>::New(*io, mesh);
-        return tfield;
-    }
-
-    return nullptr;
-}
-
-
-//- Get internal field and make it a zero-gradient volume field with subsetting
-template<class GeoField>
-tmp<GeoField>
-getZeroGradField(const IOobject* io, const fvMesh& mesh)
-{
-    if (io)
-    {
-        auto tdimfield =
-            tmp<typename GeoField::Internal>::New(*io, mesh);
-
-        auto tfield = fvMeshSubsetProxy::zeroGradientField(tdimfield());
-        tdimfield.clear();
-
-        return tfield;
-    }
-
-    return nullptr;
-}
-
-
-//- Check if fields are good to use (available at all times)
-//  ignore special fields (_0 fields),
-//  ignore fields that are not available for all time-steps
-label checkData
-(
-    const fvMesh& mesh,
-    const instantList& timeDirs,
-    wordList& objectNames
-);
-
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeDimFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeDimFields.H
deleted file mode 100644
index cc3fa5f7307..00000000000
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeDimFields.H
+++ /dev/null
@@ -1,125 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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/>.
-
-InNamespace
-    Foam
-
-Description
-    Read dimensioned fields from disk and write with ensightParts
-
-SourceFiles
-    writeDimFields.H
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightParts_writeDimFields_H
-#define ensightParts_writeDimFields_H
-
-#include "writeVolFields.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-template<class Type>
-label writeDimFields
-(
-    ensightCase& ensCase,
-    const ensightParts& ensParts,
-    const fvMesh& mesh,
-    const IOobjectList& objects
-)
-{
-    typedef GeometricField<Type, fvPatchField, volMesh> GeoField;
-
-    typedef typename
-        GeometricField
-        <
-            Type, fvPatchField, volMesh
-        >::Internal DimField;
-
-
-    label count = 0;
-
-    for (const word& fieldName : objects.sortedNames<DimField>())
-    {
-        const IOobject* io = objects.findObject(fieldName);
-
-        if
-        (
-            writeVolField<Type>
-            (
-                ensCase,
-                ensParts,
-                mesh,
-                getZeroGradField<GeoField>(io, mesh)
-            )
-        )
-        {
-            Info<< ' ' << fieldName;
-            ++count;
-        }
-    }
-
-    return count;
-}
-
-
-label writeAllDimFields
-(
-    ensightCase& ensCase,
-    const ensightParts& ensParts,
-    const fvMesh& mesh,
-    const IOobjectList& objects
-)
-{
-    #undef  foamToEnsight_WRITE_FIELD
-    #define foamToEnsight_WRITE_FIELD(PrimitiveType)    \
-        writeDimFields<PrimitiveType>                   \
-        (                                               \
-            ensCase, ensParts,                          \
-            mesh,                                       \
-            objects                                     \
-        )
-
-    label count = 0;
-    count += foamToEnsight_WRITE_FIELD(scalar);
-    count += foamToEnsight_WRITE_FIELD(vector);
-    count += foamToEnsight_WRITE_FIELD(sphericalTensor);
-    count += foamToEnsight_WRITE_FIELD(symmTensor);
-    count += foamToEnsight_WRITE_FIELD(tensor);
-
-    #undef foamToEnsight_WRITE_FIELD
-    return count;
-}
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/conversion/Make/files b/src/conversion/Make/files
index 4cf7b300a08..a00ca056813 100644
--- a/src/conversion/Make/files
+++ b/src/conversion/Make/files
@@ -7,10 +7,6 @@ common/writer/meshWriter.C
 common/tables/boundaryRegion.C
 common/tables/cellTable.C
 
-ensight/mesh/ensightMesh.C
-ensight/mesh/ensightMeshIO.C
-ensight/mesh/ensightMeshOptions.C
-
 fire/FIREMeshReader.C
 fire/FIREMeshWriter.C
 fire/checkFireEdges.C
diff --git a/src/conversion/ensight/mesh/ensightMesh.C b/src/conversion/ensight/mesh/ensightMesh.C
deleted file mode 100644
index 0b9dd7a4c2f..00000000000
--- a/src/conversion/ensight/mesh/ensightMesh.C
+++ /dev/null
@@ -1,404 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "ensightMesh.H"
-#include "fvMesh.H"
-#include "globalMeshData.H"
-#include "PstreamCombineReduceOps.H"
-#include "emptyPolyPatch.H"
-#include "processorPolyPatch.H"
-#include "mapDistribute.H"
-#include "stringListOps.H"
-
-#include "ensightFile.H"
-#include "ensightGeoFile.H"
-#include "demandDrivenData.H"
-
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
-
-void Foam::ensightMesh::clear()
-{
-    meshCells_.clear();
-    boundaryPatchFaces_.clear();
-    faceZoneFaces_.clear();
-    patchLookup_.clear();
-    globalPointsPtr_.clear();
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::ensightMesh::ensightMesh
-(
-    const fvMesh& mesh,
-    const ensightMesh::options& opts
-)
-:
-    options_(new options(opts)),
-    mesh_(mesh),
-    needsUpdate_(true)
-{
-    if (!option().lazy())
-    {
-        correct();
-    }
-}
-
-
-Foam::ensightMesh::ensightMesh(const fvMesh& mesh)
-:
-    ensightMesh(mesh, IOstream::streamFormat::BINARY)
-{}
-
-
-Foam::ensightMesh::ensightMesh
-(
-    const fvMesh& mesh,
-    const IOstream::streamFormat format
-)
-:
-    options_(new options(format)),
-    mesh_(mesh),
-    needsUpdate_(true)
-{
-    if (!option().lazy())
-    {
-        correct();
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::ensightMesh::~ensightMesh()
-{
-    deleteDemandDrivenData(options_);
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-bool Foam::ensightMesh::needsUpdate() const
-{
-    return needsUpdate_;
-}
-
-
-bool Foam::ensightMesh::expire()
-{
-    clear();
-
-    // Already marked as expired
-    if (needsUpdate_)
-    {
-        return false;
-    }
-
-    needsUpdate_ = true;
-    return true;
-}
-
-
-void Foam::ensightMesh::correct()
-{
-    clear();
-
-    // Part number
-    label nParts = 0;
-
-    if (useInternalMesh())
-    {
-        meshCells_.index() = nParts++;
-        meshCells_.classify(mesh_);
-
-        // Determine parallel shared points
-        globalPointsPtr_ = mesh_.globalData().mergePoints
-        (
-            pointToGlobal_,
-            uniquePointMap_
-        );
-    }
-    meshCells_.reduce();
-
-
-    if (useBoundaryMesh())
-    {
-        // Patches are output. Check that they are synced.
-        mesh_.boundaryMesh().checkParallelSync(true);
-
-        wordList patchNames = mesh_.boundaryMesh().names();
-        if (Pstream::parRun())
-        {
-            // Do not include processor patches in matching
-            patchNames.resize(mesh_.boundaryMesh().nNonProcessor());
-        }
-
-        const wordRes& matcher = option().patchSelection();
-
-        const labelList patchIds =
-        (
-            matcher.empty()
-          ? identity(patchNames.size())         // Use all
-          : findStrings(matcher, patchNames)    // Use specified names
-        );
-
-        for (const label patchId : patchIds)
-        {
-            const word& patchName = patchNames[patchId];
-
-            // Use fvPatch (not polyPatch) to automatically remove empty patches
-            const fvPatch& p = mesh_.boundary()[patchId];
-
-            ensightFaces& ensFaces = boundaryPatchFaces_(patchName);
-            ensFaces.clear();
-
-            if (p.size())
-            {
-                // Local face addressing (offset = 0),
-                // - this is what we'll need later when writing fields
-                ensFaces.classify(p.patch());
-            }
-            else
-            {
-                // The patch is empty (on this processor)
-                // or the patch is 'empty' (as fvPatch type)
-                ensFaces.clear();
-            }
-
-            // Finalize
-            ensFaces.reduce();
-
-            if (ensFaces.total())
-            {
-                patchLookup_.set(patchId, patchName);
-                ensFaces.index() = nParts++;
-            }
-            else
-            {
-                boundaryPatchFaces_.erase(patchName);
-            }
-        }
-
-        // At this point,
-        // * patchLookup_        is a map of (patchId, name)
-        // * boundaryPatchFaces_ is a lookup by name for the faces elements
-    }
-
-
-    if (option().useFaceZones())
-    {
-        // Mark boundary faces to be excluded from export
-        bitSet excludeFace(mesh_.nFaces());
-
-        for (const polyPatch& pp : mesh_.boundaryMesh())
-        {
-            const auto* procPatch = isA<processorPolyPatch>(pp);
-
-            if (isA<emptyPolyPatch>(pp))
-            {
-                excludeFace.set(pp.range());
-            }
-            else if (procPatch && !procPatch->owner())
-            {
-                // Exclude neighbour-side, retain owner-side only
-                excludeFace.set(pp.range());
-            }
-        }
-
-        // Use sorted order for later consistency
-        const wordList zoneNames =
-            mesh_.faceZones().sortedNames(option().faceZoneSelection());
-
-        // Count face types in each selected faceZone
-        for (const word& zoneName : zoneNames)
-        {
-            const label zoneID = mesh_.faceZones().findZoneID(zoneName);
-            const faceZone& fz = mesh_.faceZones()[zoneID];
-
-            ensightFaces& ensFaces = faceZoneFaces_(zoneName);
-            ensFaces.clear();
-
-            if (fz.size())
-            {
-                ensFaces.classify
-                (
-                    mesh_.faces(),
-                    fz,
-                    fz.flipMap(),
-                    excludeFace
-                );
-            }
-
-            // Finalize
-            ensFaces.reduce();
-
-            if (ensFaces.total())
-            {
-                ensFaces.index() = nParts++;
-            }
-            else
-            {
-                faceZoneFaces_.erase(zoneName);
-            }
-        }
-    }
-
-    needsUpdate_ = false;
-}
-
-
-void Foam::ensightMesh::write(ensightGeoFile& os) const
-{
-    //
-    // Write internalMesh
-    //
-    if (useInternalMesh())
-    {
-        const label nPoints = globalPoints().size();
-
-        const pointField uniquePoints(mesh_.points(), uniquePointMap_);
-
-        // writePartHeader(os, 0, "internalMesh");
-        // beginCoordinates(os, nPoints);
-        writeAllPoints
-        (
-            meshCells_.index(),
-            "internalMesh",
-            nPoints,
-            uniquePoints,
-            os
-        );
-
-        writeCellConnectivity(meshCells_, pointToGlobal_, os);
-    }
-
-
-    //
-    // Write patches - sorted by Id
-    //
-    for (const label patchId : patchLookup_.sortedToc())
-    {
-        const word& patchName = patchLookup_[patchId];
-        const ensightFaces& ensFaces = boundaryPatchFaces_[patchName];
-
-        const polyPatch& pp = mesh_.boundaryMesh()[patchId];
-
-        // Renumber the patch points/faces into unique points
-        labelList pointToGlobal;
-        labelList uniqueMeshPointLabels;
-        autoPtr<globalIndex> globalPointsPtr =
-            mesh_.globalData().mergePoints
-            (
-                pp.meshPoints(),
-                pp.meshPointMap(),
-                pointToGlobal, // local point to unique global index
-                uniqueMeshPointLabels // unique global points
-            );
-
-        // Renumber the patch faces,
-        // from local patch indexing to unique global index
-        faceList patchFaces(pp.localFaces());
-        for (face& f : patchFaces)
-        {
-            inplaceRenumber(pointToGlobal, f);
-        }
-
-        writeAllPoints
-        (
-            ensFaces.index(),
-            patchName,
-            globalPointsPtr().size(),
-            pointField(mesh_.points(), uniqueMeshPointLabels),
-            os
-        );
-
-        writeFaceConnectivity(ensFaces, patchFaces, os);
-    }
-
-
-    //
-    // Write faceZones, if requested
-    //
-    for (const word& zoneName : faceZoneFaces_.sortedToc())
-    {
-        const ensightFaces& ensFaces = faceZoneFaces_[zoneName];
-
-        // Use the properly sorted faceIds (ensightFaces) and do NOT use the
-        // faceZone directly, otherwise the point-maps will not correspond.
-        // - perform face-flipping later
-
-        indirectPrimitivePatch pp
-        (
-            IndirectList<face>(mesh_.faces(), ensFaces.faceIds()),
-            mesh_.points()
-        );
-
-        // Renumber the points/faces into unique points
-        labelList pointToGlobal;
-        labelList uniqueMeshPointLabels;
-        autoPtr<globalIndex> globalPointsPtr =
-            mesh_.globalData().mergePoints
-            (
-                pp.meshPoints(),
-                pp.meshPointMap(),
-                pointToGlobal, // local point to unique global index
-                uniqueMeshPointLabels // unique global points
-            );
-
-        // Renumber the faces belonging to the faceZone,
-        // from local numbering to unique global index.
-        // Also a good place to perform face flipping
-        const boolList& flip = ensFaces.flipMap();
-        faceList patchFaces(pp.localFaces());
-        forAll(patchFaces, facei)
-        {
-            face& f = patchFaces[facei];
-
-            if (flip[facei])
-            {
-                f.flip();
-            }
-
-            inplaceRenumber(pointToGlobal, f);
-        }
-
-        writeAllPoints
-        (
-            ensFaces.index(),
-            zoneName,
-            globalPointsPtr().size(),
-            pointField(mesh_.points(), uniqueMeshPointLabels),
-            os
-        );
-
-        writeFaceConnectivity(ensFaces, patchFaces, os, true);
-    }
-}
-
-
-// ************************************************************************* //
diff --git a/src/conversion/ensight/mesh/ensightMesh.H b/src/conversion/ensight/mesh/ensightMesh.H
deleted file mode 100644
index fe0b4f25dbd..00000000000
--- a/src/conversion/ensight/mesh/ensightMesh.H
+++ /dev/null
@@ -1,503 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::ensightMesh
-
-Description
-    Encapsulation of volume meshes for writing in ensight format.
-
-SourceFiles
-    ensightMesh.C
-    ensightMeshIO.C
-    ensightMeshOptions.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightMesh_H
-#define ensightMesh_H
-
-#include "ensightCells.H"
-#include "ensightFaces.H"
-#include "ensightGeoFile.H"
-#include "cellList.H"
-#include "faceList.H"
-#include "cellShapeList.H"
-#include "HashTable.H"
-#include "Map.H"
-#include "scalarField.H"
-#include "wordRes.H"
-#include "globalIndex.H"
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-// Forward declarations
-class fvMesh;
-class ensightMesh;
-
-/*---------------------------------------------------------------------------*\
-                         Class ensightMesh Declaration
-\*---------------------------------------------------------------------------*/
-
-class ensightMesh
-{
-public:
-
-    // Forward declarations
-    class options;
-
-
-private:
-
-    // Private data
-
-        //- Writer options
-        const options* options_;
-
-        //- Reference to the OpenFOAM mesh
-        const fvMesh& mesh_;
-
-        //- The volume cells (internalMesh)
-        ensightCells meshCells_;
-
-        //- Face elements per patch
-        HashTable<ensightFaces> boundaryPatchFaces_;
-
-        //- Face elements per faceZone
-        HashTable<ensightFaces> faceZoneFaces_;
-
-        //- The list of patches to be output
-        Map<word> patchLookup_;
-
-        //- Track if it needs an update
-        mutable bool needsUpdate_;
-
-
-        // Parallel merged points
-
-        //- Global numbering for merged points
-        autoPtr<globalIndex> globalPointsPtr_;
-
-        //- From mesh point to global merged point
-        labelList pointToGlobal_;
-
-        //- Local points that are unique
-        labelList uniquePointMap_;
-
-
-    // Private Member Functions
-
-        //- Clear some storage
-        void clear();
-
-
-        //- Inplace renumber of cell-shapes
-        static cellShapeList& renumberShapes
-        (
-            cellShapeList& shapes,
-            const labelUList& pointToGlobal
-        );
-
-        //- Copy and return renumbered cell-shapes
-        static cellShapeList renumberShapes
-        (
-            const cellShapeList& shapes,
-            const labelUList& addr,
-            const labelUList& pointToGlobal
-        );
-
-        //- Write list of faces
-        static void writeFaceList
-        (
-            const faceList& faces,
-            ensightGeoFile& os
-        );
-
-        //- Write list of faces
-        static void writeFaceList
-        (
-            const UIndirectList<face>& faces,
-            ensightGeoFile& os
-        );
-
-        //- Return sizes of faces in the list
-        static labelList getFaceSizes
-        (
-            const faceList& faces
-        );
-
-        //- Return sizes of faces in the list
-        static labelList getFaceSizes
-        (
-            const UIndirectList<face>& faces
-        );
-
-        //- Write sizes of faces in the list
-        static void writeFaceSizes
-        (
-            const faceList& faces,
-            ensightGeoFile& os
-        );
-
-        //- Write sizes of faces in the list
-        static void writeFaceSizes
-        (
-            const UIndirectList<face>& faces,
-            ensightGeoFile& os
-        );
-
-        //- Write cell connectivity via cell shapes
-        static void writeCellShapes
-        (
-            const cellShapeList& shapes,
-            ensightGeoFile& os
-        );
-
-        //- Return the number of faces per poly element
-        static labelList getPolysNFaces
-        (
-            const labelUList& polys,
-            const cellList& cellFaces
-        );
-
-        //- Write the number of faces per poly element
-        static void writePolysNFaces
-        (
-            const labelUList& polys,
-            const cellList& cellFaces,
-            ensightGeoFile& os
-        );
-
-        //- Return the number of points per poly element
-        static labelList getPolysNPointsPerFace
-        (
-            const labelUList& polys,
-            const cellList& cellFaces,
-            const faceList& faces
-        );
-
-        //- Write the number of points per poly element
-        static void writePolysNPointsPerFace
-        (
-            const labelUList& polys,
-            const cellList& cellFaces,
-            const faceList& faces,
-            ensightGeoFile& os
-        );
-
-        //- Write the point ids per poly element
-        static void writePolysPoints
-        (
-            const labelUList& addr,
-            const cellList& cellFaces,
-            const faceList& faces,
-            const labelList& faceOwner,
-            ensightGeoFile& os
-        );
-
-        //- Write the poly connectivity
-        void writePolysConnectivity
-        (
-            const labelUList& polys,
-            const labelList& pointToGlobal,
-            ensightGeoFile&
-        ) const;
-
-        //- Write the regular cell connectivity for all types
-        void writeCellConnectivity
-        (
-            const ensightCells& ensCells,
-            const labelList& pointToGlobal,
-            ensightGeoFile& os
-        ) const;
-
-        //- Write the regular cell connectivity for specified type
-        void writeCellConnectivity
-        (
-            ensightCells::elemType elemType,
-            const ensightCells& ensCells,
-            const labelList& pointToGlobal,
-            ensightGeoFile& os
-        ) const;
-
-        //- Write the regular face connectivity for specified type and
-        //- and specified faces
-        void writeFaceConnectivity
-        (
-            ensightFaces::elemType elemType,
-            const label nTotal,
-            const faceList& faces,
-            const labelUList& addr,
-            ensightGeoFile&
-        ) const;
-
-        //- Write the regular face connectivity for specified type
-        void writeFaceConnectivity
-        (
-            ensightFaces::elemType elemType,
-            const label nTotal,
-            const faceList& faces,
-            ensightGeoFile& os
-        ) const;
-
-
-        void writeFaceConnectivity
-        (
-            const ensightFaces& ensFaces,
-            const faceList& faces,
-            ensightGeoFile& os,
-            const bool raw = false
-        ) const;
-
-
-        void writeAllPoints
-        (
-            const label partId,
-            const word& ensightPartName,
-            const label nTotal,
-            const pointField& uniquePoints,
-            ensightGeoFile&
-        ) const;
-
-
-        //- No copy construct
-        ensightMesh(const ensightMesh&) = delete;
-
-        //- No copy assignment
-        void operator=(const ensightMesh&) = delete;
-
-
-public:
-
-    // Constructors
-
-        //- Construct from components
-        ensightMesh(const fvMesh& mesh, const options& opts);
-
-        //- Construct from fvMesh with all default options, binary output
-        explicit ensightMesh(const fvMesh& mesh);
-
-        //- Construct from fvMesh with all default options and specified format
-        ensightMesh(const fvMesh& mesh, const IOstream::streamFormat format);
-
-
-    //- Destructor
-    ~ensightMesh();
-
-
-    // Member Functions
-
-    // Access
-
-        //- Reference to the underlying fvMesh
-        inline const fvMesh& mesh() const;
-
-        //- Reference to the writer/mesh options
-        inline const ensightMesh::options& option() const;
-
-        //- Ascii/Binary file output
-        inline IOstream::streamFormat format() const;
-
-        //- Using internal?
-        inline bool useInternalMesh() const;
-
-        //- Using boundary?
-        inline bool useBoundaryMesh() const;
-
-        //- The volume cells (internalMesh)
-        inline const ensightCells& meshCells() const;
-
-        //- The list of patches to be output
-        inline const Map<word>& patches() const;
-
-        //- Face elements per selected patch
-        inline const HashTable<ensightFaces>& boundaryPatchFaces() const;
-
-        //- Face elements per selected faceZone.
-        //  To be output in sorted order.
-        inline const HashTable<ensightFaces>& faceZoneFaces() const;
-
-
-    // Parallel point merging
-
-        //- Global numbering for merged points
-        const globalIndex& globalPoints() const
-        {
-            return globalPointsPtr_();
-        }
-
-        //- From mesh point to global merged point
-        const labelList& pointToGlobal() const
-        {
-            return pointToGlobal_;
-        }
-
-        //- Local points that are unique
-        const labelList& uniquePointMap() const
-        {
-            return uniquePointMap_;
-        }
-
-
-    // Other
-
-        //- Does the content need an update?
-        bool needsUpdate() const;
-
-        //- Mark as needing an update.
-        //  May also free up unneeded data.
-        //  Return false if already marked as expired.
-        bool expire();
-
-        //- Update for new mesh
-        void correct();
-
-
-    // Output
-
-        //- Write to file
-        inline void write(autoPtr<ensightGeoFile>& os) const;
-
-        //- Write to file
-        void write(ensightGeoFile& os) const;
-
-};
-
-
-//- Configuration options for the ensightMesh
-class ensightMesh::options
-{
-    //- Ascii/Binary file output
-    IOstream::streamFormat format_;
-
-    //- Create in 'expired' mode
-    bool lazy_;
-
-    //- Use the internal mesh
-    bool internal_;
-
-    //- Use the boundary mesh
-    bool boundary_;
-
-    //- Output of selected patches only
-    wordRes patchPatterns_;
-
-    //- Output of selected faceZones
-    wordRes faceZonePatterns_;
-
-
-public:
-
-    // Constructors
-
-        //- Construct for binary output
-        options();
-
-        //- Construct for specified format
-        explicit options(IOstream::streamFormat format);
-
-
-    // Member Functions
-
-    // Access
-
-        //- File output format (ascii | binary)
-        IOstream::streamFormat format() const;
-
-        //- Lazy creation? (ie, ensightMesh starts as needsUpdate)
-        bool lazy() const;
-
-        //- Using internal?
-        bool useInternalMesh() const;
-
-        //- Using boundary?
-        bool useBoundaryMesh() const;
-
-        //- Using faceZones?
-        bool useFaceZones() const;
-
-        //- Selection of patches. Empty if unspecified.
-        const wordRes& patchSelection() const;
-
-        //- Selection of faceZones. Empty if unspecified.
-        const wordRes& faceZoneSelection() const;
-
-
-    // Edit
-
-        //- Reset to defaults
-        void reset();
-
-        //- Lazy creation - ensightMesh starts as needsUpdate.
-        void lazy(bool beLazy);
-
-        //- Alter the useBoundaryMesh state
-        void useInternalMesh(bool on);
-
-        //- Alter the useBoundaryMesh state
-        void useBoundaryMesh(bool on);
-
-        //- Define patch selection matcher
-        void patchSelection(const UList<wordRe>& patterns);
-
-        //- Define patch selection matcher
-        void patchSelection(List<wordRe>&& patterns);
-
-        //- Define faceZone selection matcher
-        void faceZoneSelection(const UList<wordRe>& patterns);
-
-        //- Define faceZone selection matcher
-        void faceZoneSelection(List<wordRe>&& patterns);
-
-
-    // Housekeeping
-
-        //- Older name for useBoundaryMesh()
-        //  \deprecated OCT-2018
-        bool usePatches() const { return useBoundaryMesh(); }
-
-        //- Older name for useBoundaryMesh()
-        //  \deprecated OCT-2018
-        void noPatches(bool off) { useBoundaryMesh(!off); }
-
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#include "ensightMeshI.H"
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/conversion/ensight/mesh/ensightMeshIO.C b/src/conversion/ensight/mesh/ensightMeshIO.C
deleted file mode 100644
index 4e3bc534041..00000000000
--- a/src/conversion/ensight/mesh/ensightMeshIO.C
+++ /dev/null
@@ -1,794 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "ensightMesh.H"
-#include "fvMesh.H"
-#include "globalMeshData.H"
-#include "PstreamCombineReduceOps.H"
-#include "processorPolyPatch.H"
-#include "mapDistribute.H"
-#include "stringListOps.H"
-
-#include "ensightFile.H"
-#include "ensightGeoFile.H"
-
-// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
-
-Foam::cellShapeList& Foam::ensightMesh::renumberShapes
-(
-    cellShapeList& shapes,
-    const labelUList& pointToGlobal
-)
-{
-    for (cellShape& shape : shapes)
-    {
-        inplaceRenumber(pointToGlobal, shape);
-    }
-
-    return shapes;
-}
-
-
-Foam::cellShapeList Foam::ensightMesh::renumberShapes
-(
-    const cellShapeList& shapes,
-    const labelUList& addr,
-    const labelUList& pointToGlobal
-)
-{
-    cellShapeList list(shapes, addr);
-
-    renumberShapes(list, pointToGlobal);
-
-    return list;
-}
-
-
-void Foam::ensightMesh::writeFaceList
-(
-    const faceList& faceLst,
-    ensightGeoFile& os
-)
-{
-    for (const face& f : faceLst)
-    {
-        for (const label labi : f)
-        {
-            os.write(labi + 1);
-        }
-
-        os.newline();
-    }
-}
-
-
-void Foam::ensightMesh::writeFaceList
-(
-    const UIndirectList<face>& faceLst,
-    ensightGeoFile& os
-)
-{
-    for (const face& f : faceLst)
-    {
-        for (const label labi : f)
-        {
-            os.write(labi + 1);
-        }
-
-        os.newline();
-    }
-}
-
-
-Foam::labelList Foam::ensightMesh::getFaceSizes
-(
-    const faceList& faceLst
-)
-{
-    labelList list(faceLst.size());
-
-    auto outIter = list.begin();
-
-    for (const face& f : faceLst)
-    {
-        *outIter = f.size();
-        ++outIter;
-    }
-
-    return list;
-}
-
-
-Foam::labelList Foam::ensightMesh::getFaceSizes
-(
-    const UIndirectList<face>& faceLst
-)
-{
-    labelList list(faceLst.size());
-
-    auto outIter = list.begin();
-
-    for (const face& f : faceLst)
-    {
-        *outIter = f.size();
-        ++outIter;
-    }
-
-    return list;
-}
-
-
-void Foam::ensightMesh::writeFaceSizes
-(
-    const faceList& faceLst,
-    ensightGeoFile& os
-)
-{
-    for (const face& f : faceLst)
-    {
-        os.write(f.size());
-        os.newline();
-    }
-}
-
-
-void Foam::ensightMesh::writeFaceSizes
-(
-    const UIndirectList<face>& faceLst,
-    ensightGeoFile& os
-)
-{
-    for (const face& f : faceLst)
-    {
-        os.write(f.size());
-        os.newline();
-    }
-}
-
-
-void Foam::ensightMesh::writeCellShapes
-(
-    const cellShapeList& shapes,
-    ensightGeoFile& os
-)
-{
-    for (const cellShape& cellPoints : shapes)
-    {
-        // Convert global -> local index
-        // (note: Ensight indices start with 1)
-
-        // In ASCII, write one cell per line
-        for (const label pointi : cellPoints)
-        {
-            os.write(pointi + 1);
-        }
-
-        os.newline();
-    }
-}
-
-
-Foam::labelList Foam::ensightMesh::getPolysNFaces
-(
-    const labelUList& addr,
-    const cellList& cellFaces
-)
-{
-    labelList list(addr.size());
-
-    auto outIter = list.begin();
-
-    // The number of faces per element
-    for (const label cellId : addr)
-    {
-        const labelUList& cf = cellFaces[cellId];
-
-        *outIter = cf.size();
-        ++outIter;
-    }
-
-    return list;
-}
-
-
-void Foam::ensightMesh::writePolysNFaces
-(
-    const labelUList& addr,
-    const cellList& cellFaces,
-    ensightGeoFile& os
-)
-{
-    // Write the number of faces per element (1/line in ASCII)
-    for (const label cellId : addr)
-    {
-        const labelUList& cf = cellFaces[cellId];
-
-        os.write(cf.size());
-        os.newline();
-    }
-}
-
-
-Foam::labelList Foam::ensightMesh::getPolysNPointsPerFace
-(
-    const labelUList& addr,
-    const cellList& cellFaces,
-    const faceList& faces
-)
-{
-    // Count the number of faces per element
-
-    label nTotFaces = 0;
-    for (const label cellId : addr)
-    {
-        const labelUList& cf = cellFaces[cellId];
-
-        nTotFaces += cf.size();
-    }
-
-    labelList list(nTotFaces);
-
-    auto outIter = list.begin();
-
-    // The number of points per element face
-    for (const label cellId : addr)
-    {
-        const labelUList& cf = cellFaces[cellId];
-
-        for (const label facei : cf)
-        {
-            *outIter = faces[facei].size();
-            ++outIter;
-        }
-    }
-
-    return list;
-}
-
-
-void Foam::ensightMesh::writePolysNPointsPerFace
-(
-    const labelUList& addr,
-    const cellList& cellFaces,
-    const faceList& faces,
-    ensightGeoFile& os
-)
-{
-    // Write the number of points per element face (1/line in ASCII)
-    for (const label cellId : addr)
-    {
-        const labelUList& cf = cellFaces[cellId];
-
-        for (const label facei : cf)
-        {
-            os.write(faces[facei].size());
-            os.newline();
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::ensightMesh::writePolysPoints
-(
-    const labelUList& addr,
-    const cellList& cellFaces,
-    const faceList& faces,
-    const labelList& faceOwner,
-    ensightGeoFile& os
-)
-{
-    for (const label cellId : addr)
-    {
-        const labelUList& cf = cellFaces[cellId];
-
-        for (const label faceId : cf)
-        {
-            const face& f = faces[faceId];  // face points (in global points)
-
-            if (faceId < faceOwner.size() && faceOwner[faceId] != cellId)
-            {
-                // internal face, neighbour
-                //
-                // as per face::reverseFace(), but without copying
-
-                os.write(f[0] + 1);
-                for (label pti = f.size()-1; pti > 0; --pti)
-                {
-                    os.write(f[pti] + 1);
-                }
-            }
-            else
-            {
-                for (const label labi : f)
-                {
-                    os.write(labi + 1);
-                }
-            }
-
-            os.newline();
-        }
-    }
-}
-
-
-void Foam::ensightMesh::writePolysConnectivity
-(
-    const labelUList& addr,
-    const labelList& pointToGlobal,
-    ensightGeoFile& os
-) const
-{
-    const cellList&  cellFaces = mesh_.cells();
-    const faceList&  meshFaces = mesh_.faces();
-    const labelList& faceOwner = mesh_.faceOwner();
-
-    // Number of faces for each poly cell
-    if (Pstream::master())
-    {
-        // Master
-        writePolysNFaces(addr, cellFaces, os);
-
-        // Slaves
-        for (int slave=1; slave<Pstream::nProcs(); ++slave)
-        {
-            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-            labelList addr(fromSlave);
-            cellList  cellFaces(fromSlave);
-
-            writePolysNFaces(addr, cellFaces, os);
-        }
-    }
-    else
-    {
-        OPstream toMaster(Pstream::commsTypes::scheduled, Pstream::masterNo());
-        toMaster
-            << addr
-            << cellFaces;
-    }
-
-    // Number of points for each face of the above list
-    if (Pstream::master())
-    {
-        // Master
-        writePolysNPointsPerFace
-        (
-            addr,
-            cellFaces,
-            meshFaces,
-            os
-        );
-
-        // Slaves
-        for (int slave=1; slave<Pstream::nProcs(); ++slave)
-        {
-            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-            labelList addr(fromSlave);
-            cellList  cellFaces(fromSlave);
-            faceList  meshFaces(fromSlave);
-
-            writePolysNPointsPerFace
-            (
-                addr,
-                cellFaces,
-                meshFaces,
-                os
-            );
-        }
-    }
-    else
-    {
-        OPstream toMaster(Pstream::commsTypes::scheduled, Pstream::masterNo());
-        toMaster
-            << addr
-            << cellFaces
-            << meshFaces;
-    }
-
-
-    // Renumber faces to use global point numbers
-    faceList faces(mesh_.faces());
-    for (face& f : faces)
-    {
-        inplaceRenumber(pointToGlobal, f);
-    }
-
-    // List of points id for each face of the above list
-    if (Pstream::master())
-    {
-        // Master
-        writePolysPoints
-        (
-            addr,
-            cellFaces,
-            faces,
-            faceOwner,
-            os
-        );
-
-        // Slaves
-        for (int slave=1; slave<Pstream::nProcs(); ++slave)
-        {
-            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-            labelList addr(fromSlave);
-            cellList  cellFaces(fromSlave);
-            faceList  faces(fromSlave);
-            labelList faceOwner(fromSlave);
-
-            writePolysPoints
-            (
-                addr,
-                cellFaces,
-                faces,
-                faceOwner,
-                os
-            );
-        }
-    }
-    else
-    {
-        OPstream toMaster(Pstream::commsTypes::scheduled, Pstream::masterNo());
-        toMaster
-            << addr
-            << cellFaces
-            << faces
-            << faceOwner;
-    }
-}
-
-
-void Foam::ensightMesh::writeCellConnectivity
-(
-    const ensightCells::elemType elemType,
-    const ensightCells& ensCells,
-    const labelList& pointToGlobal,
-    ensightGeoFile& os
-) const
-{
-    const label nTotal = ensCells.total(elemType);
-
-    if (nTotal)
-    {
-        const labelUList& addr = ensCells.cellIds(elemType);
-
-        if (Pstream::master())
-        {
-            os.writeKeyword(ensightCells::key(elemType));
-            os.write(nTotal);
-            os.newline();
-        }
-
-        if (elemType == ensightCells::NFACED)
-        {
-            writePolysConnectivity
-            (
-                addr,
-                pointToGlobal,
-                os
-            );
-        }
-        else
-        {
-            const cellShapeList shapes
-            (
-                renumberShapes
-                (
-                    mesh_.cellShapes(),
-                    addr,
-                    pointToGlobal
-                )
-            );
-
-
-            if (Pstream::master())
-            {
-                writeCellShapes(shapes, os);
-
-                for (int slave=1; slave<Pstream::nProcs(); ++slave)
-                {
-                    IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                    cellShapeList recv(fromSlave);
-
-                    writeCellShapes(recv, os);
-                }
-            }
-            else
-            {
-                OPstream toMaster
-                (
-                    Pstream::commsTypes::scheduled,
-                    Pstream::masterNo()
-                );
-
-                toMaster
-                    << shapes;
-            }
-        }
-    }
-}
-
-
-void Foam::ensightMesh::writeCellConnectivity
-(
-    const ensightCells& ensCells,
-    const labelList& pointToGlobal,
-    ensightGeoFile& os
-) const
-{
-    for (label typei=0; typei < ensightCells::nTypes; ++typei)
-    {
-        const ensightCells::elemType what =
-            ensightCells::elemType(typei);
-
-        writeCellConnectivity(what, ensCells, pointToGlobal, os);
-    }
-}
-
-
-void Foam::ensightMesh::writeFaceConnectivity
-(
-    ensightFaces::elemType elemType,
-    const label nTotal,
-    const faceList& faces,
-    ensightGeoFile& os
-) const
-{
-    if (nTotal)
-    {
-        if (Pstream::master())
-        {
-            os.writeKeyword(ensightFaces::key(elemType));
-            os.write(nTotal);
-            os.newline();
-        }
-
-        if (elemType == ensightFaces::NSIDED)
-        {
-            // Number of points per face
-
-            if (Pstream::master())
-            {
-                writeFaceSizes(faces, os);
-
-                for (int slave=1; slave<Pstream::nProcs(); ++slave)
-                {
-                    IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                    faceList recv(fromSlave);
-
-                    writeFaceSizes(recv, os);
-                }
-            }
-            else
-            {
-                OPstream toMaster
-                (
-                    Pstream::commsTypes::scheduled,
-                    Pstream::masterNo()
-                );
-
-                toMaster
-                    << faces;
-            }
-        }
-
-
-        // List of points id for each face
-        if (Pstream::master())
-        {
-            writeFaceList(faces, os);
-
-            for (int slave=1; slave<Pstream::nProcs(); ++slave)
-            {
-                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                faceList recv(fromSlave);
-
-                writeFaceList(recv, os);
-            }
-        }
-        else
-        {
-            OPstream toMaster
-            (
-                Pstream::commsTypes::scheduled,
-                Pstream::masterNo()
-            );
-
-            toMaster
-                << faces;
-        }
-    }
-}
-
-
-void Foam::ensightMesh::writeFaceConnectivity
-(
-    ensightFaces::elemType elemType,
-    const label nTotal,
-    const faceList& faceLst,
-    const labelUList& addr,
-    ensightGeoFile& os
-) const
-{
-    if (nTotal)
-    {
-        if (Pstream::master())
-        {
-            os.writeKeyword(ensightFaces::key(elemType));
-            os.write(nTotal);
-            os.newline();
-        }
-
-        const UIndirectList<face> faces(faceLst, addr);
-
-        if (elemType == ensightFaces::NSIDED)
-        {
-            // Number of points per face
-
-            if (Pstream::master())
-            {
-                writeFaceSizes(faces, os);
-
-                for (int slave=1; slave<Pstream::nProcs(); ++slave)
-                {
-                    IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                    faceList recv(fromSlave);
-
-                    writeFaceSizes(recv, os);
-                }
-            }
-            else
-            {
-                OPstream toMaster
-                (
-                    Pstream::commsTypes::scheduled,
-                    Pstream::masterNo()
-                );
-
-                toMaster
-                    << faces;
-            }
-        }
-
-        // List of points id per face
-        if (Pstream::master())
-        {
-            writeFaceList(faces, os);
-
-            for (int slave=1; slave<Pstream::nProcs(); ++slave)
-            {
-                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                faceList recv(fromSlave);
-
-                writeFaceList(recv, os);
-            }
-        }
-        else
-        {
-            OPstream toMaster
-            (
-                Pstream::commsTypes::scheduled,
-                Pstream::masterNo()
-            );
-
-            toMaster
-                << faces;
-        }
-    }
-}
-
-
-void Foam::ensightMesh::writeFaceConnectivity
-(
-    const ensightFaces& ensFaces,
-    const faceList& faceLst,
-    ensightGeoFile& os,
-    const bool raw
-) const
-{
-    for (label typei=0; typei < ensightFaces::nTypes; ++typei)
-    {
-        const ensightFaces::elemType what =
-            ensightFaces::elemType(typei);
-
-        if (raw)
-        {
-            writeFaceConnectivity
-            (
-                what,
-                ensFaces.total(what),
-                SubList<face>
-                (
-                    faceLst,
-                    ensFaces.faceIds(what).size(),
-                    ensFaces.offset(what)
-                ),
-                os
-            );
-        }
-        else
-        {
-            writeFaceConnectivity
-            (
-                what,
-                ensFaces.total(what),
-                faceLst,
-                ensFaces.faceIds(what),
-                os
-            );
-        }
-    }
-}
-
-
-void Foam::ensightMesh::writeAllPoints
-(
-    const label partId,
-    const word& ensightPartName,
-    const label nPoints,
-    const pointField& uniquePoints,
-    ensightGeoFile& os
-) const
-{
-    if (Pstream::master())
-    {
-        os.beginPart(partId, ensightPartName);
-
-        // Write points
-        os.beginCoordinates(nPoints);
-
-        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
-        {
-            os.writeList(uniquePoints.component(cmpt));
-
-            for (int slave=1; slave<Pstream::nProcs(); ++slave)
-            {
-                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                scalarField recv(fromSlave);
-                os.writeList(recv);
-            }
-        }
-    }
-    else
-    {
-        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
-        {
-            OPstream toMaster
-            (
-                Pstream::commsTypes::scheduled,
-                Pstream::masterNo()
-            );
-
-            toMaster
-                << uniquePoints.component(cmpt);
-        }
-    }
-}
-
-
-// ************************************************************************* //
diff --git a/src/conversion/ensight/mesh/ensightMeshOptions.C b/src/conversion/ensight/mesh/ensightMeshOptions.C
deleted file mode 100644
index 73ba0bd937e..00000000000
--- a/src/conversion/ensight/mesh/ensightMeshOptions.C
+++ /dev/null
@@ -1,183 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "ensightMesh.H"
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::ensightMesh::options::options()
-:
-    options(IOstream::streamFormat::BINARY)
-{}
-
-
-Foam::ensightMesh::options::options(IOstream::streamFormat format)
-:
-    format_(format),
-    lazy_(false),
-    internal_(true),
-    boundary_(true),
-    patchPatterns_(),
-    faceZonePatterns_()
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-Foam::IOstream::streamFormat Foam::ensightMesh::options::format() const
-{
-    return format_;
-}
-
-
-bool Foam::ensightMesh::options::lazy() const
-{
-    return lazy_;
-}
-
-
-bool Foam::ensightMesh::options::useInternalMesh() const
-{
-    return internal_;
-}
-
-
-bool Foam::ensightMesh::options::useBoundaryMesh() const
-{
-    return boundary_;
-}
-
-
-bool Foam::ensightMesh::options::useFaceZones() const
-{
-    return faceZonePatterns_.size();
-}
-
-
-void Foam::ensightMesh::options::reset()
-{
-    internal_ = true;
-    boundary_ = true;
-    patchPatterns_.clear();
-    faceZonePatterns_.clear();
-}
-
-
-void Foam::ensightMesh::options::lazy(bool beLazy)
-{
-    lazy_ = beLazy;
-}
-
-
-void Foam::ensightMesh::options::useInternalMesh(bool on)
-{
-    internal_ = on;
-}
-
-
-void Foam::ensightMesh::options::useBoundaryMesh(bool on)
-{
-    boundary_ = on;
-
-    if (!boundary_ && patchPatterns_.size())
-    {
-        patchPatterns_.clear();
-
-        WarningInFunction
-            << "Deactivating boundary and removing old patch selection"
-            << endl;
-    }
-}
-
-
-void Foam::ensightMesh::options::patchSelection
-(
-    const UList<wordRe>& patterns
-)
-{
-    patchPatterns_ = wordRes(patterns);
-
-    if (!boundary_ && patchPatterns_.size())
-    {
-        patchPatterns_.clear();
-
-        WarningInFunction
-            << "Ignoring patch selection, boundary is not active"
-            << endl;
-    }
-}
-
-
-void Foam::ensightMesh::options::patchSelection
-(
-    List<wordRe>&& patterns
-)
-{
-    patchPatterns_ = wordRes(std::move(patterns));
-
-    if (!boundary_ && patchPatterns_.size())
-    {
-        patchPatterns_.clear();
-
-        WarningInFunction
-            << "Ignoring patch selection, boundary is not active"
-            << endl;
-    }
-}
-
-
-void Foam::ensightMesh::options::faceZoneSelection
-(
-    const UList<wordRe>& patterns
-)
-{
-    faceZonePatterns_ = wordRes(patterns);
-}
-
-
-void Foam::ensightMesh::options::faceZoneSelection
-(
-    List<wordRe>&& patterns
-)
-{
-    faceZonePatterns_ = wordRes(std::move(patterns));
-}
-
-
-const Foam::wordRes& Foam::ensightMesh::options::patchSelection() const
-{
-    return patchPatterns_;
-}
-
-
-const Foam::wordRes& Foam::ensightMesh::options::faceZoneSelection() const
-{
-    return faceZonePatterns_;
-}
-
-
-// ************************************************************************* //
diff --git a/src/conversion/ensight/output/ensightOutputVolField.H b/src/conversion/ensight/output/ensightOutputVolField.H
index 21a570b4954..7309103d4d4 100644
--- a/src/conversion/ensight/output/ensightOutputVolField.H
+++ b/src/conversion/ensight/output/ensightOutputVolField.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -35,10 +35,8 @@ Description
 #define ensightOutputVolField_H
 
 #include "ensightOutput.H"
-#include "ensightPart.H"
-#include "ensightParts.H"
-#include "ensightPartFaces.H"
-#include "ensightPartCells.H"
+#include "ensightFaces.H"
+#include "ensightCells.H"
 #include "volFields.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -46,40 +44,12 @@ Description
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class ensightMesh;
 
 namespace ensightOutput
 {
 
-/*---------------------------------------------------------------------------*\
-                        Namespace ensightOutput::Detail
-\*---------------------------------------------------------------------------*/
-
-namespace Detail
-{
-
-//- Write volume field component-wise
-template<class Type>
-bool writeVolField
-(
-    const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
-);
-
-//- Write point field component-wise
-template<class Type>
-bool writePointField
-(
-    const GeometricField<Type, pointPatchField, pointMesh>& pf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
-);
-
-} // End namespace Detail
-
-
 /*---------------------------------------------------------------------------*\
                             Namespace ensightOutput
 \*---------------------------------------------------------------------------*/
@@ -88,51 +58,31 @@ bool writePointField
 template<class Type>
 bool writeVolField
 (
-    const GeometricField<Type, fvPatchField, volMesh>&,
-    const ensightMesh& ensMesh,
     ensightFile& os,
-    const bool nodeValues = false
-);
-
-
-/*---------------------------------------------------------------------------*\
-                        Namespace ensightOutput::Serial
-\*---------------------------------------------------------------------------*/
-
-namespace Serial
-{
-
-//- Write volume field component-wise for specified faces
-template<class Type>
-bool writeVolField
-(
     const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightPartFaces& part,
-    ensightFile& os
+    const ensightMesh& ensMesh
 );
 
 
-//- Write volume field component-wise for specified cells
+//- Write volume field component-wise, optionally forcing interpolation
 template<class Type>
 bool writeVolField
 (
+    ensightFile& os,
     const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightPartCells& part,
-    ensightFile& os
+    const ensightMesh& ensMesh,
+    const bool nodeValues
 );
 
-
-//- Write volume field component-wise
+//- Write point field component-wise
 template<class Type>
-bool writeVolField
+bool writePointField
 (
-    const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightParts& list,
-    ensightFile& os
+    ensightFile& os,
+    const GeometricField<Type, pointPatchField, pointMesh>& pf,
+    const ensightMesh& ensMesh
 );
 
-} // End namespace Serial
-
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
diff --git a/src/conversion/ensight/output/ensightOutputVolFieldTemplates.C b/src/conversion/ensight/output/ensightOutputVolFieldTemplates.C
index de1238cf54f..f08c0ad0c65 100644
--- a/src/conversion/ensight/output/ensightOutputVolFieldTemplates.C
+++ b/src/conversion/ensight/output/ensightOutputVolFieldTemplates.C
@@ -5,8 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,132 +29,167 @@ License
 #include "ensightMesh.H"
 
 #include "fvMesh.H"
-#include "globalIndex.H"
+#include "linear.H"
 #include "volPointInterpolation.H"
 #include "interpolation.H"
-#include "linear.H"
 #include "processorFvPatch.H"
-#include "uindirectPrimitivePatch.H"
+#include "DynamicField.H"
 
 // * * * * * * * * * * * * * * * *  Detail * * * * * * * * * * * * * * * * * //
 
 template<class Type>
-bool Foam::ensightOutput::Detail::writeVolField
+bool Foam::ensightOutput::writeVolField
 (
+    ensightFile& os,
     const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
+    const ensightMesh& ensMesh
 )
 {
-    constexpr bool parallel = true;
+    bool parallel = Pstream::parRun();
 
-    const fvMesh& mesh = ensMesh.mesh();
-    const ensightCells& meshCells = ensMesh.meshCells();
-    const Map<word>&  patchLookup = ensMesh.patches();
-    const HashTable<ensightFaces>& patchFaces = ensMesh.boundaryPatchFaces();
-    const HashTable<ensightFaces>&  zoneFaces = ensMesh.faceZoneFaces();
+    const fvMesh& mesh = vf.mesh();
+    const polyBoundaryMesh& bmesh = mesh.boundaryMesh();
 
-    //
-    // write internalMesh, unless patch-selection was requested
-    //
-    if (ensMesh.useInternalMesh())
+    const Map<ensightCells>& cellZoneParts = ensMesh.cellZoneParts();
+    const Map<ensightFaces>& faceZoneParts = ensMesh.faceZoneParts();
+    const Map<ensightFaces>& boundaryParts = ensMesh.boundaryParts();
+
+
+    // Write internalMesh and cellZones - sorted by index
+    for (const label zoneId : cellZoneParts.sortedToc())
     {
-        Detail::writeCellField(vf, meshCells, os, parallel);
+        const ensightCells& part = cellZoneParts[zoneId];
+
+        ensightOutput::writeField(os, vf, part, parallel);
     }
 
-    //
-    // write patches
-    // use sortedToc for extra safety
-    //
-    const labelList patchIds = patchLookup.sortedToc();
-    for (const label patchId : patchIds)
+
+    // Write patches - sorted by index
+    for (const label patchId : boundaryParts.sortedToc())
     {
-        const word& patchName = patchLookup[patchId];
-        const ensightFaces& part = patchFaces[patchName];
+        const ensightFaces& part = boundaryParts[patchId];
 
-        writeFaceField
+        if (patchId < 0 || patchId >= bmesh.size())
+        {
+            // Future handling of combined patches?
+            continue;
+        }
+
+        const label patchStart = bmesh[patchId].start();
+
+        // Either use a flat boundary field for all patches,
+        // or patch-local face ids
+
+        // Operate on a copy
+        ensightFaces localPart(part);
+
+        // Change from global faceIds to patch-local
+        localPart.decrFaceIds(patchStart);
+
+        ensightOutput::writeField
         (
-            vf.boundaryField()[patchId],
-            part,
             os,
+            vf.boundaryField()[patchId],
+            localPart,
             parallel
         );
     }
 
 
-    //
-    // write faceZones, if requested
-    // use sortedToc for extra safety
-    //
-    const wordList zoneNames = zoneFaces.sortedToc();
-    if (!zoneNames.empty())
+    // No face zones data
+    if (faceZoneParts.empty())
     {
-        // Interpolates cell values to faces - needed only when exporting
-        // faceZones...
-        GeometricField<Type, fvsPatchField, surfaceMesh> sf
-        (
-            Foam::linearInterpolate(vf)
-        );
+        return true;
+    }
 
-        // flat boundary field
-        // similar to volPointInterpolation::flatBoundaryField()
 
-        Field<Type> flat(mesh.nBoundaryFaces(), Zero);
+    // Flat boundary field
+    // similar to volPointInterpolation::flatBoundaryField()
+
+    Field<Type> flat(mesh.nBoundaryFaces(), Zero);
+
+    const fvBoundaryMesh& bm = mesh.boundary();
+    forAll(vf.boundaryField(), patchi)
+    {
+        const polyPatch& pp = bm[patchi].patch();
+        const auto& bf = vf.boundaryField()[patchi];
 
-        const fvBoundaryMesh& bm = mesh.boundary();
-        forAll(vf.boundaryField(), patchi)
+        if (isA<processorFvPatch>(bm[patchi]))
         {
-            const polyPatch& pp = bm[patchi].patch();
-            const auto& bf = vf.boundaryField()[patchi];
-
-            if (isA<processorFvPatch>(bm[patchi]))
-            {
-                // Use average value for processor faces
-                // own cell value = patchInternalField
-                // nei cell value = evaluated boundary values
-                SubList<Type>
-                (
-                    flat,
-                    bf.size(),
-                    pp.offset()
-                ) = (0.5 * (bf.patchInternalField() + bf));
-            }
-            else if (!isA<emptyFvPatch>(bm[patchi]))
-            {
-                SubList<Type>
-                (
-                    flat,
-                    bf.size(),
-                    pp.offset()
-                ) = bf;
-            }
+            // Use average value for processor faces
+            // own cell value = patchInternalField
+            // nei cell value = evaluated boundary values
+            SubList<Type>
+            (
+                flat,
+                bf.size(),
+                pp.offset()
+            ) = (0.5 * (bf.patchInternalField() + bf));
+        }
+        else if (!isA<emptyFvPatch>(bm[patchi]))
+        {
+            SubList<Type>
+            (
+                flat,
+                bf.size(),
+                pp.offset()
+            ) = bf;
         }
+    }
+
+
+    // Interpolate cell values to faces
+    // - only needed when exporting faceZones...
+
+    tmp<GeometricField<Type, fvsPatchField, surfaceMesh>> tsfld
+        = Foam::linearInterpolate(vf);
+
+    const auto& sfld = tsfld();
+
+
+    // Local output buffer
+    label maxLen = 0;
+
+    forAllConstIters(faceZoneParts, iter)
+    {
+        maxLen = max(maxLen, iter.val().size());
+    }
+
+    DynamicField<Type> values(maxLen);
+
 
-        for (const word& zoneName : zoneNames)
+    //
+    // Write requested faceZones - sorted by index
+    //
+    for (const label zoneId : faceZoneParts.sortedToc())
+    {
+        const ensightFaces& part = faceZoneParts[zoneId];
+
+        // Loop over face ids to store the needed field values
+        // - internal faces use linear interpolation
+        // - boundary faces use the corresponding patch value
+
+        // Local copy of the field
+        values.resize(part.size());
+        values = Zero;
+
+        auto valIter = values.begin();
+
+        for (const label faceId : part.faceIds())
         {
-            const ensightFaces& part = zoneFaces[zoneName];
-
-            // Field (local size)
-            Field<Type> values(part.size());
-
-            // Loop over face ids to store the needed field values
-            // - internal faces use linear interpolation
-            // - boundary faces use the corresponding patch value
-            forAll(part, i)
-            {
-                const label faceId = part[i];
-                values[i] =
-                (
-                    mesh.isInternalFace(faceId)
-                  ? sf[faceId]
-                  : flat[faceId - mesh.nInternalFaces()]
-                );
-            }
-
-            // The field is already copied in the proper order
-            // - just need its corresponding sub-fields
-            Detail::writeFaceSubField(values, part, os, parallel);
+            *valIter =
+            (
+                mesh.isInternalFace(faceId)
+              ? sfld[faceId]
+              : flat[faceId - mesh.nInternalFaces()]
+            );
+
+            ++valIter;
         }
+
+        // The field is already in the proper element order
+        // - just need its corresponding sub-fields
+        ensightOutput::Detail::writeFaceSubField(os, values, part, parallel);
     }
 
     return true;
@@ -163,118 +197,90 @@ bool Foam::ensightOutput::Detail::writeVolField
 
 
 template<class Type>
-bool Foam::ensightOutput::Detail::writePointField
+bool Foam::ensightOutput::writePointField
 (
+    ensightFile& os,
     const GeometricField<Type, pointPatchField, pointMesh>& pf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
+    const ensightMesh& ensMesh
 )
 {
-    constexpr bool parallel = true;
+    bool parallel = Pstream::parRun();
 
-    const fvMesh& mesh = ensMesh.mesh();
-    const Map<word>& patchLookup  = ensMesh.patches();
+    const polyMesh& mesh = ensMesh.mesh();
 
-    const HashTable<ensightFaces>& patchFaces = ensMesh.boundaryPatchFaces();
-    const HashTable<ensightFaces>&  zoneFaces = ensMesh.faceZoneFaces();
+    const Map<ensightCells>& cellZoneParts = ensMesh.cellZoneParts();
+    const Map<ensightFaces>& faceZoneParts = ensMesh.faceZoneParts();
+    const Map<ensightFaces>& boundaryParts = ensMesh.boundaryParts();
 
     //
-    // write internalMesh, unless patch-selection was requested
+    // Write internalMesh and cellZones - sorted by index
     //
-    if (ensMesh.useInternalMesh())
+    for (const label zoneId : cellZoneParts.sortedToc())
     {
+        const ensightCells& part = cellZoneParts[zoneId];
+
+        labelList uniqueMeshPointLabels;
+        part.uniqueMeshPoints(mesh, uniqueMeshPointLabels, parallel);
+
         if (Pstream::master())
         {
-            os.beginPart(0); // 0 = internalMesh
+            os.beginPart(part.index());
         }
 
-        Detail::writeFieldComponents
+        ensightOutput::Detail::writeFieldComponents
         (
-            "coordinates",
-            Field<Type>(pf.internalField(), ensMesh.uniquePointMap()),
             os,
+            ensightFile::coordinates,
+            UIndirectList<Type>(pf.internalField(), uniqueMeshPointLabels),
             parallel
         );
     }
 
+
     //
-    // write patches
-    // use sortedToc for extra safety
+    // Write patches - sorted by index
     //
-    const labelList patchIds = patchLookup.sortedToc();
-    for (const label patchId : patchIds)
+    for (const label patchId : boundaryParts.sortedToc())
     {
-        const word& patchName = patchLookup[patchId];
-        const ensightFaces& part = patchFaces[patchName];
-
-        const fvPatch& p = mesh.boundary()[patchId];
+        const ensightFaces& part = boundaryParts[patchId];
 
-        // Renumber the patch points/faces into unique points
-        labelList pointToGlobal;
         labelList uniqueMeshPointLabels;
-        autoPtr<globalIndex> globalPointsPtr =
-            mesh.globalData().mergePoints
-            (
-                p.patch().meshPoints(),
-                p.patch().meshPointMap(),
-                pointToGlobal,
-                uniqueMeshPointLabels
-            );
+        part.uniqueMeshPoints(mesh, uniqueMeshPointLabels, parallel);
 
         if (Pstream::master())
         {
             os.beginPart(part.index());
         }
 
-        Detail::writeFieldComponents
+        ensightOutput::Detail::writeFieldComponents
         (
-            "coordinates",
-            Field<Type>(pf.internalField(), uniqueMeshPointLabels),
             os,
+            ensightFile::coordinates,
+            UIndirectList<Type>(pf.internalField(), uniqueMeshPointLabels),
             parallel
         );
     }
 
     //
-    // write faceZones, if requested
+    // Write requested faceZones - sorted by index
     //
-    const wordList zoneNames = zoneFaces.sortedToc();
-    for (const word& zoneName : zoneNames)
+    for (const label zoneId : faceZoneParts.sortedToc())
     {
-        const ensightFaces& part = zoneFaces[zoneName];
-
-        uindirectPrimitivePatch p
-        (
-            UIndirectList<face>
-            (
-                mesh.faces(),
-                part.faceIds()
-            ),
-            mesh.points()
-        );
+        const ensightFaces& part = faceZoneParts[zoneId];
 
-        // Renumber the patch points/faces into unique points
-        labelList pointToGlobal;
         labelList uniqueMeshPointLabels;
-        autoPtr<globalIndex> globalPointsPtr =
-            mesh.globalData().mergePoints
-            (
-                p.meshPoints(),
-                p.meshPointMap(),
-                pointToGlobal,
-                uniqueMeshPointLabels
-            );
+        part.uniqueMeshPoints(mesh, uniqueMeshPointLabels, parallel);
 
         if (Pstream::master())
         {
             os.beginPart(part.index());
         }
 
-        Detail::writeFieldComponents
+        ensightOutput::Detail::writeFieldComponents
         (
-            "coordinates",
-            Field<Type>(pf.internalField(), uniqueMeshPointLabels),
             os,
+            ensightFile::coordinates,
+            UIndirectList<Type>(pf.internalField(), uniqueMeshPointLabels),
             parallel
         );
     }
@@ -288,9 +294,9 @@ bool Foam::ensightOutput::Detail::writePointField
 template<class Type>
 bool Foam::ensightOutput::writeVolField
 (
+    ensightFile& os,
     const GeometricField<Type, fvPatchField, volMesh>& vf,
     const ensightMesh& ensMesh,
-    ensightFile& os,
     const bool nodeValues
 )
 {
@@ -303,90 +309,10 @@ bool Foam::ensightOutput::writeVolField
         pfld.ref().checkOut();
         pfld.ref().rename(vf.name());
 
-        return Detail::writePointField<Type>(pfld, ensMesh, os);
+        return ensightOutput::writePointField<Type>(os, pfld, ensMesh);
     }
 
-    return Detail::writeVolField<Type>(vf, ensMesh, os);
-}
-
-
-// * * * * * * * * * * * * * * * *  Serial * * * * * * * * * * * * * * * * * //
-
-template<class Type>
-bool Foam::ensightOutput::Serial::writeVolField
-(
-    const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightPartFaces& part,
-    ensightFile& os
-)
-{
-    constexpr bool parallel = false; // serial
-
-    const label patchi = part.patchIndex();
-
-    if (patchi >= 0 && patchi < vf.boundaryField().size())
-    {
-        return ensightOutput::Detail::writeFaceField
-        (
-            vf.boundaryField()[patchi],
-            part,
-            os,
-            parallel
-        );
-    }
-
-    return false;
-}
-
-
-template<class Type>
-bool Foam::ensightOutput::Serial::writeVolField
-(
-    const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightPartCells& part,
-    ensightFile& os
-)
-{
-    constexpr bool parallel = false; // serial
-
-    return ensightOutput::Detail::writeCellField
-    (
-        vf.internalField(),
-        part,
-        os,
-        parallel
-    );
-}
-
-
-template<class Type>
-bool Foam::ensightOutput::Serial::writeVolField
-(
-    const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightParts& list,
-    ensightFile& os
-)
-{
-    for (const ensightPart& part : list)
-    {
-        const ensightPartFaces* fptr = isA<ensightPartFaces>(part);
-
-        if (fptr)
-        {
-            Serial::writeVolField(vf, *fptr, os);
-            continue;
-        }
-
-        const ensightPartCells* cptr = isA<ensightPartCells>(part);
-
-        if (cptr)
-        {
-            Serial::writeVolField(vf, *cptr, os);
-            continue;
-        }
-    }
-
-    return true;
+    return ensightOutput::writeVolField<Type>(os, vf, ensMesh);
 }
 
 
diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 62f54aaba6c..e08ede224e5 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -7,12 +7,20 @@ ensight/file/ensightCaseOptions.C
 ensight/file/ensightFile.C
 ensight/file/ensightGeoFile.C
 
-ensight/part/ensightCells.C
-ensight/part/ensightFaces.C
-ensight/part/ensightPart.C
-ensight/part/ensightPartCells.C
-ensight/part/ensightPartFaces.C
-ensight/part/ensightParts.C
+ensight/mesh/ensightMesh.C
+ensight/mesh/ensightMeshOptions.C
+
+ensight/output/ensightOutput.C
+
+part = ensight/part
+$(part)/cells/ensightCells.C
+$(part)/cells/ensightCellsAddr.C
+$(part)/cells/ensightCellsIO.C
+$(part)/faces/ensightFaces.C
+$(part)/faces/ensightFacesAddr.C
+$(part)/faces/ensightFacesIO.C
+$(part)/part/ensightPart.C
+$(part)/surface/ensightOutputSurface.C
 
 ensight/read/ensightReadFile.C
 ensight/type/ensightPTraits.C
diff --git a/src/fileFormats/ensight/mesh/ensightMesh.C b/src/fileFormats/ensight/mesh/ensightMesh.C
new file mode 100644
index 00000000000..4dfdf835c37
--- /dev/null
+++ b/src/fileFormats/ensight/mesh/ensightMesh.C
@@ -0,0 +1,479 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 "ensightMesh.H"
+#include "ensightGeoFile.H"
+#include "polyMesh.H"
+#include "emptyPolyPatch.H"
+#include "processorPolyPatch.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+const Foam::label Foam::ensightMesh::internalZone = -1;
+
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Find matching ids based on whitelist, blacklist
+//
+// An empty whitelist accepts everything that is not blacklisted.
+// A regex match is trumped by a literal match.
+//
+// Eg,
+//     input:  ( abc apple wall wall1 wall2 )
+//     whitelist:  ( abc  def  "wall.*" )
+//     blacklist:  ( "[ab].*"  wall )
+//
+//     result:  (abc wall1 wall2)
+//
+static labelList getSelected
+(
+    const UList<word>& input,
+    const wordRes& whitelist,
+    const wordRes& blacklist
+)
+{
+    const label len = input.size();
+
+    if (whitelist.empty() && blacklist.empty())
+    {
+        return identity(len);
+    }
+
+    labelList indices(len);
+
+    label count = 0;
+    for (label i=0; i < len; ++i)
+    {
+        const auto& text = input[i];
+
+        bool accept = false;
+
+        if (whitelist.size())
+        {
+            const auto result = whitelist.matched(text);
+
+            accept =
+            (
+                result == wordRe::LITERAL
+              ? true
+              : (result == wordRe::REGEX && !blacklist.match(text))
+            );
+        }
+        else
+        {
+            accept = !blacklist.match(text);
+        }
+
+        if (accept)
+        {
+            indices[count] = i;
+            ++count;
+        }
+    }
+    indices.resize(count);
+
+    return indices;
+}
+
+
+// Patch names without processor patches
+static wordList nonProcessorPatchNames(const polyBoundaryMesh& bmesh)
+{
+    #ifdef FULLDEBUG
+    // Patches are output. Check that they are synced.
+    bmesh.checkParallelSync(true);
+    #endif
+
+    wordList patchNames(bmesh.names());
+    patchNames.resize(bmesh.nNonProcessor());
+
+    return patchNames;
+}
+
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightMesh::clear()
+{
+    cellZoneParts_.clear();
+    faceZoneParts_.clear();
+    boundaryParts_.clear();
+}
+
+
+void Foam::ensightMesh::renumber()
+{
+    label partNo = 0;
+
+    for (const label id : cellZoneParts_.sortedToc())
+    {
+        cellZoneParts_[id].index() = partNo++;
+    }
+
+    for (const label id : boundaryParts_.sortedToc())
+    {
+        boundaryParts_[id].index() = partNo++;
+    }
+
+    for (const label id : faceZoneParts_.sortedToc())
+    {
+        faceZoneParts_[id].index() = partNo++;
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ensightMesh::ensightMesh
+(
+    const polyMesh& mesh
+)
+:
+    ensightMesh(mesh, ensightMesh::options())
+{}
+
+
+Foam::ensightMesh::ensightMesh
+(
+    const polyMesh& mesh,
+    const ensightMesh::options& opts
+)
+:
+    options_(new options(opts)),
+    mesh_(mesh),
+    needsUpdate_(true)
+{
+    if (!option().lazy())
+    {
+        correct();
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::ensightMesh::correct()
+{
+    clear();
+
+    const wordRes& czMatcher = option().cellZoneSelection();
+    const wordRes& fzMatcher = option().faceZoneSelection();
+
+    // Possible cellZones
+    const wordList czNames =
+    (
+        (
+            option().useCellZones()
+         && (!czMatcher.empty() || option().useInternalMesh())
+        )
+      ? mesh_.cellZones().names()
+      : wordList()
+    );
+
+    const labelList czoneIds =
+    (
+        czMatcher.empty()
+      ? identity(czNames.size())        // All
+      : czMatcher.matching(czNames)     // Selected names
+    );
+
+
+    // Possible faceZones
+    const wordList fzNames =
+    (
+        option().useFaceZones()
+      ? mesh_.faceZones().names()
+      : wordList()
+    );
+
+    const labelList fzoneIds =
+    (
+        fzMatcher.empty()
+      ? identity(fzNames.size())        // All
+      : fzMatcher.matching(fzNames)     // Selected names
+    );
+
+
+    // Possible patchNames
+    const wordList patchNames =
+    (
+        option().useBoundaryMesh()
+      ? nonProcessorPatchNames(mesh_.boundaryMesh())
+      : wordList()
+    );
+
+    const labelList patchIds =
+    (
+        option().useBoundaryMesh()
+      ? getSelected
+        (
+            patchNames,
+            option().patchSelection(),
+            option().patchExclude()
+        )
+      : labelList()
+    );
+
+
+    // Track which cells are in a zone or not
+    bitSet cellSelection;
+
+    // Faces to be excluded from export
+    bitSet excludeFace;
+
+
+    // cellZones first
+    for (const label zoneId : czoneIds)
+    {
+        const word& zoneName = czNames[zoneId];
+        const cellZone& zn = mesh_.cellZones()[zoneId];
+
+        if (returnReduce(!zn.empty(), orOp<bool>()))
+        {
+            cellSelection.set(zn);
+
+            ensightCells& part = cellZoneParts_(zoneId);
+
+            part.clear();
+            part.identifier() = zoneId;
+            part.rename(zoneName);
+
+            part.classify(mesh_, zn);
+
+            // Finalize
+            part.reduce();
+        }
+    }
+
+    if (option().useInternalMesh() && czMatcher.empty())
+    {
+        // The internal mesh:
+        // Either the entire mesh (if no zones specified)
+        // or whatever is leftover as unzoned
+
+        if (cellZoneParts_.empty())
+        {
+            ensightCells& part = cellZoneParts_(internalZone);
+
+            part.clear();
+            part.identifier() = internalZone;
+            part.rename("internalMesh");
+
+            part.classify(mesh_);
+
+            // Finalize
+            part.reduce();
+        }
+        else
+        {
+            // Unzoned cells - flip selection from zoned to unzoned
+            cellSelection.flip();
+
+            if (returnReduce(cellSelection.any(), orOp<bool>()))
+            {
+                ensightCells& part = cellZoneParts_(internalZone);
+
+                part.clear();
+                part.identifier() = internalZone;
+                part.rename("internalMesh");
+
+                part.classify(mesh_, cellSelection);
+
+                // Finalize
+                part.reduce();
+            }
+        }
+
+        // Handled all cells
+        cellSelection.clearStorage();
+    }
+    else if (cellSelection.none())
+    {
+        // No internal, no cellZones selected - just ignore
+        cellSelection.clearStorage();
+    }
+
+
+    // Face exclusion based on cellZones
+
+    if (returnReduce(!cellSelection.empty(), orOp<bool>()))
+    {
+        excludeFace.resize(mesh_.nFaces());
+
+        const labelList& owner = mesh_.faceOwner();
+
+        forAll(owner, facei)
+        {
+            const label celli = owner[facei];
+
+            if (!cellSelection.test(celli))
+            {
+                excludeFace.set(facei);
+            }
+        }
+    }
+
+
+    // Face exclusion for empty/processor types
+    // so they are ignored for face zones
+
+    if (fzoneIds.size())
+    {
+        excludeFace.resize(mesh_.nFaces());
+
+        for (const polyPatch& p : mesh_.boundaryMesh())
+        {
+            const auto* procPatch = isA<processorPolyPatch>(p);
+
+            if (isA<emptyPolyPatch>(p))
+            {
+                excludeFace.set(p.range());
+            }
+            else if (procPatch && !procPatch->owner())
+            {
+                // Exclude neighbour-side, retain owner-side only
+                excludeFace.set(p.range());
+            }
+        }
+    }
+
+
+    // Patches
+    for (const label patchId : patchIds)
+    {
+        const word& patchName = patchNames[patchId];
+        const polyPatch& p = mesh_.boundaryMesh()[patchId];
+
+        if (isA<emptyPolyPatch>(p))
+        {
+            // Skip empty patch types
+            continue;
+        }
+        else if (isA<processorPolyPatch>(p))
+        {
+            // Only real (non-processor) boundaries.
+            break;
+        }
+
+        ensightFaces& part = boundaryParts_(patchId);
+
+        part.clear();
+        part.identifier() = patchId;
+        part.rename(patchName);
+
+        part.classify
+        (
+            mesh_.faces(),
+            identity(p.size(), p.start()),
+            boolList(),  // no flip map
+            excludeFace
+        );
+
+        // Finalize
+        part.reduce();
+
+        if (!part.total())
+        {
+            boundaryParts_.erase(patchId);
+        }
+    }
+
+
+    // Face zones
+
+    for (const label zoneId : fzoneIds)
+    {
+        const word& zoneName = fzNames[zoneId];
+        const faceZone& zn = mesh_.faceZones()[zoneId];
+
+        ensightFaces& part = faceZoneParts_(zoneId);
+
+        part.clear();
+        part.identifier() = zoneId;
+        part.rename(zoneName);
+
+        if (zn.size())
+        {
+            part.classify
+            (
+                mesh_.faces(),
+                zn,
+                zn.flipMap(),
+                excludeFace
+            );
+        }
+
+        // Finalize
+        part.reduce();
+
+        if (!part.total())
+        {
+            faceZoneParts_.erase(zoneId);
+        }
+    }
+
+    renumber();
+
+    needsUpdate_ = false;
+}
+
+
+void Foam::ensightMesh::write
+(
+    ensightGeoFile& os,
+    bool parallel
+) const
+{
+    // The internalMesh, cellZones
+    for (const label id : cellZoneParts_.sortedToc())
+    {
+        cellZoneParts_[id].write(os, mesh_, parallel);
+    }
+
+    // Patches - sorted by index
+    for (const label id : boundaryParts_.sortedToc())
+    {
+        boundaryParts_[id].write(os, mesh_, parallel);
+    }
+
+    // Requested faceZones - sorted by index
+    for (const label id : faceZoneParts_.sortedToc())
+    {
+        faceZoneParts_[id].write(os, mesh_, parallel);
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/mesh/ensightMesh.H b/src/fileFormats/ensight/mesh/ensightMesh.H
new file mode 100644
index 00000000000..1db242c65e5
--- /dev/null
+++ b/src/fileFormats/ensight/mesh/ensightMesh.H
@@ -0,0 +1,342 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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::ensightMesh
+
+Description
+    Encapsulation of volume meshes for writing in ensight format.
+    It manages cellZones, facesZone, patches.
+
+    When cellZones are present (and not disabled), the cells are grouped
+    in parts according to the zone.
+    Any remaining \em unzoned cells are placed into the "internalMesh" part,
+    which is always part 0. If cellZones are missing or disabled,
+    all cells are placed into the "internalMesh" part.
+
+    If one or more cellZones are explicitly requested, all other cells
+    (including any unzoned cells) are ignored.
+
+    The converted patch faces are restricted by the volume mesh coverage.
+    Except when the entire internal mesh has been explicitly suppressed.
+
+Note
+    The internal data management uses a Map for cellZones, faceZones and
+    patches. The internalMesh is treated as cellZone with a special index.
+
+    Since the patches are subsetted by the internal mesh coverage,
+    they are treated as indirect patches rather than regular poly patches.
+
+SourceFiles
+    ensightMesh.C
+    ensightMeshI.H
+    ensightMeshOptions.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ensightMesh_H
+#define ensightMesh_H
+
+#include "Map.H"
+#include "ensightCells.H"
+#include "ensightFaces.H"
+#include "wordRes.H"
+#include <memory>
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class polyMesh;
+class ensightGeoFile;
+class ensightMesh;
+
+/*---------------------------------------------------------------------------*\
+                         Class ensightMesh Declaration
+\*---------------------------------------------------------------------------*/
+
+class ensightMesh
+{
+public:
+
+    // Forward Declarations
+    class options;
+
+    //- The zone-id for internal mesh or unzoned cells.
+    static const label internalZone;
+
+
+private:
+
+    // Private Data
+
+        //- Writer options
+        const std::unique_ptr<options> options_;
+
+        //- Reference to the OpenFOAM mesh
+        const polyMesh& mesh_;
+
+        //- Volume elements per cellZone, lookup by zone index.
+        //  The zone -1 is reserved for internal mesh (unzoned cells)
+        Map<ensightCells> cellZoneParts_;
+
+        //- Face elements per faceZone, lookup by zone index.
+        Map<ensightFaces> faceZoneParts_;
+
+        //- Face elements per selected patch, lookup by patch index
+        Map<ensightFaces> boundaryParts_;
+
+        //- Track if it needs an update
+        mutable bool needsUpdate_;
+
+
+    // Private Member Functions
+
+        //- Clear all storage
+        void clear();
+
+        //- Enforce consistent index/part numbering
+        void renumber();
+
+        //- No copy construct
+        ensightMesh(const ensightMesh&) = delete;
+
+        //- No copy assignment
+        void operator=(const ensightMesh&) = delete;
+
+
+public:
+
+    // Constructors
+
+        //- Construct from mesh with all default options
+        explicit ensightMesh(const polyMesh& mesh);
+
+        //- Construct from components
+        ensightMesh(const polyMesh& mesh, const options& opts);
+
+
+    // Member Functions
+
+    // Access
+
+        //- Reference to the underlying polyMesh
+        inline const polyMesh& mesh() const;
+
+        //- Reference to the writer/mesh options
+        inline const ensightMesh::options& option() const;
+
+        //- Face elements per selected patch, lookup by patch index
+        //  Process in sorted order.
+        //  May require special treatment for zone -1 (internal).
+        inline const Map<ensightCells>& cellZoneParts() const;
+
+        //- Face elements per faceZone, lookup by zone index.
+        //  Process in sorted order.
+        inline const Map<ensightFaces>& faceZoneParts() const;
+
+        //- Face elements per selected patch, lookup by patch index
+        //  Process in sorted order.
+        inline const Map<ensightFaces>& boundaryParts() const;
+
+
+    // Sizing Information
+
+        //- Any parts?
+        inline bool empty() const;
+
+        //- Number of parts
+        inline label size() const;
+
+
+    // Other
+
+        //- Does the content need an update?
+        inline bool needsUpdate() const;
+
+        //- Mark as needing an update.
+        //  May also free up unneeded data.
+        //  Return false if already marked as expired.
+        inline bool expire();
+
+        //- Update for new mesh
+        void correct();
+
+
+    // Output
+
+        //- Write geometry to file. Normally in parallel
+        inline void write
+        (
+            autoPtr<ensightGeoFile>& os,
+            bool parallel = Pstream::parRun()
+        ) const;
+
+        //- Write geometry to file. Normally in parallel
+        void write
+        (
+            ensightGeoFile& os,
+            bool parallel = Pstream::parRun()
+        ) const;
+};
+
+
+/*---------------------------------------------------------------------------*\
+                    Class ensightMesh::options Declaration
+\*---------------------------------------------------------------------------*/
+
+//- Configuration options for the ensightMesh
+class ensightMesh::options
+{
+    // Private Data
+
+        //- Create in 'expired' mode
+        bool lazy_;
+
+        //- Use the internal mesh
+        bool internal_;
+
+        //- Use the boundary mesh
+        bool boundary_;
+
+        //- Handle cellZones (if internal_ is true)
+        bool cellZones_;
+
+        //- Selected patches only
+        wordRes patchInclude_;
+
+        //- Deselected patches
+        wordRes patchExclude_;
+
+        //- Selected cellZones
+        wordRes cellZoneInclude_;
+
+        //- Selected faceZones
+        wordRes faceZoneInclude_;
+
+
+public:
+
+    // Constructors
+
+        //- Default construct. Non-lazy with internal/boundary/cellZones.
+        options();
+
+
+    // Member Functions
+
+    // Access
+
+        //- Lazy creation? (ie, ensightMesh starts as needsUpdate)
+        bool lazy() const;
+
+        //- Using internal?
+        bool useInternalMesh() const;
+
+        //- Using boundary?
+        bool useBoundaryMesh() const;
+
+        //- Using faceZones?
+        bool useFaceZones() const;
+
+        //- Using cellZones?
+        bool useCellZones() const;
+
+        //- Selection of patches. Empty if unspecified.
+        const wordRes& patchSelection() const;
+
+        //- Selection of black listed patches. Empty if unspecified.
+        const wordRes& patchExclude() const;
+
+        //- Selection of faceZones. Empty if unspecified.
+        const wordRes& faceZoneSelection() const;
+
+        //- Selection of faceZones. Empty if unspecified.
+        const wordRes& cellZoneSelection() const;
+
+
+    // Edit
+
+        //- Reset to defaults
+        void reset();
+
+        //- Lazy creation - ensightMesh starts as needsUpdate
+        void lazy(bool beLazy);
+
+        //- Alter the useBoundaryMesh state
+        void useInternalMesh(bool on);
+
+        //- Alter the useBoundaryMesh state
+        void useBoundaryMesh(bool on);
+
+        //- Alter the useCellZones state
+        void useCellZones(bool on);
+
+        //- Define patch selection matcher
+        void patchSelection(const UList<wordRe>& patterns);
+
+        //- Define patch selection matcher
+        void patchSelection(List<wordRe>&& patterns);
+
+        //- Define patch selection blacklist
+        void patchExclude(const UList<wordRe>& patterns);
+
+        //- Define patch selection blacklist
+        void patchExclude(List<wordRe>&& patterns);
+
+        //- Define faceZone selection matcher
+        void faceZoneSelection(const UList<wordRe>& patterns);
+
+        //- Define faceZone selection matcher
+        void faceZoneSelection(List<wordRe>&& patterns);
+
+        //- Define cellZone selection matcher
+        void cellZoneSelection(const UList<wordRe>& patterns);
+
+        //- Define cellZone selection matcher
+        void cellZoneSelection(List<wordRe>&& patterns);
+
+
+    // Output
+
+        //- Report values
+        void print(Ostream& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "ensightMeshI.H"
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/conversion/ensight/mesh/ensightMeshI.H b/src/fileFormats/ensight/mesh/ensightMeshI.H
similarity index 55%
rename from src/conversion/ensight/mesh/ensightMeshI.H
rename to src/fileFormats/ensight/mesh/ensightMeshI.H
index 8691435d298..a9cdbfbc730 100644
--- a/src/conversion/ensight/mesh/ensightMeshI.H
+++ b/src/fileFormats/ensight/mesh/ensightMeshI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,9 +25,9 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline const Foam::fvMesh& Foam::ensightMesh::mesh() const
+inline const Foam::polyMesh& Foam::ensightMesh::mesh() const
 {
     return mesh_;
 }
@@ -39,53 +39,77 @@ inline const Foam::ensightMesh::options& Foam::ensightMesh::option() const
 }
 
 
-inline Foam::IOstream::streamFormat Foam::ensightMesh::format() const
+inline const Foam::Map<Foam::ensightCells>&
+Foam::ensightMesh::cellZoneParts() const
 {
-    return options_->format();
+    return cellZoneParts_;
 }
 
 
-inline bool Foam::ensightMesh::useInternalMesh() const
+inline const Foam::Map<Foam::ensightFaces>&
+Foam::ensightMesh::faceZoneParts() const
 {
-    return options_->useInternalMesh();
+    return faceZoneParts_;
 }
 
 
-inline bool Foam::ensightMesh::useBoundaryMesh() const
+inline const Foam::Map<Foam::ensightFaces>&
+Foam::ensightMesh::boundaryParts() const
 {
-    return options_->useBoundaryMesh();
+    return boundaryParts_;
 }
 
 
-inline const Foam::ensightCells& Foam::ensightMesh::meshCells() const
+inline bool Foam::ensightMesh::expire()
 {
-    return meshCells_;
+    clear();
+
+    // Already marked as expired
+    if (needsUpdate_)
+    {
+        return false;
+    }
+
+    needsUpdate_ = true;
+    return true;
 }
 
 
-inline const Foam::Map<Foam::word>& Foam::ensightMesh::patches() const
+inline bool Foam::ensightMesh::needsUpdate() const
 {
-    return patchLookup_;
+    return needsUpdate_;
 }
 
 
-inline const Foam::HashTable<Foam::ensightFaces>&
-Foam::ensightMesh::boundaryPatchFaces() const
+inline bool Foam::ensightMesh::empty() const
 {
-    return boundaryPatchFaces_;
+    return
+    (
+        cellZoneParts_.empty()
+     && faceZoneParts_.empty()
+     && boundaryParts_.empty()
+    );
 }
 
 
-inline const Foam::HashTable<Foam::ensightFaces>&
-Foam::ensightMesh::faceZoneFaces() const
+inline Foam::label Foam::ensightMesh::size() const
 {
-    return faceZoneFaces_;
+    return
+    (
+        cellZoneParts_.size()
+      + faceZoneParts_.size()
+      + boundaryParts_.size()
+    );
 }
 
 
-inline void Foam::ensightMesh::write(autoPtr<ensightGeoFile>& os) const
+inline void Foam::ensightMesh::write
+(
+    autoPtr<ensightGeoFile>& os,
+    bool parallel
+) const
 {
-    write(os.ref());
+    write(os.ref(), parallel);
 }
 
 
diff --git a/src/fileFormats/ensight/mesh/ensightMeshOptions.C b/src/fileFormats/ensight/mesh/ensightMeshOptions.C
new file mode 100644
index 00000000000..831d076897f
--- /dev/null
+++ b/src/fileFormats/ensight/mesh/ensightMeshOptions.C
@@ -0,0 +1,340 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 "ensightMesh.H"
+#include "Switch.H"
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// DIY flatOutput without a leading size indicator
+static Ostream& printPatterns(Ostream& os, const wordRes& list)
+{
+    os << token::BEGIN_LIST;
+
+    bool sep = false;
+
+    for (const wordRe& item : list)
+    {
+        if (sep) os << token::SPACE;
+        os << item;
+
+        sep = true;
+    }
+    os << token::END_LIST;
+
+    return os;
+}
+
+} // End namespace
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ensightMesh::options::options()
+:
+    lazy_(false),
+    internal_(true),
+    boundary_(true),
+    cellZones_(true),
+    patchInclude_(),
+    patchExclude_(),
+    cellZoneInclude_(),
+    faceZoneInclude_()
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::ensightMesh::options::lazy() const
+{
+    return lazy_;
+}
+
+
+bool Foam::ensightMesh::options::useInternalMesh() const
+{
+    return internal_;
+}
+
+
+bool Foam::ensightMesh::options::useBoundaryMesh() const
+{
+    return boundary_;
+}
+
+
+bool Foam::ensightMesh::options::useCellZones() const
+{
+    return cellZones_;
+}
+
+
+bool Foam::ensightMesh::options::useFaceZones() const
+{
+    return faceZoneInclude_.size();
+}
+
+
+void Foam::ensightMesh::options::reset()
+{
+    internal_ = true;
+    boundary_ = true;
+    cellZones_ = true;
+    patchInclude_.clear();
+    patchExclude_.clear();
+    faceZoneInclude_.clear();
+    cellZoneInclude_.clear();
+}
+
+
+void Foam::ensightMesh::options::lazy(bool beLazy)
+{
+    lazy_ = beLazy;
+}
+
+
+void Foam::ensightMesh::options::useInternalMesh(bool on)
+{
+    internal_ = on;
+}
+
+
+void Foam::ensightMesh::options::useBoundaryMesh(bool on)
+{
+    boundary_ = on;
+
+    if (!boundary_)
+    {
+        if (patchInclude_.size())
+        {
+            patchInclude_.clear();
+
+            WarningInFunction
+                << "Deactivating boundary, removed old patch selection"
+                << endl;
+        }
+    }
+}
+
+
+void Foam::ensightMesh::options::useCellZones(bool on)
+{
+    cellZones_ = on;
+
+    if (!cellZones_ && cellZoneInclude_.size())
+    {
+        cellZoneInclude_.clear();
+
+        WarningInFunction
+            << "Deactivating cellZones, removed old zone selection"
+            << endl;
+    }
+}
+
+
+void Foam::ensightMesh::options::patchSelection
+(
+    const UList<wordRe>& patterns
+)
+{
+    patchInclude_ = wordRes(patterns);
+
+    if (!boundary_ && patchInclude_.size())
+    {
+        patchInclude_.clear();
+
+        WarningInFunction
+            << "Ignoring patch selection, boundary is disabled"
+            << endl;
+    }
+}
+
+
+void Foam::ensightMesh::options::patchSelection
+(
+    List<wordRe>&& patterns
+)
+{
+    patchInclude_ = wordRes(std::move(patterns));
+
+    if (!boundary_ && patchInclude_.size())
+    {
+        patchInclude_.clear();
+
+        WarningInFunction
+            << "Ignoring patch selection, boundary is disabled"
+            << endl;
+    }
+}
+
+
+void Foam::ensightMesh::options::patchExclude
+(
+    const UList<wordRe>& patterns
+)
+{
+    patchExclude_ = wordRes(patterns);
+}
+
+
+void Foam::ensightMesh::options::patchExclude
+(
+    List<wordRe>&& patterns
+)
+{
+    patchExclude_ = wordRes(std::move(patterns));
+}
+
+
+void Foam::ensightMesh::options::faceZoneSelection
+(
+    const UList<wordRe>& patterns
+)
+{
+    faceZoneInclude_ = wordRes(patterns);
+}
+
+
+void Foam::ensightMesh::options::faceZoneSelection
+(
+    List<wordRe>&& patterns
+)
+{
+    faceZoneInclude_ = wordRes(std::move(patterns));
+}
+
+
+void Foam::ensightMesh::options::cellZoneSelection
+(
+    const UList<wordRe>& patterns
+)
+{
+    cellZoneInclude_ = wordRes(patterns);
+
+    if (!cellZones_ && cellZoneInclude_.size())
+    {
+        cellZoneInclude_.clear();
+
+        WarningInFunction
+            << "Ignoring cellZone selection, cellZones are disabled"
+            << endl;
+    }
+}
+
+
+void Foam::ensightMesh::options::cellZoneSelection
+(
+    List<wordRe>&& patterns
+)
+{
+    cellZoneInclude_ = wordRes(std::move(patterns));
+
+    if (!cellZones_ && cellZoneInclude_.size())
+    {
+        cellZoneInclude_.clear();
+
+        WarningInFunction
+            << "Ignoring cellZone selection, cellZones are disabled"
+            << endl;
+    }
+}
+
+
+const Foam::wordRes& Foam::ensightMesh::options::patchSelection() const
+{
+    return patchInclude_;
+}
+
+
+const Foam::wordRes& Foam::ensightMesh::options::patchExclude() const
+{
+    return patchExclude_;
+}
+
+
+const Foam::wordRes& Foam::ensightMesh::options::faceZoneSelection() const
+{
+    return faceZoneInclude_;
+}
+
+
+const Foam::wordRes& Foam::ensightMesh::options::cellZoneSelection() const
+{
+    return cellZoneInclude_;
+}
+
+
+void Foam::ensightMesh::options::print(Ostream& os) const
+{
+    os << "internal: " << Switch::name(internal_) << nl;
+    os << "cellZones: " << Switch::name(cellZones_) << nl;
+    if (cellZones_)
+    {
+        os.incrIndent();
+        if (!cellZoneInclude_.empty())
+        {
+            os.writeKeyword("include");
+            printPatterns(os, cellZoneInclude_) << nl;
+        }
+        os.decrIndent();
+    }
+
+    os << "boundary: " << Switch::name(boundary_) << nl;
+    if (boundary_)
+    {
+        os.incrIndent();
+        if (!patchInclude_.empty())
+        {
+            os.writeKeyword("include");
+            printPatterns(os, patchInclude_) << nl;
+        }
+        if (!patchExclude_.empty())
+        {
+            os.writeKeyword("exclude");
+            printPatterns(os, patchExclude_) << nl;
+        }
+        os.decrIndent();
+    }
+
+    os << "faceZones: " << Switch::name(useFaceZones()) << nl;
+    if (useFaceZones())
+    {
+        os.incrIndent();
+        if (!faceZoneInclude_.empty())
+        {
+            os.writeKeyword("include");
+            printPatterns(os, faceZoneInclude_) << nl;
+        }
+        os.decrIndent();
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/output/ensightOutput.C b/src/fileFormats/ensight/output/ensightOutput.C
new file mode 100644
index 00000000000..461f8322355
--- /dev/null
+++ b/src/fileFormats/ensight/output/ensightOutput.C
@@ -0,0 +1,490 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 "ensightOutput.H"
+
+#include "cell.H"
+#include "cellShape.H"
+#include "face.H"
+#include "polyMesh.H"
+#include "ListOps.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Sizes
+
+Foam::labelList Foam::ensightOutput::Detail::getFaceSizes
+(
+    const UList<face>& faces
+)
+{
+    labelList list(faces.size());
+
+    auto outIter = list.begin();
+
+    for (const face& f : faces)
+    {
+        *outIter = f.size();
+        ++outIter;
+    }
+
+    return list;
+}
+
+
+Foam::labelList Foam::ensightOutput::Detail::getFaceSizes
+(
+    const UIndirectList<face>& faces
+)
+{
+    labelList list(faces.size());
+
+    auto outIter = list.begin();
+
+    for (const face& f : faces)
+    {
+        *outIter = f.size();
+        ++outIter;
+    }
+
+    return list;
+}
+
+
+Foam::labelList Foam::ensightOutput::Detail::getPolysNFaces
+(
+    const polyMesh& mesh,
+    const labelUList& addr
+)
+{
+    const cellList& meshCells = mesh.cells();
+
+    labelList list(addr.size());
+
+    auto outIter = list.begin();
+
+    // The number of faces per element
+    for (const label cellId : addr)
+    {
+        *outIter = meshCells[cellId].size();
+        ++outIter;
+    }
+
+    return list;
+}
+
+
+Foam::labelList Foam::ensightOutput::Detail::getPolysNPointsPerFace
+(
+    const polyMesh& mesh,
+    const labelUList& addr
+)
+{
+    const cellList& meshCells = mesh.cells();
+    const faceList& meshFaces = mesh.faces();
+
+    // Count the number of faces per element
+
+    label nTotFaces = 0;
+    for (const label cellId : addr)
+    {
+        nTotFaces += meshCells[cellId].size();
+    }
+
+    labelList list(nTotFaces);
+
+    auto outIter = list.begin();
+
+    // The number of points per element face
+    for (const label cellId : addr)
+    {
+        for (const label facei : meshCells[cellId])
+        {
+            *outIter = meshFaces[facei].size();
+            ++outIter;
+        }
+    }
+
+    return list;
+}
+
+
+void Foam::ensightOutput::writeFaceList
+(
+    ensightGeoFile& os,
+    const UList<face>& faces
+)
+{
+    for (const face& f : faces)
+    {
+        for (const label labi : f)
+        {
+            os.write(labi + 1);
+        }
+
+        os.newline();
+    }
+}
+
+
+void Foam::ensightOutput::writeFaceList
+(
+    ensightGeoFile& os,
+    const UIndirectList<face>& faces
+)
+{
+    for (const face& f : faces)
+    {
+        for (const label labi : f)
+        {
+            os.write(labi + 1);
+        }
+
+        os.newline();
+    }
+}
+
+
+void Foam::ensightOutput::writeCellShapes
+(
+    ensightGeoFile& os,
+    const UList<cellShape>& shapes
+)
+{
+    for (const cellShape& cellPoints : shapes)
+    {
+        // Convert global -> local index
+        // (note: Ensight indices start with 1)
+
+        // In ASCII, write one cell per line
+        for (const label pointi : cellPoints)
+        {
+            os.write(pointi + 1);
+        }
+
+        os.newline();
+    }
+}
+
+
+void Foam::ensightOutput::writePolysPoints
+(
+    ensightGeoFile& os,
+    const polyMesh& mesh,
+    const labelUList& addr,
+    const labelList& pointMap
+)
+{
+    const cellList& meshCells = mesh.cells();
+    const faceList& meshFaces = mesh.faces();
+    const labelList& owner = mesh.faceOwner();
+
+    for (const label cellId : addr)
+    {
+        for (const label faceId : meshCells[cellId])
+        {
+            const face& f = meshFaces[faceId];
+
+            if (faceId < owner.size() && owner[faceId] != cellId)
+            {
+                // The neighbour of an internal face
+                // - write as face::reverseFace()
+
+                os.write(pointMap[f[0]] + 1);
+                for (label pti = f.size()-1; pti > 0; --pti)
+                {
+                    os.write(pointMap[f[pti]] + 1);
+                }
+            }
+            else
+            {
+                for (const label pointi : f)
+                {
+                    os.write(pointMap[pointi] + 1);
+                }
+            }
+
+            os.newline();
+        }
+    }
+}
+
+
+void Foam::ensightOutput::writePolysPoints
+(
+    ensightGeoFile& os,
+    const cellUList& meshCells,
+    const labelUList& addr,
+    const faceUList& meshFaces,
+    const labelUList& owner
+)
+{
+    for (const label cellId : addr)
+    {
+        for (const label faceId : meshCells[cellId])
+        {
+            const face& f = meshFaces[faceId];
+
+            if (faceId < owner.size() && owner[faceId] != cellId)
+            {
+                // The neighbour of an internal face
+                // - write as face::reverseFace()
+
+                os.write(f[0] + 1);
+                for (label pti = f.size()-1; pti > 0; --pti)
+                {
+                    os.write(f[pti] + 1);
+                }
+            }
+            else
+            {
+                for (const label pointi : f)
+                {
+                    os.write(pointi + 1);
+                }
+            }
+
+            os.newline();
+        }
+    }
+}
+
+
+void Foam::ensightOutput::writeFaceConnectivity
+(
+    ensightGeoFile& os,
+    const ensightFaces::elemType etype,
+    const label nTotal,
+    const faceUList& faces,
+    bool parallel
+)
+{
+    if (!nTotal)
+    {
+        return;
+    }
+
+    parallel = parallel && Pstream::parRun();
+
+    const label nSlaves = (parallel ? Pstream::nProcs() : 0);
+
+    if (Pstream::master())
+    {
+        os.writeKeyword(ensightFaces::key(etype));
+        os.write(nTotal);
+        os.newline();
+    }
+
+    if (etype == ensightFaces::NSIDED)
+    {
+        // Face sizes (number of points per face)
+
+        labelList send(ensightOutput::Detail::getFaceSizes(faces));
+
+        if (Pstream::master())
+        {
+            os.writeLabels(send);
+
+            for (int slave=1; slave < nSlaves; ++slave)
+            {
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+                labelList recv(fromSlave);
+
+                os.writeLabels(recv);
+            }
+        }
+        else if (nSlaves)
+        {
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            toMaster << send;
+        }
+    }
+
+
+    // List of points id for each face
+    if (Pstream::master())
+    {
+        writeFaceList(os, faces);
+
+        for (int slave=1; slave < nSlaves; ++slave)
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+            List<face> recv(fromSlave);
+
+            writeFaceList(os, recv);
+        }
+    }
+    else if (nSlaves)
+    {
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        toMaster << faces;
+    }
+}
+
+
+void Foam::ensightOutput::writeFaceConnectivity
+(
+    ensightGeoFile& os,
+    const ensightFaces::elemType etype,
+    const label nTotal,
+    const UIndirectList<face>& faces,
+    bool parallel
+)
+{
+    if (!nTotal)
+    {
+        return;
+    }
+
+    parallel = parallel && Pstream::parRun();
+
+    const label nSlaves = (parallel ? Pstream::nProcs() : 0);
+
+    if (Pstream::master())
+    {
+        os.writeKeyword(ensightFaces::key(etype));
+        os.write(nTotal);
+        os.newline();
+    }
+
+    if (etype == ensightFaces::NSIDED)
+    {
+        // Face sizes (number of points per face)
+
+        labelList send(ensightOutput::Detail::getFaceSizes(faces));
+
+        if (Pstream::master())
+        {
+            os.writeLabels(send);
+
+            for (int slave=1; slave < nSlaves; ++slave)
+            {
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+                labelList recv(fromSlave);
+
+                os.writeLabels(recv);
+            }
+        }
+        else if (nSlaves)
+        {
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            toMaster << send;
+        }
+    }
+
+
+    // List of points id per face
+
+    if (Pstream::master())
+    {
+        writeFaceList(os, faces);
+
+        for (int slave=1; slave < nSlaves; ++slave)
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+            List<face> recv(fromSlave);
+
+            writeFaceList(os, recv);
+        }
+    }
+    else if (nSlaves)
+    {
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        toMaster << faces;
+    }
+}
+
+
+void Foam::ensightOutput::writeFaceConnectivity
+(
+    ensightGeoFile& os,
+    const ensightFaces& part,
+    const faceUList& faces,
+    bool parallel
+)
+{
+    for (label typei=0; typei < ensightFaces::nTypes; ++typei)
+    {
+        const auto etype = ensightFaces::elemType(typei);
+
+        writeFaceConnectivity
+        (
+            os,
+            etype,
+            part.total(etype),
+            UIndirectList<face>(faces, part.faceIds(etype)),
+            parallel
+        );
+    }
+}
+
+
+void Foam::ensightOutput::writeFaceConnectivityPresorted
+(
+    ensightGeoFile& os,
+    const ensightFaces& part,
+    const faceUList& faces,
+    bool parallel
+)
+{
+    for (label typei=0; typei < ensightFaces::nTypes; ++typei)
+    {
+        const auto etype = ensightFaces::elemType(typei);
+
+        writeFaceConnectivity
+        (
+            os,
+            etype,
+            part.total(etype),
+            SubList<face>(faces, part.range(etype)),
+            parallel
+        );
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/output/ensightOutput.H b/src/fileFormats/ensight/output/ensightOutput.H
index 4c6a23fb78c..4a2f77dcbf4 100644
--- a/src/fileFormats/ensight/output/ensightOutput.H
+++ b/src/fileFormats/ensight/output/ensightOutput.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,6 +30,7 @@ Description
     A collection of functions for writing ensight file content.
 
 SourceFiles
+    ensightOutput.C
     ensightOutputTemplates.C
 
 \*---------------------------------------------------------------------------*/
@@ -38,97 +39,276 @@ SourceFiles
 #define ensightOutput_H
 
 #include "ensightFile.H"
+#include "ensightGeoFile.H"
 #include "ensightCells.H"
 #include "ensightFaces.H"
 #include "ensightPTraits.H"
 
+#include "faceListFwd.H"
+#include "cellListFwd.H"
+
+#include "ListOps.H"
+#include "ListListOps.H"
+#include "IndirectList.H"
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+// Local definitions, could be relocated to ListListOps directly
+
+namespace Foam
+{
+namespace ListListOps
+{
+
+//- Return the sizes of the sub-lists
+template<class T, class Addr, class AccessOp>
+labelList subSizes
+(
+    const IndirectListBase<T, Addr>& lists,
+    AccessOp aop
+)
+{
+    labelList output(lists.size());
+    auto out = output.begin();
+
+    for (const T& sub : lists)
+    {
+        *out = aop(sub).size();
+        ++out;
+    }
+
+    return output;
+}
+
+
+//- Inplace renumber the values (not the indices) of a list of lists.
+//  Negative IntListType elements are left untouched.
+template<class IntListType>
+void inplaceRenumber
+(
+    const labelUList& oldToNew,
+    IntListType& lists
+)
+{
+    for (auto& sub : lists)
+    {
+        for (auto& item : sub)
+        {
+            if (item >= 0)
+            {
+                item = oldToNew[item];
+            }
+        }
+    }
+}
+
+} // End namespace ListListOps
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
+
+// Forward Declarations
+class cellShape;
+class polyMesh;
+
 namespace ensightOutput
 {
 
 /*---------------------------------------------------------------------------*\
-                        Namespace ensightOutput::Detail
+                                Geometry Output
 \*---------------------------------------------------------------------------*/
 
-//- \brief Implementation details and output backends that would not normally
-//- be called directly by a user.
+//- Write list of faces
+void writeFaceList
+(
+    ensightGeoFile& os,
+    const UList<face>& faces
+);
 
-namespace Detail
-{
+//- Write list of faces
+void writeFaceList
+(
+    ensightGeoFile& os,
+    const UIndirectList<face>& faces
+);
 
-//- Write field content (component-wise) for the given ensight element type
-template<template<typename> class FieldContainer, class Type>
-bool writeFieldComponents
+//- Write cell connectivity via cell shapes
+void writeCellShapes
 (
-    const char* key,
-    const FieldContainer<Type>& fld,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
+    ensightGeoFile& os,
+    const UList<cellShape>& shapes
 );
 
 
-//- Write a field of faces values as an indirect list,
-//- using the face ids from ensightFaces
-template<class Type>
-bool writeFaceField
+//- Write the point ids per poly element.
+//  Points have been already renumbered
+void writePolysPoints
 (
-    const Field<Type>& fld,
+    ensightGeoFile& os,
+    const cellUList& meshCells,
+    const labelUList& addr,     //!< Cell ids to write
+    const faceUList& meshFaces,
+    const labelUList& faceOwner
+);
+
+//- Write the point ids per poly element, with point renumbering
+void writePolysPoints
+(
+    ensightGeoFile& os,
+    const polyMesh& mesh,
+    const labelUList& addr,     //!< Cell ids to write
+    const labelList& pointMap   //!< Point map to use
+);
+
+
+//- Write the regular face connectivity for specified type and
+//- and specified faces
+void writeFaceConnectivity
+(
+    ensightGeoFile& os,
+    const ensightFaces::elemType etype,
+    const label nTotal,
+    const UIndirectList<face>& faces,
+    bool parallel               //!< Collective write?
+);
+
+
+//- Write the regular face connectivity for specified type
+void writeFaceConnectivity
+(
+    ensightGeoFile& os,
+    const ensightFaces::elemType etype,
+    const label nTotal,
+    const faceUList& faces,
+    bool parallel               //!< Collective write?
+);
+
+
+//- Write the face connectivity for the part
+void writeFaceConnectivity
+(
+    ensightGeoFile& os,
     const ensightFaces& part,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
+    const faceUList& faces,
+    bool parallel               //!< Collective write?
 );
 
 
-//- Write a sub-field of faces values as an indirect list,
-//- using the sublist sizing information from ensightFaces
-template<class Type>
-bool writeFaceSubField
+//- Write the \b presorted face connectivity for the part
+//  This is a special case when the list of faces is already in
+//  ensight sorted order
+void writeFaceConnectivityPresorted
 (
-    const Field<Type>& fld,
+    ensightGeoFile& os,
     const ensightFaces& part,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
+    const faceUList& faces,
+    bool parallel               //!< Collective write?
 );
 
 
+/*---------------------------------------------------------------------------*\
+                                Field Output
+\*---------------------------------------------------------------------------*/
 
 //- Write a field of cell values as an indirect list,
 //- using the cell ids from ensightCells
 template<class Type>
-bool writeCellField
+bool writeField
 (
+    ensightFile& os,
     const Field<Type>& fld,
     const ensightCells& part,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
+    bool parallel  //!< Collective write?
 );
 
-} // End namespace Detail
+//- Write a field of faces values as an indirect list,
+//- using the face ids from ensightFaces
+template<class Type>
+bool writeField
+(
+    ensightFile& os,
+    const Field<Type>& fld,
+    const ensightFaces& part,
+    bool parallel  //!< Collective write?
+);
 
 
 /*---------------------------------------------------------------------------*\
-                        Namespace ensightOutput::Serial
+                        Namespace ensightOutput::Detail
 \*---------------------------------------------------------------------------*/
 
-//- \brief Output routines that are either designed for serial-only,
-//- or for which parallelization is pending.
-namespace Serial
+//- \brief Implementation details and output backends that would not normally
+//- be called directly by a user.
+
+namespace Detail
 {
 
-//- Write a field of point (node) values (already compacted?)
+//- Return sizes of faces in the list
+labelList getFaceSizes(const UList<face>& faces);
+
+//- Return sizes of faces in the list
+labelList getFaceSizes(const UIndirectList<face>& faces);
+
+//- The number of faces per poly element
+labelList getPolysNFaces(const polyMesh& mesh, const labelUList& addr);
+
+//- The number of points for each face of the poly elements
+labelList getPolysNPointsPerFace(const polyMesh& mesh, const labelUList& addr);
+
+
+//- Copy specified field component into a scalarField
+//  works for various lists types
+template<template<typename> class FieldContainer, class Type>
+void copyComponent
+(
+    scalarField& res,
+    const FieldContainer<Type>& input,
+    const direction cmpt
+);
+
+
+//- Write coordinates (component-wise) for the given part
+template<template<typename> class FieldContainer>
+bool writeCoordinates
+(
+    ensightGeoFile& os,
+    const label partId,
+    const word& partName,
+    const label nPoints,
+    const FieldContainer<Foam::point>& fld,
+    bool parallel  //!< Collective write?
+);
+
+
+//- Write field content (component-wise) for the given ensight element type
+template<template<typename> class FieldContainer, class Type>
+bool writeFieldComponents
+(
+    ensightFile& os,
+    const char* key,
+    const FieldContainer<Type>& fld,
+    bool parallel  //!< Collective write?
+);
+
+
+//- Write a sub-field of faces values as an indirect list,
+//- using the sub-list sizing information from ensightFaces
 template<class Type>
-bool writePointField
+bool writeFaceSubField
 (
+    ensightFile& os,
     const Field<Type>& fld,
     const ensightFaces& part,
-    ensightFile& os
+    bool parallel  //!< Collective write?
 );
 
-} // End namespace Serial
+
+} // End namespace Detail
+
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
diff --git a/src/fileFormats/ensight/output/ensightOutputTemplates.C b/src/fileFormats/ensight/output/ensightOutputTemplates.C
index 13a9eeaa01a..a0985b2cf13 100644
--- a/src/fileFormats/ensight/output/ensightOutputTemplates.C
+++ b/src/fileFormats/ensight/output/ensightOutputTemplates.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,73 +31,72 @@ License
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 template<template<typename> class FieldContainer, class Type>
-bool Foam::ensightOutput::Detail::writeFieldComponents
+void Foam::ensightOutput::Detail::copyComponent
 (
-    const char* key,
-    const FieldContainer<Type>& fld,
-    ensightFile& os,
-    bool parallel
+    scalarField& res,
+    const FieldContainer<Type>& input,
+    const direction cmpt
 )
 {
-    // Preliminary checks
-    // ~~~~~~~~~~~~~~~~~~
-
-    parallel = parallel && Pstream::parRun();
+    res.resize(input.size());
 
-    bool hasField = !fld.empty();
+    auto iter = res.begin();
 
-    if (parallel)
+    for (const Type& val : input)
     {
-        reduce(hasField, orOp<bool>());
+        *iter = component(val, cmpt);
+        ++iter;
     }
+}
+
 
-    // Nothing to write
-    if (!hasField) return false;
+template<template<typename> class FieldContainer>
+bool Foam::ensightOutput::Detail::writeCoordinates
+(
+    ensightGeoFile& os,
+    const label partId,
+    const word& partName,
+    const label nPoints,
+    const FieldContainer<Foam::point>& fld,
+    bool parallel
+)
+{
+    parallel = parallel && Pstream::parRun();
 
-    // End preliminary checks
-    // ~~~~~~~~~~~~~~~~~~~~~~
+    const label nSlaves = (parallel ? Pstream::nProcs() : 0);
 
+    // Using manual copyComponent(...) instead of fld.component() to support
+    // indirect lists etc.
+
+    scalarField send(fld.size());
 
     if (Pstream::master())
     {
-        os.writeKeyword(key);
+        // Serial output, or parallel (master)
 
-        if (!parallel)
-        {
-            // Serial output
-            for (direction d=0; d < pTraits<Type>::nComponents; ++d)
-            {
-                const label cmpt = ensightPTraits<Type>::componentOrder[d];
+        os.beginPart(partId, partName);
+        os.beginCoordinates(nPoints);
 
-                os.writeList(fld.component(cmpt));
-            }
-        }
-        else
+        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
         {
-            // Parallel (master)
+            copyComponent(send, fld, cmpt);
+            os.writeList(send);
 
-            for (direction d=0; d < pTraits<Type>::nComponents; ++d)
+            for (int slave=1; slave < nSlaves; ++slave)
             {
-                const label cmpt = ensightPTraits<Type>::componentOrder[d];
-
-                os.writeList(fld.component(cmpt));
-
-                for (int slave=1; slave<Pstream::nProcs(); ++slave)
-                {
-                    IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                    scalarField received(fromSlave);
-                    os.writeList(received);
-                }
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+                scalarField recv(fromSlave);
+                os.writeList(recv);
             }
         }
     }
-    else if (parallel)
+    else if (nSlaves)
     {
         // Parallel (slaves)
 
-        for (direction d=0; d < pTraits<Type>::nComponents; ++d)
+        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
         {
-            const label cmpt = ensightPTraits<Type>::componentOrder[d];
+            copyComponent(send, fld, cmpt);
 
             OPstream toMaster
             (
@@ -105,8 +104,7 @@ bool Foam::ensightOutput::Detail::writeFieldComponents
                 Pstream::masterNo()
             );
 
-            toMaster
-                << fld.component(cmpt);
+            toMaster << send;
         }
     }
 
@@ -114,52 +112,77 @@ bool Foam::ensightOutput::Detail::writeFieldComponents
 }
 
 
-template<class Type>
-bool Foam::ensightOutput::Detail::writeFaceField
+template<template<typename> class FieldContainer, class Type>
+bool Foam::ensightOutput::Detail::writeFieldComponents
 (
-    const Field<Type>& fld,
-    const ensightFaces& part,
     ensightFile& os,
+    const char* key,
+    const FieldContainer<Type>& fld,
     bool parallel
 )
 {
-    // Preliminary checks
-    // ~~~~~~~~~~~~~~~~~~
-
     parallel = parallel && Pstream::parRun();
 
-    bool hasGeom = (parallel ? part.total() : part.size());
-    bool hasField = !fld.empty();
+    const label nSlaves = (parallel ? Pstream::nProcs() : 0);
 
-    if (parallel)
+    // Preliminary checks
     {
-        // Used 'pre-reduced' information for hasGeom
-        reduce(hasField, orOp<bool>());
+        bool hasField = !fld.empty();
+
+        if (parallel)
+        {
+            reduce(hasField, orOp<bool>());
+        }
+
+        // No field
+        if (!hasField) return false;
     }
 
-    // Nothing to write
-    if (!hasGeom || !hasField) return false;
 
-    // End preliminary checks
-    // ~~~~~~~~~~~~~~~~~~~~~~
+    // Using manual copyComponent(...) instead of fld.component() to support
+    // indirect lists etc.
 
+    scalarField send(fld.size());
 
     if (Pstream::master())
     {
-        os.beginPart(part.index());
-    }
+        // Serial output, or parallel (master)
 
-    for (int typei=0; typei < ensightFaces::nTypes; ++typei)
+        os.writeKeyword(key);
+
+        for (direction d=0; d < pTraits<Type>::nComponents; ++d)
+        {
+            const direction cmpt = ensightPTraits<Type>::componentOrder[d];
+
+            copyComponent(send, fld, cmpt);
+            os.writeList(send);
+
+            for (label slave=1; slave < nSlaves; ++slave)
+            {
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+                scalarField recv(fromSlave);
+                os.writeList(recv);
+            }
+        }
+    }
+    else if (nSlaves)
     {
-        const ensightFaces::elemType what = ensightFaces::elemType(typei);
+        // Parallel (slaves)
 
-        writeFieldComponents
-        (
-            ensightFaces::key(what),
-            Field<Type>(fld, part.faceIds(what)),
-            os,
-            parallel
-        );
+        for (direction d=0; d < pTraits<Type>::nComponents; ++d)
+        {
+            const direction cmpt = ensightPTraits<Type>::componentOrder[d];
+
+            copyComponent(send, fld, cmpt);
+
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            toMaster << send;
+        }
     }
 
     return true;
@@ -169,31 +192,29 @@ bool Foam::ensightOutput::Detail::writeFaceField
 template<class Type>
 bool Foam::ensightOutput::Detail::writeFaceSubField
 (
+    ensightFile& os,
     const Field<Type>& fld,
     const ensightFaces& part,
-    ensightFile& os,
     bool parallel
 )
 {
-    // Preliminary checks
-    // ~~~~~~~~~~~~~~~~~~
-
     parallel = parallel && Pstream::parRun();
 
-    bool hasGeom = (parallel ? part.total() : part.size());
-    bool hasField = !fld.empty();
-
-    if (parallel)
+    // Preliminary checks: total() contains pre-reduced information
     {
-        // Used 'pre-reduced' information for hasGeom
-        reduce(hasField, orOp<bool>());
-    }
+        // No geometry
+        if (parallel ? !part.total() : !part.size()) return false;
 
-    // Nothing to write
-    if (!hasGeom || !hasField) return false;
+        bool hasField = !fld.empty();
 
-    // End preliminary checks
-    // ~~~~~~~~~~~~~~~~~~~~~~
+        if (parallel)
+        {
+            reduce(hasField, orOp<bool>());
+        }
+
+        // No field
+        if (!hasField) return false;
+    }
 
 
     if (Pstream::master())
@@ -201,23 +222,19 @@ bool Foam::ensightOutput::Detail::writeFaceSubField
         os.beginPart(part.index());
     }
 
+    labelList localAddr;
 
-    label start = 0; // The start of the sub-list
     for (int typei=0; typei < ensightFaces::nTypes; ++typei)
     {
-        const ensightFaces::elemType what = ensightFaces::elemType(typei);
-
-        const label size = part.faceIds(what).size();
+        const auto etype = ensightFaces::elemType(typei);
 
-        writeFieldComponents
+        ensightOutput::Detail::writeFieldComponents
         (
-            ensightFaces::key(what),
-            SubField<Type>(fld, size, start),
             os,
+            ensightFaces::key(etype),
+            SubField<Type>(fld, part.range(etype)),
             parallel
         );
-
-        start += size;  // Advance the start for next sub-list
     }
 
     return true;
@@ -225,79 +242,80 @@ bool Foam::ensightOutput::Detail::writeFaceSubField
 
 
 template<class Type>
-bool Foam::ensightOutput::Serial::writePointField
+bool Foam::ensightOutput::writeField
 (
+    ensightFile& os,
     const Field<Type>& fld,
-    const ensightFaces& part,
-    ensightFile& os
+    const ensightCells& part,
+    bool parallel
 )
 {
-    // Preliminary checks
-    // ~~~~~~~~~~~~~~~~~~
-
-    bool parallel = false && Pstream::parRun();
-
-    bool hasGeom = (parallel ? part.total() : part.size());
-    bool hasField = !fld.empty();
+    parallel = parallel && Pstream::parRun();
 
-    if (parallel)
+    // Preliminary checks: total() contains pre-reduced information
     {
-        // Used 'pre-reduced' information for hasGeom
-        reduce(hasField, orOp<bool>());
-    }
+        // No geometry
+        if (parallel ? !part.total() : !part.size()) return false;
 
-    // Nothing to write
-    if (!hasGeom || !hasField) return false;
+        bool hasField = !fld.empty();
 
-    // End preliminary checks
-    // ~~~~~~~~~~~~~~~~~~~~~~
+        if (parallel)
+        {
+            reduce(hasField, orOp<bool>());
+        }
 
+        // No field
+        if (!hasField) return false;
+    }
 
     if (Pstream::master())
     {
         os.beginPart(part.index());
     }
 
-    ensightOutput::Detail::writeFieldComponents
-    (
-        "coordinates",
-        fld,
-        os,
-        parallel
-    );
+    for (int typei=0; typei < ensightCells::nTypes; ++typei)
+    {
+        const auto etype = ensightCells::elemType(typei);
+
+        ensightOutput::Detail::writeFieldComponents
+        (
+            os,
+            ensightCells::key(etype),
+            UIndirectList<Type>(fld, part.cellIds(etype)),
+            parallel
+        );
+    }
 
     return true;
 }
 
 
 template<class Type>
-bool Foam::ensightOutput::Detail::writeCellField
+bool Foam::ensightOutput::writeField
 (
-    const Field<Type>& fld,
-    const ensightCells& part,
     ensightFile& os,
+    const Field<Type>& fld,
+    const ensightFaces& part,
     bool parallel
 )
 {
-    // Preliminary checks
-    // ~~~~~~~~~~~~~~~~~~
-
     parallel = parallel && Pstream::parRun();
 
-    bool hasGeom = (parallel ? part.total() : part.size());
-    bool hasField = !fld.empty();
-
-    if (parallel)
+    // Preliminary checks: total() contains pre-reduced information
     {
-        // Used 'pre-reduced' information for hasGeom
-        reduce(hasField, orOp<bool>());
-    }
+        // No geometry
+        if (parallel ? !part.total() : !part.size()) return false;
 
-    // Nothing to write
-    if (!hasGeom || !hasField) return false;
+        bool hasField = !fld.empty();
 
-    // End preliminary checks
-    // ~~~~~~~~~~~~~~~~~~~~~~
+        if (parallel)
+        {
+            reduce(hasField, orOp<bool>());
+        }
+
+        // No field
+        if (!hasField) return false;
+    }
 
 
     if (Pstream::master())
@@ -305,15 +323,15 @@ bool Foam::ensightOutput::Detail::writeCellField
         os.beginPart(part.index());
     }
 
-    for (int typei=0; typei < ensightCells::nTypes; ++typei)
+    for (int typei=0; typei < ensightFaces::nTypes; ++typei)
     {
-        const ensightCells::elemType what = ensightCells::elemType(typei);
+        const auto etype = ensightFaces::elemType(typei);
 
-        Detail::writeFieldComponents
+        ensightOutput::Detail::writeFieldComponents
         (
-            ensightCells::key(what),
-            Field<Type>(fld, part.cellIds(what)),
             os,
+            ensightFaces::key(etype),
+            UIndirectList<Type>(fld, part.faceIds(etype)),
             parallel
         );
     }
diff --git a/src/fileFormats/ensight/part/ensightCells.C b/src/fileFormats/ensight/part/cells/ensightCells.C
similarity index 68%
rename from src/fileFormats/ensight/part/ensightCells.C
rename to src/fileFormats/ensight/part/cells/ensightCells.C
index 728366cee1a..625977a99ae 100644
--- a/src/fileFormats/ensight/part/ensightCells.C
+++ b/src/fileFormats/ensight/part/cells/ensightCells.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -33,31 +33,41 @@ License
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
+namespace Foam
+{
+    defineTypeNameAndDebug(ensightCells, 0);
+}
+
 const char* Foam::ensightCells::elemNames[5] =
     { "tetra4", "pyramid5", "penta6", "hexa8", "nfaced" };
 
+static_assert
+(
+    Foam::ensightCells::nTypes == 5,
+    "Support exactly 5 cell types (tetra4, pyramid5, penta6, hexa8, nfaced)"
+);
+
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 void Foam::ensightCells::resizeAll()
 {
-    // overall required size
-    label n = 0;
-    forAll(sizes_, typei)
-    {
-        n += sizes_[typei];
-    }
-    address_.setSize(n, Zero);
+    // Assign sub-list offsets, determine overall size
 
-    // assign corresponding sub-lists
-    n = 0;
-    forAll(sizes_, typei)
+    label len = 0;
+
+    auto iter = offsets_.begin();
+
+    *iter = 0;
+    for (const label n : sizes_)
     {
-        slices_[typei].setStart(n);
-        slices_[typei].setSize(sizes_[typei]);
+        len += n;
 
-        n += sizes_[typei];
+        *(++iter) = len;
     }
+
+    // The addressing space
+    addressing().resize(len, Zero);
 }
 
 
@@ -65,38 +75,17 @@ void Foam::ensightCells::resizeAll()
 
 Foam::ensightCells::ensightCells()
 :
-    ensightCells(0)
-{}
-
-
-Foam::ensightCells::ensightCells(const label partIndex)
-:
-    index_(partIndex),
-    address_(),
-    slices_(),
+    ensightPart(),
+    offsets_(Zero),
     sizes_(Zero)
-{
-    resizeAll(); // adjust allocation/sizing
-}
+{}
 
 
-Foam::ensightCells::ensightCells(const ensightCells& obj)
+Foam::ensightCells::ensightCells(const string& description)
 :
-    index_(obj.index_),
-    address_(obj.address_),
-    slices_(),
-    sizes_()
+    ensightCells()
 {
-    // Save the total (reduced) sizes
-    FixedList<label, 5> totSizes = obj.sizes_;
-
-    // Need local sizes for the resize operation
-    this->sizes_ = obj.sizes();
-
-    resizeAll(); // Adjust allocation/sizing
-
-    // Restore total (reduced) sizes
-    this->sizes_ = totSizes;
+    rename(description);
 }
 
 
@@ -105,9 +94,10 @@ Foam::ensightCells::ensightCells(const ensightCells& obj)
 Foam::FixedList<Foam::label, 5> Foam::ensightCells::sizes() const
 {
     FixedList<label, 5> count;
-    forAll(slices_, typei)
+
+    forAll(count, typei)
     {
-        count[typei] = slices_[typei].size();
+        count[typei] = size(elemType(typei));
     }
 
     return count;
@@ -127,17 +117,25 @@ Foam::label Foam::ensightCells::total() const
 
 void Foam::ensightCells::clear()
 {
-    sizes_ = Zero;  // reset sizes
-    resizeAll();
+    clearOut();
+
+    ensightPart::clear();
+
+    sizes_ = Zero;
+    offsets_ = Zero;
 }
 
 
+void Foam::ensightCells::clearOut()
+{}
+
+
 void Foam::ensightCells::reduce()
 {
     // No listCombineGather, listCombineScatter for FixedList
     forAll(sizes_, typei)
     {
-        sizes_[typei] = slices_[typei].size();
+        sizes_[typei] = size(elemType(typei));
         Foam::reduce(sizes_[typei], sumOp<label>());
     }
 }
@@ -145,12 +143,15 @@ void Foam::ensightCells::reduce()
 
 void Foam::ensightCells::sort()
 {
-    forAll(slices_, typei)
+    for (int typei=0; typei < nTypes; ++typei)
     {
-        if (slices_[typei].size())
+        const labelRange sub(range(elemType(typei)));
+
+        if (!sub.empty())
         {
-            SubList<label> idLst(address_, slices_[typei]);
-            Foam::sort(idLst);
+            SubList<label> ids(addressing(), sub);
+
+            Foam::sort(ids);
         }
     }
 }
@@ -178,59 +179,56 @@ void Foam::ensightCells::classifyImpl
     {
         const cellModel& model = shapes[id].model();
 
-        enum elemType what = NFACED;
+        elemType etype(NFACED);
         if (model == tet)
         {
-            what = TETRA4;
+            etype = TETRA4;
         }
         else if (model == pyr)
         {
-            what = PYRAMID5;
+            etype = PYRAMID5;
         }
         else if (model == prism)
         {
-            what = PENTA6;
+            etype = PENTA6;
         }
         else if (model == hex)
         {
-            what = HEXA8;
+            etype = HEXA8;
         }
 
-        sizes_[what]++;
+        ++sizes_[etype];
     }
 
     resizeAll();    // adjust allocation
     sizes_ = Zero;  // reset sizes - use for local indexing here
 
+
     // Pass 2: Assign cell-id per shape type
 
     for (const label id : cellIds)
     {
         const cellModel& model = shapes[id].model();
 
-        enum elemType what = NFACED;
+        elemType etype(NFACED);
         if (model == tet)
         {
-            what = TETRA4;
+            etype = TETRA4;
         }
         else if (model == pyr)
         {
-            what = PYRAMID5;
+            etype = PYRAMID5;
         }
         else if (model == prism)
         {
-            what = PENTA6;
+            etype = PENTA6;
         }
         else if (model == hex)
         {
-            what = HEXA8;
+            etype = HEXA8;
         }
 
-        // eg, the processor local cellId
-        labelUList slice = address_[slices_[what]];
-
-        slice[sizes_[what]] = id;
-        sizes_[what]++;
+        add(etype, id);
     }
 }
 
@@ -262,4 +260,28 @@ void Foam::ensightCells::classify
 }
 
 
+void Foam::ensightCells::writeDict(Ostream& os, const bool full) const
+{
+    os.beginBlock(type());
+
+    os.writeEntry("id",     index()+1); // Ensight starts with 1
+    os.writeEntry("name",   name());
+    os.writeEntry("size",   size());
+
+    if (full)
+    {
+        for (int typei=0; typei < ensightCells::nTypes; ++typei)
+        {
+            const auto etype = ensightCells::elemType(typei);
+
+            os.writeKeyword(ensightCells::key(etype));
+
+            cellIds(etype).writeList(os, 0) << endEntry;  // Flat output
+        }
+    }
+
+    os.endBlock();
+}
+
+
 // ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/cells/ensightCells.H b/src/fileFormats/ensight/part/cells/ensightCells.H
new file mode 100644
index 00000000000..d16dcc7a86c
--- /dev/null
+++ b/src/fileFormats/ensight/part/cells/ensightCells.H
@@ -0,0 +1,279 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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::ensightCells
+
+Description
+    Sorting/classification of cells (3D) into corresponding ensight element
+    types.
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ensightCells_H
+#define ensightCells_H
+
+#include "ensightPart.H"
+#include "FixedList.H"
+#include "Map.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class bitSet;
+class polyMesh;
+
+/*---------------------------------------------------------------------------*\
+                        Class ensightCells Declaration
+\*---------------------------------------------------------------------------*/
+
+class ensightCells
+:
+    public ensightPart
+{
+public:
+
+    // Public Data
+
+        //- Supported ensight 'Cell' element types
+        //  Must be zero-based since they are also for internal bookkeeping.
+        enum elemType
+        {
+            TETRA4 = 0, //!< "tetra4"
+            PYRAMID5,   //!< "pyramid5"
+            PENTA6,     //!< "penta6"
+            HEXA8,      //!< "hexa8"
+            NFACED      //!< "nfaced"
+        };
+
+        //- Number of 'Cell' element types (5)
+        static constexpr int nTypes = 5;
+
+        //- The ensight 'Cell' element type names
+        static const char* elemNames[nTypes];
+
+
+    // Static Functions
+
+        //- The ensight element name for the specified 'Cell' type
+        inline static const char* key(const elemType etype);
+
+
+private:
+
+    // Private Data
+
+        //- Begin/end offsets for address of each element type
+        FixedList<label, nTypes+1> offsets_;
+
+        //- List of global sizes for each element type.
+        //  Used temporarily for local sizes when building the element lists.
+        FixedList<label, nTypes> sizes_;
+
+
+    // Private Member Functions
+
+        //- Low-level internal addition routine
+        inline void add(const elemType etype, label id);
+
+        //- Use temporarily stored sizes to redimension the element lists
+        void resizeAll();
+
+        //- Classify cell types, set element lists for selection (implemention)
+        template<class Addressing>
+        void classifyImpl(const polyMesh& mesh, const Addressing& cellIds);
+
+
+        label meshPointMapppings
+        (
+            const polyMesh& mesh,
+            labelList& pointToGlobal,  // Can also be labelList::null()
+            labelList& uniqueMeshPointLabels,
+            bool parallel
+        ) const;
+
+
+        //- Write cell connectivity for polyhedral cells
+        static void writePolysConnectivity
+        (
+            ensightGeoFile& os,
+            const polyMesh& mesh,
+            const ensightCells& part,
+            const labelList& pointToGlobal,
+            bool parallel
+        );
+
+        //- Write cell connectivity for specified (non-poly) type
+        static void writeShapeConnectivity
+        (
+            ensightGeoFile& os,
+            const polyMesh& mesh,
+            const ensightCells::elemType etype,
+            const ensightCells& part,
+            const labelList& pointToGlobal,
+            bool parallel
+        );
+
+
+public:
+
+    //- Declare type-name, virtual type (with debug switch)
+    TypeName("ensightCells");
+
+
+    // Constructors
+
+        //- Default construct, with part index 0
+        ensightCells();
+
+        //- Default construct, with description/partName
+        explicit ensightCells(const string& description);
+
+
+    //- Destructor
+    virtual ~ensightCells() = default;
+
+
+
+    // Member Functions
+
+    // Access
+
+        //- Processor-local size of all elements.
+        using ensightPart::size;
+
+        //- Processor-local size of the specified element type.
+        inline label size(const elemType etype) const;
+
+        //- Processor-local offset/size of element type.
+        inline labelRange range(const elemType etype) const;
+
+        //- The global size of all element types.
+        //  This value is only meaningful after a reduce operation.
+        label total() const;
+
+        //- The global size of the specified element type.
+        //  This value is only meaningful after a reduce operation.
+        inline label total(const elemType etype) const;
+
+        //- The global sizes for each element type.
+        //  This value is only meaningful after a reduce operation.
+        inline const FixedList<label, nTypes>& totals() const;
+
+        //- Processor-local sizes per element type.
+        FixedList<label, nTypes> sizes() const;
+
+        //- Processor-local cell ids of all elements
+        inline const labelList& cellIds() const;
+
+        //- Processor-local cell ids of the specified element type
+        inline const labelUList cellIds(const elemType etype) const;
+
+
+    // Addressing
+
+        //- Mesh point map.
+        //  Map mesh point index to local (compact) point index
+        Map<label> meshPointMap(const polyMesh& mesh) const;
+
+
+    // Edit
+
+        //- Classify cell types and set the element lists.
+        void classify(const polyMesh& mesh);
+
+        //- Classify cell types and set element lists,
+        //- using a subgroup of cells (eg, from a cellZone etc).
+        void classify(const polyMesh& mesh, const labelUList& cellIds);
+
+        //- Classify cell types and set element lists,
+        //- using a subgroup of cells
+        void classify(const polyMesh& mesh, const bitSet& selection);
+
+
+        //- Clear any demand-driven data
+        void clearOut();
+
+        //- Set addressable sizes to zero, free up addressing memory.
+        void clear();
+
+        //- Sum element counts across all processes.
+        void reduce();
+
+        //- Sort element lists numerically.
+        void sort();
+
+
+    // Advanced (use with caution)
+
+        //- Increase cell ids by specified offset value
+        //  Eg, to change zone local Ids to global Ids
+        inline void incrCellIds(const label off);
+
+        //- Decrease face ids by specified offset value
+        //  Eg, to change global Ids to zone local Ids
+        inline void decrCellIds(const label off);
+
+
+
+    // Output
+
+        //- Globally unique mesh points. Required when writing point fields.
+        label uniqueMeshPoints
+        (
+            const polyMesh& mesh,
+            labelList& uniqueMeshPointLabels,
+            bool parallel
+        ) const;
+
+
+        //- Write information about the object as a dictionary,
+        //- optionally write all element addresses
+        virtual void writeDict(Ostream& os, const bool full=false) const;
+
+        //- Write geometry, using a mesh reference (serial only)
+        virtual void write
+        (
+            ensightGeoFile& os,
+            const polyMesh& mesh,
+            bool parallel
+        ) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "ensightCellsI.H"
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/cells/ensightCellsAddr.C b/src/fileFormats/ensight/part/cells/ensightCellsAddr.C
new file mode 100644
index 00000000000..09c9f62af90
--- /dev/null
+++ b/src/fileFormats/ensight/part/cells/ensightCellsAddr.C
@@ -0,0 +1,238 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 "ensightCells.H"
+#include "polyMesh.H"
+#include "globalIndex.H"
+#include "globalMeshData.H"
+#include "ListOps.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::Map<Foam::label>
+Foam::ensightCells::meshPointMap(const polyMesh& mesh) const
+{
+    const label nEstimate = 8*this->size();
+
+    Map<label> pointMap(nEstimate);
+
+    // Pass 1: markup used points from cells
+
+    for (const label celli : this->cellIds())
+    {
+        for (const label facei : mesh.cells()[celli])
+        {
+            for (const label pointi : mesh.faces()[facei])
+            {
+                pointMap.insert(pointi, 0);
+            }
+        }
+    }
+
+    // Compact point numbering, preserves the original order
+    label nPoints = 0;
+    for (const label pointi : pointMap.sortedToc())
+    {
+        pointMap(pointi) = nPoints++;
+    }
+
+    return pointMap;
+}
+
+
+Foam::label Foam::ensightCells::meshPointMapppings
+(
+    const polyMesh& mesh,
+    labelList& pointToGlobalRequest,
+    labelList& uniqueMeshPointLabels,
+    bool parallel
+) const
+{
+    labelList pointToGlobal;
+
+    const bool rewritePointMap = notNull(pointToGlobalRequest);
+
+    if (notNull(pointToGlobalRequest))
+    {
+        pointToGlobal.transfer(pointToGlobalRequest);
+    }
+
+
+    const ensightCells& part = *this;
+
+    parallel = parallel && Pstream::parRun();
+
+    // Renumber the points/faces into unique points
+    label nPoints = 0;  // Total number of points
+
+    bool allCells = (part.size() == mesh.nCells());
+
+    if (parallel)
+    {
+        Foam::reduce(allCells, andOp<bool>());
+
+        if (allCells)
+        {
+            // All cells used, and thus all points
+
+            autoPtr<globalIndex> globalPointsPtr =
+                mesh.globalData().mergePoints
+                (
+                    pointToGlobal,
+                    uniqueMeshPointLabels
+                );
+
+            nPoints = globalPointsPtr().size();   // nPoints (global)
+        }
+        else
+        {
+            // Map mesh point index to local (compact) point index
+
+            Map<label> meshPointMap(part.meshPointMap(mesh));
+
+            labelList meshPoints(meshPointMap.sortedToc());
+
+            autoPtr<globalIndex> globalPointsPtr =
+                mesh.globalData().mergePoints
+                (
+                    meshPoints,
+                    meshPointMap,
+                    pointToGlobal,
+                    uniqueMeshPointLabels
+                );
+
+            nPoints = globalPointsPtr().size();   // nPoints (global)
+
+            meshPointMap.clear();
+
+            // The mergePoints returns pointToGlobal under the
+            // assumption of local addressing
+            // (eg, patch localFaces).
+            // Recast as original mesh points to new global points
+
+            if (rewritePointMap)
+            {
+                labelList oldToNew(mesh.nPoints(), -1);
+
+                forAll(meshPoints, i)
+                {
+                    const label orig = meshPoints[i];
+                    const label glob = pointToGlobal[i];
+
+                    oldToNew[orig] = glob;
+                }
+
+                pointToGlobal.transfer(oldToNew);
+            }
+        }
+    }
+    else
+    {
+        // Non-parallel
+
+        nPoints = mesh.nPoints();
+        pointToGlobal.resize(nPoints);
+
+        if (allCells)
+        {
+            // All cells used, and thus all points
+
+            uniqueMeshPointLabels.resize(nPoints);
+
+            ListOps::identity(pointToGlobal);
+            ListOps::identity(uniqueMeshPointLabels);
+        }
+        else
+        {
+            // Mark up with -1 for unused entries
+            pointToGlobal = -1;
+
+            nPoints = 0;
+
+            // Pass 1: markup used points from cells
+
+            for (const label celli : this->cellIds())
+            {
+                for (const label facei : mesh.cells()[celli])
+                {
+                    for (const label pointi : mesh.faces()[facei])
+                    {
+                        if (pointToGlobal[pointi] == -1)
+                        {
+                            pointToGlobal[pointi] = nPoints++;
+                        }
+                    }
+                }
+            }
+
+            // Pass 2:
+            //
+            // Compact point numbering, preserving original point order
+            uniqueMeshPointLabels.resize(nPoints);
+
+            nPoints = 0;
+            forAll(pointToGlobal, pointi)
+            {
+                if (pointToGlobal[pointi] != -1)
+                {
+                    pointToGlobal[pointi] = nPoints;
+
+                    uniqueMeshPointLabels[nPoints] = pointi;
+
+                    ++nPoints;
+                }
+            }
+        }
+    }
+
+    if (notNull(pointToGlobalRequest))
+    {
+        pointToGlobalRequest.transfer(pointToGlobal);
+    }
+
+    return nPoints;
+}
+
+
+Foam::label Foam::ensightCells::uniqueMeshPoints
+(
+    const polyMesh& mesh,
+    labelList& uniqueMeshPointLabels,
+    bool parallel
+) const
+{
+    return meshPointMapppings
+    (
+        mesh,
+        const_cast<labelList&>(labelList::null()),  // Ignore pointToGlobal
+        uniqueMeshPointLabels,
+        parallel
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightCellsI.H b/src/fileFormats/ensight/part/cells/ensightCellsI.H
similarity index 54%
rename from src/fileFormats/ensight/part/ensightCellsI.H
rename to src/fileFormats/ensight/part/cells/ensightCellsI.H
index dcac7ab0030..cfccab8eed4 100644
--- a/src/fileFormats/ensight/part/ensightCellsI.H
+++ b/src/fileFormats/ensight/part/cells/ensightCellsI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,76 +25,71 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "error.H"
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-inline const char* Foam::ensightCells::key(const enum elemType what)
+inline void Foam::ensightCells::add(const elemType etype, label id)
 {
-    return elemNames[what];
-}
-
+    // Linear addressing location
+    const label index = offsets_[etype] + sizes_[etype]++;
 
-inline Foam::label Foam::ensightCells::index() const
-{
-    return index_;
+    addressing()[index] = id;
 }
 
 
-inline Foam::label& Foam::ensightCells::index()
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+inline const char* Foam::ensightCells::key(const elemType etype)
 {
-    return index_;
+    return elemNames[etype];
 }
 
 
-inline Foam::label Foam::ensightCells::size() const
+inline const Foam::FixedList<Foam::label,5>& Foam::ensightCells::totals() const
 {
-    return address_.size();
+    return sizes_;
 }
 
 
-inline const Foam::FixedList<Foam::label,5>& Foam::ensightCells::totals() const
+inline Foam::label Foam::ensightCells::total(const elemType etype) const
 {
-    return sizes_;
+    return sizes_[etype];
 }
 
 
-inline Foam::label Foam::ensightCells::total(const enum elemType what) const
+inline Foam::label Foam::ensightCells::size(const elemType etype) const
 {
-    return sizes_[what];
+    return (offsets_[etype+1] - offsets_[etype]);
 }
 
 
-inline Foam::label Foam::ensightCells::size(const enum elemType what) const
+inline Foam::labelRange Foam::ensightCells::range(const elemType etype) const
 {
-    return slices_[what].size();
+    return labelRange(offsets_[etype], offsets_[etype+1] - offsets_[etype]);
 }
 
 
-inline Foam::label Foam::ensightCells::offset(const enum elemType what) const
+inline const Foam::labelList& Foam::ensightCells::cellIds() const
 {
-    return slices_[what].start();
+    return addressing();
 }
 
 
-inline const Foam::labelUList Foam::ensightCells::cellIds
-(
-    const enum elemType what
-) const
+inline const Foam::labelUList
+Foam::ensightCells::cellIds(const elemType etype) const
 {
-    return address_[slices_[what]];
+    return addressing()[range(etype)];
 }
 
 
-inline const Foam::labelUList& Foam::ensightCells::cellIds() const
+inline void Foam::ensightCells::incrCellIds(const label off)
 {
-    return address_;
+    incrAddressing(off);
 }
 
 
-inline Foam::label Foam::ensightCells::operator[](const label i) const
+inline void Foam::ensightCells::decrCellIds(const label off)
 {
-    return address_[i];
+    decrAddressing(off);
 }
 
 
diff --git a/src/fileFormats/ensight/part/cells/ensightCellsIO.C b/src/fileFormats/ensight/part/cells/ensightCellsIO.C
new file mode 100644
index 00000000000..217543b2a19
--- /dev/null
+++ b/src/fileFormats/ensight/part/cells/ensightCellsIO.C
@@ -0,0 +1,322 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 "ensightCells.H"
+#include "ensightOutput.H"
+#include "polyMesh.H"
+#include "globalIndex.H"
+#include "globalMeshData.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightCells::writePolysConnectivity
+(
+    ensightGeoFile& os,
+    const polyMesh& mesh,
+    const ensightCells& part,
+    const labelList& pointToGlobal,
+    const bool parallel
+)
+{
+    constexpr ensightCells::elemType etype(ensightCells::NFACED);
+
+    // Slaves
+    const label nSlaves = (parallel ? Pstream::nProcs() : 0);
+
+
+    const label nTotal = part.total(etype);
+    const labelUList& addr = part.cellIds(etype);
+
+    if (!nTotal)
+    {
+        return;
+    }
+
+    if (Pstream::master())
+    {
+        os.writeKeyword(ensightCells::key(etype));
+        os.write(nTotal);
+        os.newline();
+    }
+
+    // Number of faces per polyhedral (1/line in ASCII)
+    {
+        labelList send
+        (
+            ensightOutput::Detail::getPolysNFaces(mesh, addr)
+        );
+
+        if (Pstream::master())
+        {
+            // Master
+            os.writeLabels(send);
+
+            // Slaves
+            for (int slave=1; slave < nSlaves; ++slave)
+            {
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+                labelList recv(fromSlave);
+                os.writeLabels(recv);
+            }
+        }
+        else if (nSlaves)
+        {
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            toMaster << send;
+        }
+    }
+
+
+    // Number of points for each polyhedral face (1/line in ASCII)
+    {
+        labelList send
+        (
+            ensightOutput::Detail::getPolysNPointsPerFace(mesh, addr)
+        );
+
+        if (Pstream::master())
+        {
+            // Master
+            os.writeLabels(send);
+
+            // Slaves
+            for (int slave=1; slave < nSlaves; ++slave)
+            {
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+                labelList recv(fromSlave);
+                os.writeLabels(recv);
+            }
+        }
+        else if (nSlaves)
+        {
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            toMaster << send;
+        }
+    }
+
+
+    // List of points id for each face of the above list
+    if (Pstream::master())
+    {
+        // Master
+        ensightOutput::writePolysPoints
+        (
+            os,
+            mesh,
+            addr,
+            pointToGlobal
+        );
+
+        // Slaves
+        for (int slave=1; slave < nSlaves; ++slave)
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            cellList  cells(fromSlave);
+            labelList addr(fromSlave);
+            faceList  faces(fromSlave);
+            labelList owner(fromSlave);
+
+            ensightOutput::writePolysPoints
+            (
+                os,
+                cells,
+                addr,
+                faces,
+                owner
+            );
+        }
+    }
+    else if (nSlaves)
+    {
+        // Renumber faces to use global point numbers
+        faceList faces(mesh.faces());
+        ListListOps::inplaceRenumber(pointToGlobal, faces);
+
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        toMaster
+            << mesh.cells()
+            << addr
+            << faces
+            << mesh.faceOwner();
+    }
+}
+
+
+void Foam::ensightCells::writeShapeConnectivity
+(
+    ensightGeoFile& os,
+    const polyMesh& mesh,
+    const ensightCells::elemType etype,
+    const ensightCells& part,
+    const labelList& pointToGlobal,
+    const bool parallel
+)
+{
+    if (etype == ensightCells::NFACED)
+    {
+        FatalErrorInFunction
+            << "Called for ensight NFACED cell. Programming error\n"
+            << exit(FatalError);
+    }
+
+    // Slaves
+    const label nSlaves = (parallel ? Pstream::nProcs() : 0);
+
+
+    const label nTotal = part.total(etype);
+    const labelUList& addr = part.cellIds(etype);
+
+    if (!nTotal)
+    {
+        return;
+    }
+
+
+    if (Pstream::master())
+    {
+        os.writeKeyword(ensightCells::key(etype));
+        os.write(nTotal);
+        os.newline();
+    }
+
+
+    // Primitive shape - get subset and renumber
+    cellShapeList shapes(mesh.cellShapes(), addr);
+
+    ListListOps::inplaceRenumber(pointToGlobal, shapes);
+
+    if (Pstream::master())
+    {
+        ensightOutput::writeCellShapes(os, shapes);
+
+        for (int slave=1; slave < nSlaves; ++slave)
+        {
+            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+            cellShapeList recv(fromSlave);
+
+            ensightOutput::writeCellShapes(os, recv);
+        }
+    }
+    else if (nSlaves)
+    {
+        OPstream toMaster
+        (
+            Pstream::commsTypes::scheduled,
+            Pstream::masterNo()
+        );
+
+        toMaster << shapes;
+    }
+}
+
+
+void Foam::ensightCells::write
+(
+    ensightGeoFile& os,
+    const polyMesh& mesh,
+    bool parallel
+) const
+{
+    const ensightCells& part = *this;
+
+    parallel = parallel && Pstream::parRun();
+
+    // Renumber the points/faces into unique points
+
+    label nPoints = 0;  // Total number of points
+    labelList pointToGlobal;  // local point to unique global index
+    labelList uniqueMeshPointLabels;  // unique global points
+
+    nPoints = meshPointMapppings
+    (
+        mesh,
+        pointToGlobal,
+        uniqueMeshPointLabels,
+        parallel
+    );
+
+    ensightOutput::Detail::writeCoordinates
+    (
+        os,
+        part.index(),
+        part.name(),
+        nPoints,  // nPoints (global)
+        UIndirectList<point>(mesh.points(), uniqueMeshPointLabels),
+        parallel  //!< Collective write?
+    );
+
+
+    for (label typei=0; typei < ensightCells::nTypes; ++typei)
+    {
+        const auto etype = ensightCells::elemType(typei);
+
+        if (etype == ensightCells::NFACED)
+        {
+            writePolysConnectivity
+            (
+                os,
+                mesh,
+                part,
+                pointToGlobal,
+                parallel
+            );
+        }
+        else
+        {
+            writeShapeConnectivity
+            (
+                os,
+                mesh,
+                etype,
+                part,
+                pointToGlobal,
+                parallel
+            );
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightCells.H b/src/fileFormats/ensight/part/ensightCells.H
deleted file mode 100644
index f2e3f3b9d3d..00000000000
--- a/src/fileFormats/ensight/part/ensightCells.H
+++ /dev/null
@@ -1,215 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::ensightCells
-
-Description
-    Sorting/classification of cells (3D) into corresponding ensight element
-    types.
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightCells_H
-#define ensightCells_H
-
-#include "labelList.H"
-#include "FixedList.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-// Forward declarations
-class bitSet;
-class polyMesh;
-
-/*---------------------------------------------------------------------------*\
-                        Class ensightCells Declaration
-\*---------------------------------------------------------------------------*/
-
-class ensightCells
-{
-public:
-
-    // Public data
-
-        //- Addressable ensight element types
-        enum elemType
-        {
-            TETRA4,     //!< "tetra4"
-            PYRAMID5,   //!< "pyramid5"
-            PENTA6,     //!< "penta6"
-            HEXA8,      //!< "hexa8"
-            NFACED      //!< "nfaced"
-        };
-
-        //- Number of element types (5)
-        static constexpr int nTypes = 5;
-
-        //- The ensight element type names
-        static const char* elemNames[5];
-
-
-    // Static Member Functions
-
-        //- Return the ensight element name for the specified type
-        inline static const char* key(const enum elemType);
-
-
-private:
-
-    // Private Data
-
-        //- Location within a list.
-        //  The ensight part number is typically this value +1.
-        label index_;
-
-        //- Linear list of ids, sub-sectioned per element type by sub-lists
-        labelList address_;
-
-        //- Slices (sub-lists) of the address ids for each element type.
-        FixedList<labelRange, 5> slices_;
-
-        //- List of global sizes for each element type.
-        //  Used temporarily for local sizes when building the element lists.
-        FixedList<label, 5> sizes_;
-
-
-    // Private Member Functions
-
-        //- Use temporarily stored sizes to redimension the element lists
-        void resizeAll();
-
-        //- Classify cell types, set element lists for selection (implemention)
-        template<class Addressing>
-        void classifyImpl(const polyMesh& mesh, const Addressing& cellIds);
-
-        //- No copy assignment
-        void operator=(const ensightCells&) = delete;
-
-
-public:
-
-    // Constructors
-
-        //- Construct null, with part index 0
-        ensightCells();
-
-        //- Construct null, with specified part index
-        explicit ensightCells(const label partIndex);
-
-        //- Copy constructor. Needed for lists etc.
-        ensightCells(const ensightCells& obj);
-
-
-    //- Destructor
-    ~ensightCells() = default;
-
-
-    // Member Functions
-
-    // Access
-
-        //- The index in a list.
-        inline label index() const;
-
-        //- The index in a list, non-const access.
-        inline label& index();
-
-        //- The processor local size of all elements.
-        inline label size() const;
-
-        //- The processor local size of the specified element type.
-        inline label size(const enum elemType) const;
-
-        //- The global number of the specified element type.
-        //  This value is only meaningful after a reduce operation.
-        inline label total(const enum elemType) const;
-
-        //- The global number of all element types.
-        //  This value is only meaningful after a reduce operation.
-        label total() const;
-
-        //- The global numbers per element type.
-        //  This value is only meaningful after a reduce operation.
-        inline const FixedList<label, 5>& totals() const;
-
-        //- The processor local sizes per element type.
-        FixedList<label, 5> sizes() const;
-
-        //- Processor local starting offset of element type.
-        inline label offset(const enum elemType what) const;
-
-        //- Return the (local) cell ids of the specified element type
-        inline const labelUList cellIds(const enum elemType) const;
-
-        //- Return the cell ids of all elements
-        inline const labelUList& cellIds() const;
-
-
-    // Edit
-
-        //- Classify cell types and set the element lists.
-        void classify(const polyMesh& mesh);
-
-        //- Classify cell types and set element lists,
-        //- using a subgroup of cells (eg, from a cellZone etc).
-        void classify(const polyMesh& mesh, const labelUList& cellIds);
-
-        //- Classify cell types and set element lists,
-        //- using a subgroup of cells
-        void classify(const polyMesh& mesh, const bitSet& selection);
-
-        //- Set addressable sizes to zero, free up addressing memory.
-        void clear();
-
-        //- Sum element counts across all processes.
-        void reduce();
-
-        //- Sort element lists numerically.
-        void sort();
-
-
-    // Member Operators
-
-        //- Return id from linear list of addressing.
-        inline label operator[](const label i) const;
-
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#include "ensightCellsI.H"
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightFaces.H b/src/fileFormats/ensight/part/ensightFaces.H
deleted file mode 100644
index 307237279a8..00000000000
--- a/src/fileFormats/ensight/part/ensightFaces.H
+++ /dev/null
@@ -1,222 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2016-2018 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::ensightFaces
-
-Description
-    Sorting/classification of faces (2D) into corresponding ensight types
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightFaces_H
-#define ensightFaces_H
-
-#include "boolList.H"
-#include "labelList.H"
-#include "faceList.H"
-#include "FixedList.H"
-#include "bitSet.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-/*---------------------------------------------------------------------------*\
-                        Class ensightFaces Declaration
-\*---------------------------------------------------------------------------*/
-
-class ensightFaces
-{
-public:
-
-    // Public Data
-
-        //- Addressable ensight element types
-        enum elemType
-        {
-            TRIA3,      //!< "tria3"
-            QUAD4,      //!< "quad4"
-            NSIDED      //!< "nsided"
-        };
-
-        //- Number of element types (3)
-        static constexpr int nTypes = 3;
-
-        //- The ensight element type names
-        static const char* elemNames[3];
-
-
-    // Static Member Functions
-
-        //- Return the ensight element name for the specified type
-        static inline const char* key(const enum elemType);
-
-
-private:
-
-    // Private data
-
-        //- Location within a list.
-        //  The ensight part number is typically this value +1.
-        label index_;
-
-        //- Linear list of ids, sub-sectioned per element type by sub-lists
-        labelList address_;
-
-        //- Linear list of face-flips
-        boolList flipMap_;
-
-        //- Slices (sub-lists) of the address and flips for each element type.
-        FixedList<labelRange, 3> slices_;
-
-        //- List of global sizes for each element type.
-        //  Used temporarily for local sizes when building the element lists.
-        FixedList<label, 3> sizes_;
-
-
-    // Private Member Functions
-
-        //- Low-level internal addition routine
-        inline void add(const face& f, const label id, const bool flip = false);
-
-        //- Use temporarily stored sizes to redimension the element lists
-        void resizeAll();
-
-        //- No copy assignment
-        void operator=(const ensightFaces&) = delete;
-
-
-public:
-
-    // Constructors
-
-        //- Construct null, with part index 0
-        ensightFaces();
-
-        //- Construct null, with specified part index
-        explicit ensightFaces(const label partIndex);
-
-        //- Copy constructor. Needed for lists etc.
-        ensightFaces(const ensightFaces& obj);
-
-
-    //- Destructor
-    ~ensightFaces() = default;
-
-
-    // Member Functions
-
-    // Access
-
-        //- The index in a list.
-        inline label index() const;
-
-        //- The index in a list, non-const access.
-        inline label& index();
-
-        //- The processor local size of all elements.
-        inline label size() const;
-
-        //- The processor local size of the specified element type.
-        inline label size(const enum elemType) const;
-
-        //- The global number of all element types.
-        //  This value is only meaningful after a reduce operation.
-        label total() const;
-
-        //- The global number of the specified element type.
-        //  This value is only meaningful after a reduce operation.
-        inline label total(const enum elemType) const;
-
-        //- The global numbers per element type.
-        //  This value is only meaningful after a reduce operation.
-        inline const FixedList<label, 3>& totals() const;
-
-        //- The processor local sizes per element type.
-        FixedList<label, 3> sizes() const;
-
-        //- Processor local starting offset of element type.
-        inline label offset(const enum elemType what) const;
-
-        //- Return the (local) face ids of the specified element type
-        inline const labelUList faceIds(const enum elemType) const;
-
-        //- Return the processor local face ids of all elements
-        inline const labelUList& faceIds() const;
-
-        //- Return the processor local flip-map of all elements
-        inline const boolList& flipMap() const;
-
-
-    // Edit
-
-        //- Classify the face types, set element list.
-        void classify(const faceList& faces);
-
-
-        //- Classify the face types, set element list.
-        //  The indirect addressing can be used when classifying groups of
-        //  face (eg, from a faceZone etc) with an optional flipMap.
-        //  The optional exclude marker can be used to skip faces on particular
-        //  boundary types or regions.
-        void classify
-        (
-            const faceList& faces,
-            const labelUList& addressing,
-            const boolList& flipMap = boolList(),
-            const bitSet& exclude = bitSet()
-        );
-
-
-        //- Set addressable sizes to zero, free up addressing memory.
-        void clear();
-
-        //- Sum element counts across all processes.
-        void reduce();
-
-        //- Sort element lists numerically.
-        void sort();
-
-
-    // Member Operators
-
-        //- Return element from linear-list.
-        inline label operator[](const label i) const;
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#include "ensightFacesI.H"
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPart.H b/src/fileFormats/ensight/part/ensightPart.H
deleted file mode 100644
index 2b9d3f9d3b9..00000000000
--- a/src/fileFormats/ensight/part/ensightPart.H
+++ /dev/null
@@ -1,183 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::ensightPart
-
-Description
-    Base class for ensightPartCells and ensightPartFaces
-
-SourceFiles
-    ensightPart.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightPart_H
-#define ensightPart_H
-
-#include "ensightGeoFile.H"
-#include "typeInfo.H"
-#include "labelList.H"
-#include "polyMesh.H"
-#include "Field.H"
-#include "IOstream.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-/*---------------------------------------------------------------------------*\
-                         Class ensightPart Declaration
-\*---------------------------------------------------------------------------*/
-
-class ensightPart
-{
-    // Private Data
-
-        //- Part name (or description)
-        string name_;
-
-
-    // Private Member Functions
-
-        //- No copy construct
-        ensightPart(const ensightPart&) = delete;
-
-        //- No copy assignment
-        void operator=(const ensightPart&) = delete;
-
-
-protected:
-
-    // Protected Classes
-
-        //- Track the points used by the part and map global to local indices
-        struct localPoints
-        {
-            //- Number of points used
-            label nPoints;
-
-            //- Map global to local indices
-            labelList list;
-
-            //- Null constructor
-            localPoints()
-            :
-                nPoints(0),
-                list()
-            {}
-
-            //- Construct for mesh points
-            localPoints(const pointField& pts)
-            :
-                nPoints(0),
-                list(pts.size(), -1)
-            {}
-        };
-
-public:
-
-    //- Runtime type information
-    TypeName("ensightPart");
-
-
-    // Constructors
-
-        //- Construct with description
-        explicit ensightPart(const string& description);
-
-
-    //- Destructor
-    virtual ~ensightPart() = default;
-
-
-    // Access
-
-        //- Part index (0-based)
-        virtual label index() const = 0;
-
-        //- Number of elements in this part
-        virtual label size() const
-        {
-            return 0;
-        }
-
-        //- Return the part name or description
-        const string& name() const
-        {
-            return name_;
-        }
-
-        //- Change the part name or description
-        void rename(string value)
-        {
-            name_ = std::move(value);
-        }
-
-
-    // Output
-
-        //- Write summary information about the object
-        virtual void writeSummary(Ostream& os) const = 0;
-
-        //- Print various types of debugging information
-        virtual void dumpInfo(Ostream& os) const = 0;
-
-        //- Write geometry
-        virtual void write(ensightGeoFile& os) const = 0;
-
-        //- Helper: write geometry with given pointField
-        virtual void write(ensightGeoFile& os, const pointField&) const = 0;
-
-
-    // Housekeeping
-
-        //- Deprecated(2019-12) - use rename() method
-        //  \deprecated(2019-12) - use rename() method
-        void FOAM_DEPRECATED_FOR(2019-12, "rename() method")
-        name(string value)
-        {
-            name_ = std::move(value);
-        }
-};
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-
-//- IOstream Operator to write geometry
-ensightGeoFile& operator<<(ensightGeoFile& os, const ensightPart& part);
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPartCells.C b/src/fileFormats/ensight/part/ensightPartCells.C
deleted file mode 100644
index 8a206be91f3..00000000000
--- a/src/fileFormats/ensight/part/ensightPartCells.C
+++ /dev/null
@@ -1,344 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "ensightPartCells.H"
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
-namespace Foam
-{
-    defineTypeNameAndDebug(ensightPartCells, 0);
-}
-
-
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
-
-Foam::ensightPart::localPoints Foam::ensightPartCells::calcLocalPoints() const
-{
-    localPoints ptList(mesh_.points());
-    labelList& usedPoints = ptList.list;
-    label nPoints = 0;
-
-    // Add all points from cells
-    const labelUList& idList = this->cellIds();
-
-    for (const label id : idList)
-    {
-        const labelUList& cFaces = mesh_.cells()[id];
-
-        forAll(cFaces, cFacei)
-        {
-            const face& f = mesh_.faces()[cFaces[cFacei]];
-
-            forAll(f, fp)
-            {
-                if (usedPoints[f[fp]] == -1)
-                {
-                    usedPoints[f[fp]] = nPoints++;
-                }
-            }
-        }
-    }
-
-    // this is not absolutely necessary, but renumber anyhow
-    nPoints = 0;
-    forAll(usedPoints, ptI)
-    {
-        if (usedPoints[ptI] > -1)
-        {
-            usedPoints[ptI] = nPoints++;
-        }
-    }
-
-    ptList.nPoints = nPoints;
-    return ptList;
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::ensightPartCells::ensightPartCells
-(
-    label partIndex,
-    const polyMesh& mesh,
-    const string& partName
-)
-:
-    ensightCells(partIndex),
-    ensightPart(partName),
-    mesh_(mesh)
-{
-    classify(mesh);
-}
-
-
-Foam::ensightPartCells::ensightPartCells
-(
-    label partIndex,
-    const polyMesh& mesh,
-    const labelUList& cellIds,
-    const string& partName
-)
-:
-    ensightCells(partIndex),
-    ensightPart(partName),
-    mesh_(mesh)
-{
-    classify(mesh, cellIds);
-}
-
-
-Foam::ensightPartCells::ensightPartCells
-(
-    label partIndex,
-    const polyMesh& mesh,
-    const bitSet& selection,
-    const string& partName
-)
-:
-    ensightCells(partIndex),
-    ensightPart(partName),
-    mesh_(mesh)
-{
-    classify(mesh, selection);
-}
-
-
-Foam::ensightPartCells::ensightPartCells
-(
-    label partIndex,
-    const polyMesh& mesh,
-    const cellZone& zn,
-    const string& partName
-)
-:
-    ensightPartCells
-    (
-        partIndex,
-        mesh,
-        static_cast<const labelList&>(zn),
-        zn.name()
-    )
-{
-    if (!partName.empty())
-    {
-        rename(partName);
-    }
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::ensightPartCells::writeConnectivity
-(
-    ensightGeoFile& os,
-    const word& key,
-    const labelUList& idList,
-    const labelUList& pointMap
-) const
-{
-    if (idList.empty()) return;
-
-    os.writeKeyword(key);
-    os.write(idList.size());
-    os.newline();
-
-    // write polyhedral
-    if (key == "nfaced")
-    {
-        const faceList& meshFaces = mesh_.faces();
-        const labelUList& owner = mesh_.faceOwner();
-
-        // write the number of faces per element
-        forAll(idList, i)
-        {
-            const label id = idList[i];
-            const labelUList& cFace = mesh_.cells()[id];
-
-            os.write(cFace.size());
-            os.newline();
-        }
-
-        // write the number of points per element face
-        forAll(idList, i)
-        {
-            const label id = idList[i];
-            const labelUList& cFace = mesh_.cells()[id];
-
-            forAll(cFace, facei)
-            {
-                const face& cf = meshFaces[cFace[facei]];
-
-                os.write(cf.size());
-                os.newline();
-            }
-        }
-
-        // write the points describing each element face
-        forAll(idList, i)
-        {
-            const label id = idList[i];
-            const labelUList& cFace = mesh_.cells()[id];
-
-            forAll(cFace, cFacei)
-            {
-                const label faceId = cFace[cFacei];
-                const face& cf = meshFaces[faceId];
-
-                // convert global -> local index
-                // (note: Ensight indices start with 1)
-
-                // ensight >= 9 needs consistently oriented nfaced cells
-                if (id == owner[faceId])
-                {
-                    forAll(cf, ptI)
-                    {
-                        os.write(pointMap[cf[ptI]] + 1);
-                    }
-                }
-                else
-                {
-                    // as per face::reverseFace(), but without copying
-
-                    os.write(pointMap[cf[0]] + 1);
-                    for (label ptI = cf.size()-1; ptI > 0; --ptI)
-                    {
-                        os.write(pointMap[cf[ptI]] + 1);
-                    }
-                }
-
-                os.newline();
-            }
-        }
-    }
-    else
-    {
-        // write primitive
-        const cellShapeList& shapes = mesh_.cellShapes();
-
-        forAll(idList, i)
-        {
-            const label id = idList[i];
-            const cellShape& cellPoints = shapes[id];
-
-            // convert global -> local index
-            // (note: Ensight indices start with 1)
-            forAll(cellPoints, ptI)
-            {
-                os.write(pointMap[cellPoints[ptI]] + 1);
-            }
-            os.newline();
-        }
-    }
-}
-
-
-void Foam::ensightPartCells::write
-(
-    ensightGeoFile& os,
-    const pointField& points
-) const
-{
-    if (size())
-    {
-        const localPoints ptList = calcLocalPoints();
-        const labelUList& pointMap = ptList.list;
-
-        os.beginPart(index(), name());
-        os.beginCoordinates(ptList.nPoints);
-
-        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
-        {
-            forAll(pointMap, ptI)
-            {
-                if (pointMap[ptI] > -1)
-                {
-                    os.write(points[ptI].component(cmpt));
-                    os.newline();
-                }
-            }
-        }
-
-        // Write each element type
-        for (int typei=0; typei < ensightCells::nTypes; ++typei)
-        {
-            const ensightCells::elemType what = ensightCells::elemType(typei);
-
-            writeConnectivity
-            (
-                os,
-                ensightCells::key(what),
-                cellIds(what),
-                pointMap
-            );
-        }
-    }
-}
-
-
-void Foam::ensightPartCells::write(ensightGeoFile& os) const
-{
-    this->write(os, mesh_.points());
-}
-
-
-void Foam::ensightPartCells::writeSummary(Ostream& os) const
-{
-    os.beginBlock(type());
-
-    os.writeEntry("id",     index()+1); // Ensight starts with 1
-    os.writeEntry("name",   name());
-    os.writeEntry("size",   size());
-
-    os.endBlock();
-}
-
-
-void Foam::ensightPartCells::dumpInfo(Ostream& os) const
-{
-    os.beginBlock(type());
-
-    os.writeEntry("id",     index()+1); // Ensight starts with 1
-    os.writeEntry("name",   name());
-    os.writeEntry("size",   size());
-
-    for (int typei=0; typei < ensightCells::nTypes; ++typei)
-    {
-        const ensightCells::elemType what = ensightCells::elemType(typei);
-        const labelUList& addr = this->cellIds(what);
-
-        os.writeKeyword(ensightCells::key(what));
-
-        addr.writeList(os, 0) << endEntry;  // Flat output
-    }
-
-    os.endBlock();
-}
-
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPartCells.H b/src/fileFormats/ensight/part/ensightPartCells.H
deleted file mode 100644
index b0ddad0d4d7..00000000000
--- a/src/fileFormats/ensight/part/ensightPartCells.H
+++ /dev/null
@@ -1,180 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::ensightPartCells
-
-Description
-    An implementation of ensightPart to hold volume mesh cells.
-
-SourceFiles
-    ensightPartCells.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightPartCells_H
-#define ensightPartCells_H
-
-#include "ensightPart.H"
-#include "ensightCells.H"
-#include "typeInfo.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-/*---------------------------------------------------------------------------*\
-                      Class ensightPartCells Declaration
-\*---------------------------------------------------------------------------*/
-
-class ensightPartCells
-:
-    public ensightCells,
-    public ensightPart
-{
-    // Private Data
-
-        //- The referenced mesh
-        const polyMesh& mesh_;
-
-
-    // Private Member Functions
-
-        //- Track points used
-        localPoints calcLocalPoints() const;
-
-        //- Element connectivity
-        void writeConnectivity
-        (
-            ensightGeoFile&,
-            const word& key,
-            const labelUList& idList,
-            const labelUList& pointMap
-        ) const;
-
-
-        //- No copy construct
-        ensightPartCells(const ensightPartCells&) = delete;
-
-        //- No copy assignment
-        void operator=(const ensightPartCells&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("ensightCells");
-
-
-    // Constructors
-
-        //- Construct from entire polyMesh without zones.
-        //- Part receives the specified name (default: "cells").
-        ensightPartCells
-        (
-            label partIndex,
-            const polyMesh& mesh,
-            const string& partName = "cells"
-        );
-
-        //- Construct a part from polyMesh and list of cells.
-        //- Part receives the specified name (default: "cells").
-        ensightPartCells
-        (
-            label partIndex,
-            const polyMesh& mesh,
-            const labelUList& cellIds,
-            const string& partName = "cells"
-        );
-
-        //- Construct a part from polyMesh and selection of cells.
-        //- Part receives the specified name (default: "cells").
-        ensightPartCells
-        (
-            label partIndex,
-            const polyMesh& mesh,
-            const bitSet& selection,
-            const string& partName = "cells"
-        );
-
-        //- Construct from polyMesh and cellZone.
-        //- Part receives the name of the zone unless otherwise specified.
-        ensightPartCells
-        (
-            label partIndex,
-            const polyMesh& mesh,
-            const cellZone& zn,
-            const string& partName = ""
-        );
-
-
-    //- Destructor
-    virtual ~ensightPartCells() = default;
-
-
-    // Member Functions
-
-    // Access
-
-        //- Part index (0-based)
-        virtual label index() const
-        {
-            return ensightCells::index();
-        }
-
-        //- Number of elements in this part
-        virtual label size() const
-        {
-            return ensightCells::size();
-        }
-
-
-    // Output
-
-        //- Write summary information about the object
-        virtual void writeSummary(Ostream& os) const;
-
-        //- Print various types of debugging information
-        virtual void dumpInfo(Ostream& os) const;
-
-        //- Write geometry
-        virtual void write(ensightGeoFile& os) const;
-
-        //- Helper: write geometry with given pointField
-        virtual void write(ensightGeoFile& os, const pointField& points) const;
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPartFaces.C b/src/fileFormats/ensight/part/ensightPartFaces.C
deleted file mode 100644
index 6e9d2747c49..00000000000
--- a/src/fileFormats/ensight/part/ensightPartFaces.C
+++ /dev/null
@@ -1,302 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "ensightPartFaces.H"
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
-namespace Foam
-{
-    defineTypeNameAndDebug(ensightPartFaces, 0);
-}
-
-
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
-
-Foam::ensightPart::localPoints Foam::ensightPartFaces::calcLocalPoints() const
-{
-    if (contiguousPoints_)
-    {
-        localPoints ptList;
-        ptList.list = identity(points_.size());
-        ptList.nPoints = points_.size();
-        return ptList;
-    }
-
-    localPoints ptList(points_);
-    labelList& usedPoints = ptList.list;
-    label nPoints = 0;
-
-    // Add all points from faces
-    const labelUList& idList = this->faceIds();
-
-    // Add all points from faces
-    forAll(idList, i)
-    {
-        const label id = idList[i] + start_;
-        const face& f = faces_[id];
-
-        forAll(f, fp)
-        {
-            if (usedPoints[f[fp]] == -1)
-            {
-                usedPoints[f[fp]] = nPoints++;
-            }
-        }
-    }
-
-
-    // This is not absolutely necessary, but renumber anyhow
-    nPoints = 0;
-    forAll(usedPoints, ptI)
-    {
-        if (usedPoints[ptI] > -1)
-        {
-            usedPoints[ptI] = nPoints++;
-        }
-    }
-
-    ptList.nPoints = nPoints;
-    return ptList;
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::ensightPartFaces::ensightPartFaces
-(
-    label partIndex,
-    const string& description,
-    const pointField& points,
-    const faceList& faces,
-    const bool contiguousPoints
-)
-:
-    ensightFaces(partIndex),
-    ensightPart(description),
-    start_(0),
-    patchIndex_(-1),
-    faces_(faces),
-    points_(points),
-    contiguousPoints_(contiguousPoints)
-{
-    // Classify the face shapes
-    classify(faces);
-}
-
-
-Foam::ensightPartFaces::ensightPartFaces
-(
-    label partIndex,
-    const polyMesh& mesh,
-    const polyPatch& patch,
-    const string& partName
-)
-:
-    ensightFaces(partIndex),
-    ensightPart(patch.name()),
-    start_(patch.start()),
-    patchIndex_(patch.index()),
-    faces_(mesh.faces()),
-    points_(mesh.points()),
-    contiguousPoints_(false)
-{
-    if (!partName.empty())
-    {
-        rename(partName);
-    }
-
-    // Classify the face shapes
-    classify(patch);
-}
-
-
-Foam::ensightPartFaces::ensightPartFaces
-(
-    label partIndex,
-    const polyPatch& patch,
-    const string& partName
-)
-:
-    ensightPartFaces(partIndex, patch.boundaryMesh().mesh(), patch, partName)
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::ensightPartFaces::writeConnectivity
-(
-    ensightGeoFile& os,
-    const word& key,
-    const faceList& faces,
-    const labelUList& idList,
-    const labelUList& pointMap
-) const
-{
-    if (idList.empty()) return;
-
-    os.writeKeyword(key);
-    os.write(idList.size());
-    os.newline();
-
-    // Write (polygon) face sizes
-    if (key == "nsided")
-    {
-        // Write the number of points per face
-        forAll(idList, i)
-        {
-            const label id = idList[i] + start_;
-            const face& f = faces[id];
-
-            os.write(f.size());
-            os.newline();
-        }
-    }
-
-    // Write the points describing the face
-    forAll(idList, i)
-    {
-        const label id = idList[i] + start_;
-        const face& f = faces[id];
-
-        // Convert global -> local index
-        // (note: Ensight indices start with 1)
-        forAll(f, fp)
-        {
-            os.write(pointMap[f[fp]] + 1);
-        }
-        os.newline();
-    }
-}
-
-
-void Foam::ensightPartFaces::writeConnectivity
-(
-    ensightGeoFile& os,
-    const word& key,
-    const labelUList& idList,
-    const labelUList& pointMap
-) const
-{
-    writeConnectivity
-    (
-        os,
-        key,
-        faces_,
-        idList,
-        pointMap
-    );
-}
-
-
-void Foam::ensightPartFaces::write
-(
-    ensightGeoFile& os,
-    const pointField& points
-) const
-{
-    if (size())
-    {
-        const localPoints ptList = calcLocalPoints();
-        const labelUList& pointMap = ptList.list;
-
-        os.beginPart(index(), name());
-        os.beginCoordinates(ptList.nPoints);
-
-        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
-        {
-            forAll(pointMap, ptI)
-            {
-                if (pointMap[ptI] > -1)
-                {
-                    os.write(points[ptI].component(cmpt));
-                    os.newline();
-                }
-            }
-        }
-
-        // Write part
-        for (int typei=0; typei < ensightFaces::nTypes; ++typei)
-        {
-            const ensightFaces::elemType what = ensightFaces::elemType(typei);
-
-            writeConnectivity
-            (
-                os,
-                ensightFaces::key(what),
-                faceIds(what),
-                pointMap
-            );
-        }
-    }
-}
-
-
-void Foam::ensightPartFaces::write(ensightGeoFile& os) const
-{
-    this->write(os, points_);
-}
-
-
-void Foam::ensightPartFaces::writeSummary(Ostream& os) const
-{
-    os.beginBlock(type());
-
-    os.writeEntry("id",     index()+1); // Ensight starts with 1
-    os.writeEntry("name",   name());
-    os.writeEntry("start",  start_);
-    os.writeEntry("size",   size());
-
-    os.endBlock();
-}
-
-
-void Foam::ensightPartFaces::dumpInfo(Ostream& os) const
-{
-    os.beginBlock(type());
-
-    os.writeEntry("id",     index()+1); // Ensight starts with 1
-    os.writeEntry("name",   name());
-    os.writeEntry("start",  start_);
-    os.writeEntry("size",   size());
-
-    for (int typei=0; typei < ensightFaces::nTypes; ++typei)
-    {
-        const ensightFaces::elemType what = ensightFaces::elemType(typei);
-        const labelUList& addr = this->faceIds(what);
-
-        os.writeKeyword(ensightFaces::key(what));
-
-        addr.writeList(os, 0) << endEntry;  // Flat output
-    }
-
-    os.endBlock();
-}
-
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPartFaces.H b/src/fileFormats/ensight/part/ensightPartFaces.H
deleted file mode 100644
index 08fb6af73b7..00000000000
--- a/src/fileFormats/ensight/part/ensightPartFaces.H
+++ /dev/null
@@ -1,202 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::ensightPartFaces
-
-Description
-    An implementation of ensightPart to hold mesh faces.
-
-SourceFiles
-    ensightPartFaces.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightPartFaces_H
-#define ensightPartFaces_H
-
-#include "ensightPart.H"
-#include "ensightFaces.H"
-#include "typeInfo.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-/*---------------------------------------------------------------------------*\
-                      Class ensightPartFaces Declaration
-\*---------------------------------------------------------------------------*/
-
-class ensightPartFaces
-:
-    public ensightFaces,
-    public ensightPart
-{
-    // Private data
-
-        //- Start offset for patch
-        const label start_;
-
-        //- Patch index
-        const label patchIndex_;
-
-        //- The referenced faces
-        const faceList& faces_;
-
-        //- The referenced pointField
-        const pointField& points_;
-
-        //- Can skip local point renumbering when points are contiguous
-        const bool contiguousPoints_;
-
-
-    // Private Member Functions
-
-        //- Track points used
-        localPoints calcLocalPoints() const;
-
-        //- Element connectivity
-        void writeConnectivity
-        (
-            ensightGeoFile&,
-            const word& key,
-            const labelUList& idList,
-            const labelUList& pointMap
-        ) const;
-
-
-        //- Helper: write connectivity
-        void writeConnectivity
-        (
-            ensightGeoFile&,
-            const word& key,
-            const faceList&,
-            const labelUList& idList,
-            const labelUList& pointMap
-        ) const;
-
-
-        //- No copy construct
-        ensightPartFaces(const ensightPartFaces&) = delete;
-
-        //- No copy assignment
-        void operator=(const ensightPartFaces&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("ensightFaces");
-
-
-    // Constructors
-
-        //- Construct part with 0-based index, description, points and faces
-        //  Can skip local point renumbering when points are contiguous
-        ensightPartFaces
-        (
-            label partIndex,
-            const string& description,
-            const pointField& points,
-            const faceList& faces,
-            const bool contiguousPoints = false
-        );
-
-        //- Construct from polyMesh and polyPatch
-        //- Part receives the name of the patch unless otherwise specified.
-        ensightPartFaces
-        (
-            label partIndex,
-            const polyMesh& mesh,
-            const polyPatch& patch,
-            const string& partName = ""
-        );
-
-        //- Construct from polyPatch
-        //- Part receives the name of the patch unless otherwise specified.
-        ensightPartFaces
-        (
-            label partIndex,
-            const polyPatch& patch,
-            const string& partName = ""
-        );
-
-
-    //- Destructor
-    virtual ~ensightPartFaces() = default;
-
-
-    // Member Functions
-
-    // Access
-
-        //- Part index (0-based)
-        virtual label index() const
-        {
-            return ensightFaces::index();
-        }
-
-
-        //- Number of elements in this part
-        virtual label size() const
-        {
-            return ensightFaces::size();
-        }
-
-
-        //- Return the patch index, -1 when not in use.
-        inline label patchIndex() const
-        {
-            return patchIndex_;
-        }
-
-
-    // Output
-
-        //- Write summary information about the object
-        virtual void writeSummary(Ostream& os) const;
-
-        //- Print various types of debugging information
-        virtual void dumpInfo(Ostream& os) const;
-
-        //- Write geometry
-        virtual void write(ensightGeoFile& os) const;
-
-        //- Helper: write geometry given the pointField
-        virtual void write(ensightGeoFile& os, const pointField& points) const;
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightParts.C b/src/fileFormats/ensight/part/ensightParts.C
deleted file mode 100644
index 70baac46477..00000000000
--- a/src/fileFormats/ensight/part/ensightParts.C
+++ /dev/null
@@ -1,134 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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 "ensightParts.H"
-#include "bitSet.H"
-#include "emptyPolyPatch.H"
-#include "processorPolyPatch.H"
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::ensightParts::ensightParts(const polyMesh& mesh)
-:
-    StorageType()
-{
-    recalculate(mesh);
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::ensightParts::recalculate(const polyMesh& mesh)
-{
-    StorageType::clear();
-
-    label nPart = 0;
-
-    // Track which cells are in a zone or not
-    bitSet selection(mesh.nCells());
-
-    // Do all cell zones
-    for (const cellZone& zn : mesh.cellZones())
-    {
-        if (returnReduce(!zn.empty(), orOp<bool>()))
-        {
-            selection.set(zn);
-            this->append(new ensightPartCells(nPart++, mesh, zn));
-        }
-    }
-
-    if (!nPart)
-    {
-        // No zones at all? - do entire mesh. Name as per ensightMesh
-        this->append(new ensightPartCells(nPart++, mesh, "internalMesh"));
-    }
-    else
-    {
-        // Flip from zoned to unzoned
-        selection.flip();
-
-        if (returnReduce(selection.any(), orOp<bool>()))
-        {
-            this->append
-            (
-                new ensightPartCells(nPart++, mesh, selection, "__internal__")
-            );
-        }
-    }
-
-
-    // Do boundaries, skipping empty and processor patches
-    for (const polyPatch& p : mesh.boundaryMesh())
-    {
-        if (isA<processorPolyPatch>(p))
-        {
-            // No processor patches
-            break;
-        }
-
-        if (returnReduce(!p.empty(), orOp<bool>()))
-        {
-            this->append(new ensightPartFaces(nPart++, mesh, p));
-        }
-    }
-}
-
-
-void Foam::ensightParts::write(ensightGeoFile& os) const
-{
-    // Some feedback
-    Info<< "Write geometry part (" << flush;
-
-    for (const ensightPart& part : *this)
-    {
-        Info<< ' ' << part.index() << flush;
-        part.write(os);
-    }
-    Info<< " )" << endl;
-}
-
-
-void Foam::ensightParts::writeSummary(Ostream& os) const
-{
-    for (const ensightPart& part : *this)
-    {
-        part.writeSummary(os);
-    }
-}
-
-
-void Foam::ensightParts::dumpInfo(Ostream& os) const
-{
-    for (const ensightPart& part : *this)
-    {
-        part.dumpInfo(os);
-    }
-}
-
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightParts.H b/src/fileFormats/ensight/part/ensightParts.H
deleted file mode 100644
index 6ee3b3b1f14..00000000000
--- a/src/fileFormats/ensight/part/ensightParts.H
+++ /dev/null
@@ -1,120 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
--------------------------------------------------------------------------------
-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::ensightParts
-
-Description
-    A collection of several ensightPart elements
-
-SourceFiles
-    ensightParts.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef ensightParts_H
-#define ensightParts_H
-
-#include "SLPtrList.H"
-#include "ensightPart.H"
-#include "ensightPartFaces.H"
-#include "ensightPartCells.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-/*---------------------------------------------------------------------------*\
-                        Class ensightParts Declaration
-\*---------------------------------------------------------------------------*/
-
-class ensightParts
-:
-    public SLPtrList<ensightPart>
-{
-    // Private Member Functions
-
-        //- No copy construct
-        ensightParts(const ensightParts&) = delete;
-
-        //- No copy assignment
-        void operator=(const ensightParts&) = delete;
-
-
-public:
-
-    //- Storage type used
-    typedef SLPtrList<ensightPart> StorageType;
-
-
-    // Constructors
-
-        //- Construct from polyMesh
-        explicit ensightParts(const polyMesh& mesh);
-
-
-    //- Destructor
-    ~ensightParts() = default;
-
-
-    // Member Functions
-
-        //- Number of parts
-        using StorageType::size;
-
-        //- Clear old information and construct anew from polyMesh
-        void recalculate(const polyMesh& mesh);
-
-
-    // Output
-
-        //- Write summary information about the objects
-        void writeSummary(Ostream& os) const;
-
-        //- Print various types of debugging information
-        void dumpInfo(Ostream& os) const;
-
-        //- Write the geometry to file
-        void write(autoPtr<ensightGeoFile>& os) const
-        {
-            write(os.ref());
-        }
-
-        //- Write the geometry to file
-        void write(ensightGeoFile& os) const;
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightFaces.C b/src/fileFormats/ensight/part/faces/ensightFaces.C
similarity index 52%
rename from src/fileFormats/ensight/part/ensightFaces.C
rename to src/fileFormats/ensight/part/faces/ensightFaces.C
index 425ebc6f968..d818a06536e 100644
--- a/src/fileFormats/ensight/part/ensightFaces.C
+++ b/src/fileFormats/ensight/part/faces/ensightFaces.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2018 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,16 +31,27 @@ License
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
+namespace Foam
+{
+    defineTypeNameAndDebug(ensightFaces, 0);
+}
+
 const char* Foam::ensightFaces::elemNames[3] =
     { "tria3", "quad4", "nsided" };
 
+static_assert
+(
+    Foam::ensightFaces::nTypes == 3,
+    "Support exactly 3 face types (tria3, quad4, nsided)"
+);
+
 
 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
 namespace
 {
 
-// Simple shape classifier
+// Trivial shape classifier
 static inline Foam::ensightFaces::elemType whatType(const Foam::face& f)
 {
     return
@@ -58,48 +69,26 @@ static inline Foam::ensightFaces::elemType whatType(const Foam::face& f)
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-// Only used in this file-scope
-inline void Foam::ensightFaces::add
-(
-    const face& f,
-    const label id,
-    const bool flip
-)
+void Foam::ensightFaces::resizeAll()
 {
-    const enum elemType what = whatType(f);
+    // Assign sub-list offsets, determine overall size
 
-    // linear addressing:
-    const label index = offset(what) + sizes_[what]++;
-
-    address_[index] = id;
-    if (flipMap_.size())
-    {
-        flipMap_[index] = flip;
-    }
-}
+    label len = 0;
 
+    auto iter = offsets_.begin();
 
-void Foam::ensightFaces::resizeAll()
-{
-    // overall required size
-    label n = 0;
-    forAll(sizes_, typei)
+    *iter = 0;
+    for (const label n : sizes_)
     {
-        n += sizes_[typei];
-    }
-    address_.setSize(n, Zero);
+        len += n;
 
-    // assign corresponding sub-lists
-    n = 0;
-    forAll(sizes_, typei)
-    {
-        slices_[typei].setStart(n);
-        slices_[typei].setSize(sizes_[typei]);
-
-        n += sizes_[typei];
+        *(++iter) = len;
     }
 
-    // normally assume no flipMap
+    // The addressing space
+    addressing().resize(len, Zero);
+
+    // Normally assume no flipMap
     flipMap_.clear();
 }
 
@@ -108,40 +97,18 @@ void Foam::ensightFaces::resizeAll()
 
 Foam::ensightFaces::ensightFaces()
 :
-    ensightFaces(0)
-{}
-
-
-Foam::ensightFaces::ensightFaces(const label partIndex)
-:
-    index_(partIndex),
-    address_(),
+    ensightPart(),
     flipMap_(),
-    slices_(),
+    offsets_(Zero),
     sizes_(Zero)
-{
-    resizeAll(); // adjust allocation/sizing
-}
+{}
 
 
-Foam::ensightFaces::ensightFaces(const ensightFaces& obj)
+Foam::ensightFaces::ensightFaces(const string& description)
 :
-    index_(obj.index_),
-    address_(obj.address_),
-    flipMap_(obj.flipMap_),
-    slices_(),
-    sizes_()
+    ensightFaces()
 {
-    // Save the total (reduced) sizes
-    FixedList<label, 3> totSizes = obj.sizes_;
-
-    // Need local sizes for the resize operation
-    this->sizes_ = obj.sizes();
-
-    resizeAll(); // adjust allocation/sizing
-
-    // Restore total (reduced) sizes
-    this->sizes_ = totSizes;
+    rename(description);
 }
 
 
@@ -150,9 +117,10 @@ Foam::ensightFaces::ensightFaces(const ensightFaces& obj)
 Foam::FixedList<Foam::label, 3> Foam::ensightFaces::sizes() const
 {
     FixedList<label, 3> count;
-    forAll(slices_, typei)
+
+    forAll(count, typei)
     {
-        count[typei] = slices_[typei].size();
+        count[typei] = size(elemType(typei));
     }
 
     return count;
@@ -172,17 +140,26 @@ Foam::label Foam::ensightFaces::total() const
 
 void Foam::ensightFaces::clear()
 {
-    sizes_ = Zero;  // reset sizes
-    resizeAll();
+    clearOut();
+
+    ensightPart::clear();
+
+    flipMap_.clear();
+    sizes_ = Zero;
+    offsets_ = Zero;
 }
 
 
+void Foam::ensightFaces::clearOut()
+{}
+
+
 void Foam::ensightFaces::reduce()
 {
     // No listCombineGather, listCombineScatter for FixedList
     forAll(sizes_, typei)
     {
-        sizes_[typei] = slices_[typei].size();
+        sizes_[typei] = size(elemType(typei));
         Foam::reduce(sizes_[typei], sumOp<label>());
     }
 }
@@ -190,89 +167,116 @@ void Foam::ensightFaces::reduce()
 
 void Foam::ensightFaces::sort()
 {
-    if (flipMap_.size() == address_.size())
+    const bool useFlip = (size() == flipMap_.size());
+
+    if (useFlip)
     {
         // Must sort flip map as well
         labelList order;
 
-        forAll(slices_, typei)
+        for (int typei=0; typei < nTypes; ++typei)
         {
-            if (slices_[typei].size())
+            const labelRange sub(range(elemType(typei)));
+
+            if (!sub.empty())
             {
-                SubList<label> idLst(address_, slices_[typei]);
-                SubList<bool>  flip(flipMap_, slices_[typei]);
+                SubList<label> ids(addressing(), sub);
+                SubList<bool> flips(flipMap_, sub);
 
-                Foam::sortedOrder(idLst, order);
+                Foam::sortedOrder(ids, order);
 
-                idLst = reorder<labelList>(order, idLst);
-                flip  = reorder<boolList>(order,  flip);
+                ids  = reorder<labelList>(order, ids);
+                flips = reorder<boolList>(order,  flips);
             }
         }
     }
     else
     {
-        // no flip-maps, simpler to sort
-        forAll(slices_, typei)
+        flipMap_.clear();  // Extra safety
+
+        // No flip-maps, simply sort addresses
+        for (int typei=0; typei < nTypes; ++typei)
         {
-            if (slices_[typei].size())
+            const labelRange sub(range(elemType(typei)));
+
+            if (!sub.empty())
             {
-                SubList<label> idLst(address_, slices_[typei]);
-                Foam::sort(idLst);
+                SubList<label> ids(addressing(), sub);
+                Foam::sort(ids);
             }
         }
-
-        flipMap_.clear();  // for extra safety
     }
 }
 
 
-void Foam::ensightFaces::classify(const faceList& faces)
+void Foam::ensightFaces::classify(const UList<face>& faces)
 {
-    const label sz = faces.size();
+    const label len = faces.size();
 
     // Pass 1: Count the shapes
 
     sizes_ = Zero;  // reset sizes
-    for (label listi = 0; listi < sz; ++listi)
+    for (label listi = 0; listi < len; ++listi)
     {
-        const enum elemType what = whatType(faces[listi]);
-        sizes_[what]++;
+        const auto etype = whatType(faces[listi]);
+
+        ++sizes_[etype];
     }
 
     resizeAll();    // adjust allocation
     sizes_ = Zero;  // reset sizes - use for local indexing here
 
+
     // Pass 2: Assign face-id per shape type
 
-    for (label listi = 0; listi < sz; ++listi)
+    for (label listi = 0; listi < len; ++listi)
     {
-        add(faces[listi], listi);
+        const auto etype = whatType(faces[listi]);
+
+        add(etype, listi);
     }
 }
 
 
 void Foam::ensightFaces::classify
 (
-    const faceList& faces,
-    const labelUList& addressing,
+    const UList<face>& faces,
+    const labelRange& range
+)
+{
+    const labelRange slice(range.subset0(faces.size()));
+
+    // Operate on a local slice
+    classify(SubList<face>(slice, faces));
+
+    // Fixup to use the real faceIds instead of the 0-based slice
+    incrAddressing(slice.start());
+}
+
+
+void Foam::ensightFaces::classify
+(
+    const UList<face>& faces,
+    const labelUList& addr,
     const boolList& flipMap,
     const bitSet& exclude
 )
 {
-    const label sz = addressing.size();
-    const bool useFlip = (addressing.size() == flipMap.size());
+    const label len = addr.size();
+    const bool useFlip = (len == flipMap.size());
 
     // Pass 1: Count the shapes
 
     sizes_ = Zero;  // reset sizes
-    for (label listi = 0; listi < sz; ++listi)
+    for (label listi = 0; listi < len; ++listi)
     {
-        const label faceId = addressing[listi];
+        const label faceId = addr[listi];
 
         if (!exclude.test(faceId))
         {
-            const enum elemType what = whatType(faces[faceId]);
-            sizes_[what]++;
+            const auto etype = whatType(faces[faceId]);
+
+            ++sizes_[etype];
         }
     }
 
@@ -281,22 +285,53 @@ void Foam::ensightFaces::classify
 
     if (useFlip)
     {
-        flipMap_.setSize(address_.size(), false);
+        flipMap_.resize(len);
         flipMap_ = false;
     }
+    else
+    {
+        flipMap_.clear();  // Extra safety
+    }
 
     // Pass 2: Assign face-id per shape type
 
-    for (label listi = 0; listi < sz; ++listi)
+    for (label listi = 0; listi < len; ++listi)
     {
-        const label faceId = addressing[listi];
+        const label faceId = addr[listi];
         const bool  doFlip = useFlip && flipMap[listi];
 
         if (!exclude.test(faceId))
         {
-            add(faces[faceId], faceId, doFlip);
+            const auto etype = whatType(faces[faceId]);
+
+            add(etype, faceId, doFlip);
         }
     }
 }
 
+
+void Foam::ensightFaces::writeDict(Ostream& os, const bool full) const
+{
+    os.beginBlock(type());
+
+    os.writeEntry("id",     index()+1); // Ensight starts with 1
+    os.writeEntry("name",   name());
+    os.writeEntry("size",   size());
+
+    if (full)
+    {
+        for (int typei=0; typei < ensightFaces::nTypes; ++typei)
+        {
+            const auto etype = ensightFaces::elemType(typei);
+
+            os.writeKeyword(ensightFaces::key(etype));
+
+            faceIds(etype).writeList(os, 0) << endEntry;  // Flat output
+        }
+    }
+
+    os.endBlock();
+}
+
+
 // ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/faces/ensightFaces.H b/src/fileFormats/ensight/part/faces/ensightFaces.H
new file mode 100644
index 00000000000..0faa6004822
--- /dev/null
+++ b/src/fileFormats/ensight/part/faces/ensightFaces.H
@@ -0,0 +1,264 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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::ensightFaces
+
+Description
+    Sorting/classification of faces (2D) into corresponding ensight types.
+
+    Some caution may be required when handling face addressing into a
+    boundaryField. Since the face addressing is absolute, it will be
+    necessary to work on a copy with local ids. For example,
+
+    \code
+        // Operate on a copy
+        ensightFaces localPart(part);
+
+        // Change from global faceIds to patch-local faceIds
+        localPart.decrFaceIds(patchStart);
+
+        // Can now address into boundaryField
+    \endcode
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ensightFaces_H
+#define ensightFaces_H
+
+#include "ensightPart.H"
+#include "boolList.H"
+#include "faceList.H"
+#include "FixedList.H"
+#include "bitSet.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+class polyMesh;
+
+/*---------------------------------------------------------------------------*\
+                        Class ensightFaces Declaration
+\*---------------------------------------------------------------------------*/
+
+class ensightFaces
+:
+    public ensightPart
+{
+public:
+
+    // Public Data
+
+        //- Supported ensight 'Face' element types.
+        //  Must be zero-based since they are also for internal bookkeeping.
+        enum elemType
+        {
+            TRIA3 = 0,  //!< "tria3"
+            QUAD4,      //!< "quad4"
+            NSIDED      //!< "nsided"
+        };
+
+        //- Number of 'Face' element types (3)
+        static constexpr int nTypes = 3;
+
+        //- The ensight 'Face' element type names
+        static const char* elemNames[nTypes];
+
+
+    // Static Functions
+
+        //- The ensight element name for the specified 'Face' type
+        static inline const char* key(const elemType etype);
+
+
+private:
+
+    // Private Data
+
+        //- Linear list of face-flips
+        boolList flipMap_;
+
+        //- Begin/end offsets for address/flips of each element type
+        FixedList<label, nTypes+1> offsets_;
+
+        //- List of global sizes for each element type.
+        //  Used temporarily for local sizes when building the element lists.
+        FixedList<label, nTypes> sizes_;
+
+
+    // Private Member Functions
+
+        //- Low-level internal addition routine
+        inline void add(const elemType etype, label id, bool flip=false);
+
+        //- Use temporarily stored sizes to redimension the element lists
+        void resizeAll();
+
+
+public:
+
+    //- Declare type-name, virtual type (with debug switch)
+    TypeName("ensightFaces");
+
+
+    // Constructors
+
+        //- Default construct, with part index 0
+        ensightFaces();
+
+        //- Default construct, with description/partName
+        explicit ensightFaces(const string& description);
+
+
+    //- Destructor
+    virtual ~ensightFaces() = default;
+
+
+    // Member Functions
+
+    // Access
+
+        //- Processor-local size of all elements.
+        using ensightPart::size;
+
+        //- Processor-local size of the specified element type.
+        inline label size(const elemType etype) const;
+
+        //- Processor-local offset/size of element type.
+        inline labelRange range(const elemType etype) const;
+
+        //- The global size of all element types.
+        //  This value is only meaningful after a reduce operation.
+        label total() const;
+
+        //- The global size of the specified element type.
+        //  This value is only meaningful after a reduce operation.
+        inline label total(const elemType etype) const;
+
+        //- The global sizes for each element type.
+        //  This value is only meaningful after a reduce operation.
+        inline const FixedList<label, nTypes>& totals() const;
+
+        //- Processor-local sizes per element type.
+        FixedList<label, nTypes> sizes() const;
+
+        //- Processor-local face ids of all elements
+        inline const labelList& faceIds() const;
+
+        //- Processor-local face ids of the specified element type
+        inline const labelUList faceIds(const elemType etype) const;
+
+        //- Processor-local flip-map of all elements
+        inline const boolList& flipMap() const;
+
+        //- True for non-zero flip-map that spans the addresses
+        inline bool usesFlipMap() const;
+
+
+    // Edit
+
+        //- Classify the face types and set the element lists.
+        void classify(const UList<face>& faces);
+
+        //- Classify face types (for a sublist) and set element lists.
+        void classify(const UList<face>& faces, const labelRange& range);
+
+        //- Classify the face types and set the element lists.
+        //  The indirect addressing can be used when classifying groups of
+        //  face (eg, from a faceZone etc) with an optional flipMap.
+        //  The optional exclude marker can be used to skip faces on particular
+        //  boundary types or regions.
+        void classify
+        (
+            const UList<face>& faces,
+            const labelUList& addr,
+            const boolList& flipMap = boolList(),
+            const bitSet& exclude = bitSet()
+        );
+
+
+        //- Clear any demand-driven data
+        void clearOut();
+
+        //- Set addressable sizes to zero, free up addressing memory.
+        void clear();
+
+        //- Sum element counts across all processes.
+        void reduce();
+
+        //- Sort element lists numerically.
+        void sort();
+
+
+    // Advanced (use with caution)
+
+        //- Increase face ids by specified offset value
+        //  Eg, to change patch local Ids to global Ids
+        void incrFaceIds(const label off);
+
+        //- Decrease face ids by specified offset value
+        //  Eg, to change global Ids to patch local Ids
+        void decrFaceIds(const label off);
+
+
+    // Output
+
+        //- Globally unique mesh points.
+        //- Required when writing point fields.
+        label uniqueMeshPoints
+        (
+            const polyMesh& mesh,
+            labelList& uniqueMeshPointLabels,
+            bool parallel
+        ) const;
+
+        //- Write information about the object as a dictionary,
+        //- optionally write all element addresses
+        virtual void writeDict(Ostream& os, const bool full=false) const;
+
+        //- Write geometry, using a mesh reference (serial only)
+        virtual void write
+        (
+            ensightGeoFile& os,
+            const polyMesh& mesh,
+            bool parallel
+        ) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "ensightFacesI.H"
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/faces/ensightFacesAddr.C b/src/fileFormats/ensight/part/faces/ensightFacesAddr.C
new file mode 100644
index 00000000000..bbfb4e21512
--- /dev/null
+++ b/src/fileFormats/ensight/part/faces/ensightFacesAddr.C
@@ -0,0 +1,96 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 "ensightFaces.H"
+#include "ensightOutput.H"
+
+#include "polyMesh.H"
+#include "globalIndex.H"
+#include "globalMeshData.H"
+#include "uindirectPrimitivePatch.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::label Foam::ensightFaces::uniqueMeshPoints
+(
+    const polyMesh& mesh,
+    labelList& uniqueMeshPointLabels,
+    bool parallel
+) const
+{
+    const ensightFaces& part = *this;
+
+    parallel = parallel && Pstream::parRun();
+
+    // Renumber the patch points/faces into unique points
+    label nPoints = 0;  // Total number of points
+    labelList pointToGlobal;  // local point to unique global index
+
+    const pointField& points = mesh.points();
+    const faceList& faces = mesh.faces();
+
+
+    // Use the properly sorted faceIds (ensightFaces) and do NOT use
+    // the faceZone or anything else directly, otherwise the
+    // point-maps will not correspond.
+    // - perform face-flipping later
+
+    uindirectPrimitivePatch pp
+    (
+        UIndirectList<face>(faces, part.faceIds()),
+        points
+    );
+
+    if (parallel)
+    {
+        autoPtr<globalIndex> globalPointsPtr =
+            mesh.globalData().mergePoints
+            (
+                pp.meshPoints(),
+                pp.meshPointMap(),
+                pointToGlobal,
+                uniqueMeshPointLabels
+            );
+
+        nPoints = globalPointsPtr().size();  // nPoints (global)
+    }
+    else
+    {
+        // Non-parallel
+        // - all information already available from PrimitivePatch
+
+        nPoints = pp.meshPoints().size();
+        uniqueMeshPointLabels = pp.meshPoints();
+
+        // Not neaded: pointToGlobal
+    }
+
+    return nPoints;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightFacesI.H b/src/fileFormats/ensight/part/faces/ensightFacesI.H
similarity index 52%
rename from src/fileFormats/ensight/part/ensightFacesI.H
rename to src/fileFormats/ensight/part/faces/ensightFacesI.H
index 15799e5610f..b971115ac8e 100644
--- a/src/fileFormats/ensight/part/ensightFacesI.H
+++ b/src/fileFormats/ensight/part/faces/ensightFacesI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2017 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,82 +25,88 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "error.H"
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-inline const char* Foam::ensightFaces::key(const enum elemType what)
+inline void Foam::ensightFaces::add(const elemType etype, label id, bool flip)
 {
-    return elemNames[what];
+    // Linear addressing location
+    const label index = offsets_[etype] + sizes_[etype]++;
+
+    addressing()[index] = id;
+
+    if (flipMap_.size())
+    {
+        flipMap_[index] = flip;
+    }
 }
 
 
-inline Foam::label Foam::ensightFaces::index() const
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+inline const char* Foam::ensightFaces::key(const elemType etype)
 {
-    return index_;
+    return elemNames[etype];
 }
 
 
-inline Foam::label& Foam::ensightFaces::index()
+inline const Foam::FixedList<Foam::label,3>& Foam::ensightFaces::totals() const
 {
-    return index_;
+    return sizes_;
 }
 
 
-inline Foam::label Foam::ensightFaces::size() const
+inline Foam::label Foam::ensightFaces::total(const elemType etype) const
 {
-    return address_.size();
+    return sizes_[etype];
 }
 
 
-inline const Foam::FixedList<Foam::label,3>& Foam::ensightFaces::totals() const
+inline Foam::label Foam::ensightFaces::size(const elemType etype) const
 {
-    return sizes_;
+    return (offsets_[etype+1] - offsets_[etype]);
 }
 
 
-inline Foam::label Foam::ensightFaces::total(const enum elemType what) const
+inline Foam::labelRange Foam::ensightFaces::range(const elemType etype) const
 {
-    return sizes_[what];
+    return labelRange(offsets_[etype], offsets_[etype+1] - offsets_[etype]);
 }
 
 
-inline Foam::label Foam::ensightFaces::size(const enum elemType what) const
+inline const Foam::labelList& Foam::ensightFaces::faceIds() const
 {
-    return slices_[what].size();
+    return addressing();
 }
 
 
-inline Foam::label Foam::ensightFaces::offset(const enum elemType what) const
+inline const Foam::labelUList
+Foam::ensightFaces::faceIds(const elemType etype) const
 {
-    return slices_[what].start();
+    return addressing()[range(etype)];
 }
 
 
-inline const Foam::labelUList Foam::ensightFaces::faceIds
-(
-    const enum elemType what
-) const
+inline const Foam::boolList& Foam::ensightFaces::flipMap() const
 {
-    return address_[slices_[what]];
+    return flipMap_;
 }
 
 
-inline const Foam::labelUList& Foam::ensightFaces::faceIds() const
+inline bool Foam::ensightFaces::usesFlipMap() const
 {
-    return address_;
+    return (!flipMap_.empty() && flipMap_.size() == size());
 }
 
 
-inline const Foam::boolList& Foam::ensightFaces::flipMap() const
+inline void Foam::ensightFaces::incrFaceIds(const label off)
 {
-    return flipMap_;
+    incrAddressing(off);
 }
 
 
-inline Foam::label Foam::ensightFaces::operator[](const label i) const
+inline void Foam::ensightFaces::decrFaceIds(const label off)
 {
-    return address_[i];
+    decrAddressing(off);
 }
 
 
diff --git a/src/fileFormats/ensight/part/faces/ensightFacesIO.C b/src/fileFormats/ensight/part/faces/ensightFacesIO.C
new file mode 100644
index 00000000000..4ce459eddb6
--- /dev/null
+++ b/src/fileFormats/ensight/part/faces/ensightFacesIO.C
@@ -0,0 +1,138 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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 "ensightFaces.H"
+#include "ensightOutput.H"
+
+#include "polyMesh.H"
+#include "globalIndex.H"
+#include "globalMeshData.H"
+#include "uindirectPrimitivePatch.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightFaces::write
+(
+    ensightGeoFile& os,
+    const polyMesh& mesh,
+    bool parallel
+) const
+{
+    const ensightFaces& part = *this;
+
+    parallel = parallel && Pstream::parRun();
+
+    // Renumber the patch points/faces into unique points
+    label nPoints = 0;  // Total number of points
+    labelList pointToGlobal;  // local point to unique global index
+    labelList uniqueMeshPointLabels;  // unique global points
+
+
+    const pointField& points = mesh.points();
+    const faceList& faces = mesh.faces();
+
+
+    // Use the properly sorted faceIds (ensightFaces) and do NOT use
+    // the faceZone or anything else directly, otherwise the
+    // point-maps will not correspond.
+    // - perform face-flipping later
+
+    uindirectPrimitivePatch pp
+    (
+        UIndirectList<face>(faces, part.faceIds()),
+        points
+    );
+
+    if (parallel)
+    {
+        autoPtr<globalIndex> globalPointsPtr =
+            mesh.globalData().mergePoints
+            (
+                pp.meshPoints(),
+                pp.meshPointMap(),
+                pointToGlobal,
+                uniqueMeshPointLabels
+            );
+
+        nPoints = globalPointsPtr().size();  // nPoints (global)
+    }
+    else
+    {
+        // Non-parallel
+        // - all information already available from PrimitivePatch
+
+        nPoints = pp.meshPoints().size();
+        uniqueMeshPointLabels = pp.meshPoints();
+
+        pointToGlobal.resize(nPoints);
+        ListOps::identity(pointToGlobal);
+    }
+
+    ensightOutput::Detail::writeCoordinates
+    (
+        os,
+        part.index(),
+        part.name(),
+        nPoints,  // nPoints (global)
+        UIndirectList<point>(points, uniqueMeshPointLabels),
+        parallel  //!< Collective write?
+    );
+
+
+    // Renumber the faces belonging to the faceZone,
+    // from local numbering to unique global index.
+
+    faceList patchFaces(pp.localFaces());
+    ListListOps::inplaceRenumber(pointToGlobal, patchFaces);
+
+    // Also a good place to perform face flipping
+    if (part.usesFlipMap())
+    {
+        const boolList& flip = part.flipMap();
+
+        forAll(patchFaces, facei)
+        {
+            face& f = patchFaces[facei];
+
+            if (flip[facei])
+            {
+                f.flip();
+            }
+        }
+    }
+
+    ensightOutput::writeFaceConnectivityPresorted
+    (
+        os,
+        part,
+        patchFaces,
+        parallel  //!< Collective write?
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPart.C b/src/fileFormats/ensight/part/part/ensightPart.C
similarity index 73%
rename from src/fileFormats/ensight/part/ensightPart.C
rename to src/fileFormats/ensight/part/part/ensightPart.C
index 2092a33f396..70d7d51e2ea 100644
--- a/src/fileFormats/ensight/part/ensightPart.C
+++ b/src/fileFormats/ensight/part/part/ensightPart.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,28 +32,49 @@ License
 
 namespace Foam
 {
-    defineTypeNameAndDebug(ensightPart, 0);
+    defineTypeName(ensightPart);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightPart::incrAddressing(const label off)
+{
+    for (label& val : address_)
+    {
+        val += off;
+    }
+}
+
+
+void Foam::ensightPart::decrAddressing(const label off)
+{
+    for (label& val : address_)
+    {
+        val -= off;
+    }
 }
 
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::ensightPart::ensightPart(const string& description)
+Foam::ensightPart::ensightPart() noexcept
 :
-    name_(description)
+    index_(0),
+    identifier_(-1),
+    name_(),
+    address_()
 {}
 
 
-// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
-
-Foam::ensightGeoFile& Foam::operator<<
-(
-    ensightGeoFile& os,
-    const ensightPart& part
-)
+Foam::ensightPart::ensightPart(const string& description)
+:
+    ensightPart()
 {
-    part.write(os);
-    return os;
+    if (!description.empty())
+    {
+        name_ = description;
+    }
 }
 
 
diff --git a/src/fileFormats/ensight/part/part/ensightPart.H b/src/fileFormats/ensight/part/part/ensightPart.H
new file mode 100644
index 00000000000..9bb3d4e250e
--- /dev/null
+++ b/src/fileFormats/ensight/part/part/ensightPart.H
@@ -0,0 +1,214 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2016-2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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::ensightPart
+
+Description
+    Base class for ensightCells, ensightFaces, ensightOutputSurfaces.
+
+SourceFiles
+    ensightPart.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ensightPart_H
+#define ensightPart_H
+
+#include "ensightGeoFile.H"
+#include "labelList.H"
+#include "typeInfo.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                         Class ensightPart Declaration
+\*---------------------------------------------------------------------------*/
+
+class ensightPart
+{
+    // Private Data
+
+        //- Part index within a list.
+        //  The ensight part number is typically this value +1.
+        label index_;
+
+        //- OpenFOAM identifier (patch index, zone index, etc).
+        //  An unused identifier is -1
+        label identifier_;
+
+        //- Part name (or description)
+        string name_;
+
+        //- Linear list of element ids (face/cell)
+        //  Sub-sectioning by element type is done by derived classes
+        labelList address_;
+
+
+protected:
+
+        //- Element addressing
+        const labelList& addressing() const
+        {
+            return address_;
+        }
+
+        //- Element addressing
+        labelList& addressing()
+        {
+            return address_;
+        }
+
+        //- Clear element addressing
+        void clear()
+        {
+            address_.clear();
+        }
+
+        //- Increase addressing by specified offset value
+        //  Eg, change local to global id
+        void incrAddressing(const label off);
+
+        //- Decrease addressing by specified offset value
+        //  Eg, change global to local id
+        void decrAddressing(const label off);
+
+
+public:
+
+    //- Declare type-name, virtual type (without debug switch)
+    TypeNameNoDebug("ensightPart");
+
+
+    // Constructors
+
+        //- Default construct. Index=0, identifier = -1
+        ensightPart() noexcept;
+
+        //- Default construct, with description/partName
+        explicit ensightPart(const string& description);
+
+
+    //- Destructor
+    virtual ~ensightPart() = default;
+
+
+    // Member Functions
+
+        //- The index in a list (0-based)
+        label index() const
+        {
+            return index_;
+        }
+
+        //- The index in a list (0-based)
+        label& index()
+        {
+            return index_;
+        }
+
+        //- OpenFOAM identifier (patch, zone, etc), -1 when not in use.
+        label identifier() const
+        {
+            return identifier_;
+        }
+
+        //- OpenFOAM identifier (patch, zone, etc), -1 when not in use.
+        label& identifier()
+        {
+            return identifier_;
+        }
+
+        //- Processor-local test for any elements.
+        bool empty() const
+        {
+            return address_.empty();
+        }
+
+        //- Processor-local size of all elements.
+        label size() const
+        {
+            return address_.size();
+        }
+
+        //- The part name or description
+        const string& name() const
+        {
+            return name_;
+        }
+
+        //- Change the part name or description
+        void rename(const string& value)
+        {
+            name_ = value;
+        }
+
+        //- Change the part name or description
+        void rename(string&& value)
+        {
+            name_ = std::move(value);
+        }
+
+
+    // Output
+
+        //- Write information about the object as a dictionary,
+        //- optionally write all element addresses
+        virtual void writeDict(Ostream& os, const bool full=false) const
+        {}
+
+
+    // Member Operators
+
+        //- Processor-local element id from linear-list of addresses.
+        label operator[](const label i) const
+        {
+            return address_[i];
+        }
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Housekeeping
+
+//- Deprecated(2020-02) - use ensightOutput or member write() methods
+//  \deprecated(2020-02) - use ensightOutput or member write() methods
+void operator<<(ensightGeoFile&, const ensightPart&) = delete;
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertVolumeFields.H b/src/fileFormats/ensight/part/surface/ensightOutputSurface.C
similarity index 59%
rename from applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertVolumeFields.H
rename to src/fileFormats/ensight/part/surface/ensightOutputSurface.C
index b5f39307ddd..fa9d577180e 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/convertVolumeFields.H
+++ b/src/fileFormats/ensight/part/surface/ensightOutputSurface.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -23,35 +23,57 @@ License
     You should have received a copy of the GNU General Public License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
-Description
-    Code chunk for converting volume and dimensioned fields
-    included by foamToEnsightParts.
-
 \*---------------------------------------------------------------------------*/
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+#include "ensightOutputSurface.H"
+#include "ensightOutput.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ensightOutputSurface::ensightOutputSurface
+(
+    const pointField& points,
+    const faceList& faces,
+    const string& description
+)
+:
+    ensightFaces(description),
+    points_(points),
+    faces_(faces)
+{
+    // Classify face types
+    classify(faces);
+}
+
 
-// Cell field data output
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::ensightOutputSurface::write(ensightGeoFile& os) const
 {
-    Info<< "Write volume field (";
+    if (!total())
+    {
+        return;
+    }
 
-    writeAllVolFields
+    // Coordinates
+    ensightOutput::Detail::writeCoordinates
     (
-        ensCase,
-        ensParts,
-        mesh,
-        objects
+        os,
+        index(),
+        name(),
+        points_.size(),
+        points_,
+        false // serial
     );
 
-    writeAllDimFields
+    // Faces
+    ensightOutput::writeFaceConnectivity
     (
-        ensCase,
-        ensParts,
-        mesh,
-        objects
+        os,
+        *this,
+        faces_,
+        false  // serial
     );
-
-    Info<< " )" << nl;
 }
 
 
diff --git a/src/fileFormats/ensight/part/surface/ensightOutputSurface.H b/src/fileFormats/ensight/part/surface/ensightOutputSurface.H
new file mode 100644
index 00000000000..33a34f8eb69
--- /dev/null
+++ b/src/fileFormats/ensight/part/surface/ensightOutputSurface.H
@@ -0,0 +1,137 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+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::ensightOutputSurface
+
+Description
+    A variant of ensightFaces that holds references to contiguous
+    points/faces with its own encapsulated write methods.
+    The surface is assumed to have been merged prior, thus the output is
+    serial-only.
+
+Note
+    The primary use is for the Foam::surfaceWriters::ensightWriter
+    but can be used independently as well.
+
+SourceFiles
+    ensightOutputSurface.C
+    ensightOutputSurfaceTemplates.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ensightOutputSurface_H
+#define ensightOutputSurface_H
+
+#include "ensightFaces.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                    Class ensightOutputSurface Declaration
+\*---------------------------------------------------------------------------*/
+
+class ensightOutputSurface
+:
+    public ensightFaces
+{
+    // Private Data
+
+        //- The referenced pointField
+        const pointField& points_;
+
+        //- The referenced faces
+        const faceList& faces_;
+
+
+    // Private Member Functions
+
+        //- No copy construct
+        ensightOutputSurface(const ensightOutputSurface&) = delete;
+
+        //- No copy assignment
+        void operator=(const ensightOutputSurface&) = delete;
+
+
+public:
+
+    // Constructors
+
+        //- Construct from points and faces.
+        //- Part receives the specified name (default: "surface").
+        ensightOutputSurface
+        (
+            const pointField& points,
+            const faceList& faces,
+            const string& description = "surface"
+        );
+
+
+    //- Destructor
+    virtual ~ensightOutputSurface() = default;
+
+
+    // Member Functions
+
+        //- Write processor-local geometry (serial-only)
+        void write(ensightGeoFile& os) const;
+
+        //- Write a field of face or point values (serial-only)
+        template<class Type>
+        void writeData
+        (
+            ensightFile& os,
+            const Field<Type>& fld,
+            const bool isPointData = false
+        ) const;
+
+        //- Write a field of face values (serial-only)
+        template<class Type>
+        void writeFaceData(ensightFile& os, const Field<Type>& fld) const;
+
+        //- Write a field of point values (serial-only)
+        template<class Type>
+        void writePointData(ensightFile& os, const Field<Type>& fld) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "ensightOutputSurfaceTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.C b/src/fileFormats/ensight/part/surface/ensightOutputSurfaceTemplates.C
similarity index 54%
rename from applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.C
rename to src/fileFormats/ensight/part/surface/ensightOutputSurfaceTemplates.C
index 10cbaa2c950..0d2296f1172 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/readFields.C
+++ b/src/fileFormats/ensight/part/surface/ensightOutputSurfaceTemplates.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,56 +25,72 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "readFields.H"
-#include "volFields.H"
+#include "ensightOutputSurface.H"
+#include "ensightOutput.H"
 
-// * * * * * * * * * * * * * * * Global Functions  * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-Foam::label Foam::checkData
+template<class Type>
+void Foam::ensightOutputSurface::writeData
 (
-    const fvMesh& mesh,
-    const instantList& timeDirs,
-    wordList& objectNames
-)
+    ensightFile& os,
+    const Field<Type>& fld,
+    const bool isPointData
+) const
 {
-    // Assume prune_0() was used prior to calling this
+    if (isPointData)
+    {
+        this->writePointData(os, fld);
+    }
+    else
+    {
+        this->writeFaceData(os, fld);
+    }
+}
+
+
+template<class Type>
+void Foam::ensightOutputSurface::writeFaceData
+(
+    ensightFile& os,
+    const Field<Type>& fld
+) const
+{
+    ensightOutput::writeField
+    (
+        os,
+        fld,
+        *this,
+        false  /* serial only! */
+    );
+}
 
-    wordHashSet goodFields;
 
-    for (const word& fieldName : objectNames)
+template<class Type>
+void Foam::ensightOutputSurface::writePointData
+(
+    ensightFile& os,
+    const Field<Type>& fld
+) const
+{
+    const ensightOutputSurface& part = *this;
+
+    // No geometry or field
+    if (part.empty() || fld.empty())
     {
-        bool good = false;
-
-        for (const instant& inst : timeDirs)
-        {
-            good =
-                IOobject
-                (
-                    fieldName,
-                    inst.name(),
-                    mesh,
-                    IOobject::NO_READ,
-                    IOobject::NO_WRITE,
-                    false  // no register
-                ).typeHeaderOk<volScalarField>(false, false);
-
-            if (!good)
-            {
-                break;
-            }
-        }
-
-        reduce(good, andOp<bool>());
-
-        if (good)
-        {
-            goodFields.insert(fieldName);
-        }
+        return;
     }
 
-    objectNames = goodFields.sortedToc();
 
-    return objectNames.size();
+    os.beginPart(part.index());
+
+    ensightOutput::Detail::writeFieldComponents
+    (
+        os,
+        ensightFile::coordinates,
+        fld,
+        false  /* serial only! */
+    );
 }
 
 
diff --git a/src/functionObjects/utilities/ensightWrite/ensightWrite.C b/src/functionObjects/utilities/ensightWrite/ensightWrite.C
index e2ca33cf1b5..06bcdacad4c 100644
--- a/src/functionObjects/utilities/ensightWrite/ensightWrite.C
+++ b/src/functionObjects/utilities/ensightWrite/ensightWrite.C
@@ -77,7 +77,8 @@ Foam::functionObjects::ensightWrite::ensightWrite
 )
 :
     fvMeshFunctionObject(name, runTime, dict),
-    writeOpts_
+    writeOpts_(),
+    caseOpts_
     (
         IOstreamOption::formatNames.lookupOrDefault
         (
@@ -87,7 +88,6 @@ Foam::functionObjects::ensightWrite::ensightWrite
             true  // Failsafe behaviour
         )
     ),
-    caseOpts_(writeOpts_.format()),
     outputDir_(),
     consecutive_(false),
     meshState_(polyMesh::TOPO_CHANGE),
diff --git a/src/functionObjects/utilities/ensightWrite/ensightWriteTemplates.C b/src/functionObjects/utilities/ensightWrite/ensightWriteTemplates.C
index 785aca83107..8156cd0acbd 100644
--- a/src/functionObjects/utilities/ensightWrite/ensightWriteTemplates.C
+++ b/src/functionObjects/utilities/ensightWrite/ensightWriteTemplates.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -58,9 +58,9 @@ Foam::label Foam::functionObjects::ensightWrite::writeVolFields
 
         ensightOutput::writeVolField<Type>
         (
+            os.ref(),
             field,
             ensMesh(),
-            os.ref(),
             caseOpts_.nodeValues()
         );
 
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriter.C b/src/surfMesh/writers/ensight/ensightSurfaceWriter.C
index 615983b1550..19d2aae5c0d 100644
--- a/src/surfMesh/writers/ensight/ensightSurfaceWriter.C
+++ b/src/surfMesh/writers/ensight/ensightSurfaceWriter.C
@@ -31,8 +31,8 @@ License
 #include "Fstream.H"
 #include "OSspecific.H"
 #include "ensightCase.H"
-#include "ensightPartFaces.H"
 #include "ensightOutput.H"
+#include "ensightOutputSurface.H"
 #include "ensightPTraits.H"
 #include "surfaceWriterMethods.H"
 #include "addToRunTimeSelectionTable.H"
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C b/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C
index a92350dffb3..4dc8fdeeda3 100644
--- a/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C
+++ b/src/surfMesh/writers/ensight/ensightSurfaceWriterCollated.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2014 OpenFOAM Foundation
-    Copyright (C) 2015-2019 OpenCFD Ltd.
+    Copyright (C) 2015-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -405,29 +405,28 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
 
         const fileName meshFile(baseDir/geometryName);
 
-        // Write geometry
-        ensightPartFaces ensPart
+        // Ensight Geometry
+        ensightOutputSurface part
         (
-            0,
-            meshFile.name(),
             surf.points(),
             surf.faces(),
-            true // contiguous points
+            meshFile.name()
         );
+
         if (!exists(meshFile))
         {
             if (verbose_)
             {
                 Info<< "Writing mesh file to " << meshFile.name() << endl;
             }
-            // Use two-argument form for path-name to avoid validating base-dir
+            // Two-argument form for path-name to avoid validating base-dir
             ensightGeoFile osGeom
             (
                 meshFile.path(),
                 meshFile.name(),
                 writeFormat_
             );
-            osGeom << ensPart;
+            part.write(osGeom); // serial
         }
 
         // Write field
@@ -443,31 +442,11 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
             Info<< "Writing field file to " << osField.name() << endl;
         }
 
-        // Write field
+        // Write field (serial only)
         osField.writeKeyword(ensightPTraits<Type>::typeName);
+        part.writeData(osField, tfield(), this->isPointData());
 
-        if (this->isPointData())
-        {
-            ensightOutput::Serial::writePointField
-            (
-                tfield(),
-                ensPart,
-                osField
-                // serial
-            );
-        }
-        else
-        {
-            ensightOutput::Detail::writeFaceField
-            (
-                tfield(),
-                ensPart,
-                osField,
-                false // serial
-            );
-        }
-
-        // Place a timestamp in the directory for future reference
+        // Timestamp in the directory for future reference
         {
             OFstream timeStamp(dataDir/"time");
             timeStamp
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C b/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C
index e5c60b99947..6a129ca0230 100644
--- a/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C
+++ b/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2014 OpenFOAM Foundation
-    Copyright (C) 2015-2019 OpenCFD Ltd.
+    Copyright (C) 2015-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -87,15 +87,13 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated()
 
         printTimeset(osCase, 1, 0.0);
 
-        ensightPartFaces ensPart
+        ensightOutputSurface part
         (
-            0,
-            osGeom.name().name(),
             surf.points(),
             surf.faces(),
-            true // contiguous points
+            osGeom.name().name()
         );
-        osGeom << ensPart;
+        part.write(osGeom); // serial
     }
 
     wroteGeom_ = true;
@@ -173,6 +171,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated
         osCase.setf(ios_base::scientific, ios_base::floatfield);
         osCase.precision(5);
 
+        // Two-argument form for path-name to avoid validating base-dir
         ensightGeoFile osGeom
         (
             baseDir,
@@ -212,39 +211,18 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated
         osCase << "# end" << nl;
 
 
-        ensightPartFaces ensPart
+        // Ensight Geometry
+        ensightOutputSurface part
         (
-            0,
-            osGeom.name().name(),
             surf.points(),
             surf.faces(),
-            true // contiguous points
+            osGeom.name().name()
         );
-        osGeom << ensPart;
+        part.write(osGeom); // serial
 
-        // Write field
+        // Write field (serial)
         osField.writeKeyword(ensightPTraits<Type>::typeName);
-
-        if (this->isPointData())
-        {
-            ensightOutput::Serial::writePointField
-            (
-                tfield(),
-                ensPart,
-                osField
-                // serial
-            );
-        }
-        else
-        {
-            ensightOutput::Detail::writeFaceField
-            (
-                tfield(),
-                ensPart,
-                osField,
-                false // serial
-            );
-        }
+        part.writeData(osField, tfield(), this->isPointData());
     }
 
     wroteGeom_ = true;
-- 
GitLab