From cc51190a725c7a5abc87fced50e5562ca2178048 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Tue, 28 Jan 2020 19:42:51 +0100
Subject: [PATCH] ENH: revise ensight handling (#1579)

- simplify internals of low-level ensight part handling and refactor
  the backend output routines for more reuse. There is still a fair
  bit of residual ugliness in the details of handling ensight output.

- added cellZone support for ensightMesh

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

* foamToEnsight:

  - added cellZone support, which was previously only available using
    foamToEnsightParts. Will seek to deprecate and remove
    foamToEnsightParts in the future.

  - now also converts point fields
---
 .../foamToEnsight/convertVolumeFields.H       |   9 +
 .../foamToEnsight/foamToEnsight.C             |  68 +-
 .../dataConversion/foamToEnsight/readFields.H |  15 +
 .../foamToEnsight/writePointFields.H          | 149 ++++
 .../foamToEnsight/writeVolFields.H            |   2 +-
 .../foamToEnsightParts/foamToEnsightParts.C   |   2 +-
 .../foamToEnsightParts/writeVolFields.H       |   5 +-
 src/conversion/ensight/mesh/ensightMesh.C     | 516 ++++++++----
 src/conversion/ensight/mesh/ensightMesh.H     | 340 +++-----
 src/conversion/ensight/mesh/ensightMeshI.H    |  37 +-
 src/conversion/ensight/mesh/ensightMeshIO.C   | 747 +++---------------
 .../ensight/mesh/ensightMeshOptions.C         | 212 ++++-
 .../ensight/output/ensightOutputVolField.H    |  40 +-
 .../output/ensightOutputVolFieldTemplates.C   | 250 +++---
 src/fileFormats/Make/files                    |   8 +
 src/fileFormats/ensight/file/ensightCase.C    |  49 +-
 src/fileFormats/ensight/file/ensightCase.H    |  90 ++-
 .../ensight/file/ensightCaseOptions.C         |  10 +-
 .../ensight/file/ensightCaseTemplates.C       |  21 +-
 .../ensight/output/ensightOutput.C            | 490 ++++++++++++
 .../ensight/output/ensightOutput.H            | 240 +++++-
 .../ensight/output/ensightOutputTemplates.C   | 371 +++++----
 src/fileFormats/ensight/part/ensightCells.C   | 155 ++--
 src/fileFormats/ensight/part/ensightCells.H   | 142 ++--
 .../ensight/part/ensightCellsAddr.C           |  64 ++
 src/fileFormats/ensight/part/ensightCellsI.H  |  59 +-
 src/fileFormats/ensight/part/ensightFaces.C   | 241 +++---
 src/fileFormats/ensight/part/ensightFaces.H   | 150 ++--
 src/fileFormats/ensight/part/ensightFacesI.H  |  64 +-
 .../ensight/part/ensightOutputSurface.C       |  92 +++
 .../ensight/part/ensightOutputSurface.H       | 114 +++
 src/fileFormats/ensight/part/ensightPart.C    |  62 +-
 src/fileFormats/ensight/part/ensightPart.H    | 175 ++--
 .../ensight/part/ensightPartCells.C           | 286 +------
 .../ensight/part/ensightPartCells.H           |  91 ++-
 .../ensight/part/ensightPartCellsAddr.C       | 113 +++
 .../ensight/part/ensightPartCellsIO.C         | 152 ++++
 .../ensight/part/ensightPartFaces.C           | 255 +-----
 .../ensight/part/ensightPartFaces.H           | 116 +--
 .../ensight/part/ensightPartFacesAddr.C       | 110 +++
 .../ensight/part/ensightPartFacesIO.C         | 120 +++
 src/fileFormats/ensight/part/ensightParts.C   |  61 +-
 src/fileFormats/ensight/part/ensightParts.H   |  27 +-
 .../ensightWrite/ensightWriteTemplates.C      |   4 +-
 .../writers/ensight/ensightSurfaceWriter.C    |   2 +-
 .../ensight/ensightSurfaceWriterCollated.C    |  23 +-
 .../ensight/ensightSurfaceWriterUncollated.C  |  29 +-
 47 files changed, 3794 insertions(+), 2584 deletions(-)
 create mode 100644 applications/utilities/postProcessing/dataConversion/foamToEnsight/writePointFields.H
 create mode 100644 src/fileFormats/ensight/output/ensightOutput.C
 create mode 100644 src/fileFormats/ensight/part/ensightCellsAddr.C
 create mode 100644 src/fileFormats/ensight/part/ensightOutputSurface.C
 create mode 100644 src/fileFormats/ensight/part/ensightOutputSurface.H
 create mode 100644 src/fileFormats/ensight/part/ensightPartCellsAddr.C
 create mode 100644 src/fileFormats/ensight/part/ensightPartCellsIO.C
 create mode 100644 src/fileFormats/ensight/part/ensightPartFacesAddr.C
 create mode 100644 src/fileFormats/ensight/part/ensightPartFacesIO.C

diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H
index f2d775a8042..439b2dc4373 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/convertVolumeFields.H
@@ -39,6 +39,15 @@ Description
     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 552b0b62a85..b98acb7072b 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/foamToEnsight.C
@@ -110,6 +110,7 @@ Note
 #include "readFields.H"
 #include "writeVolFields.H"
 #include "writeDimFields.H"
+#include "writePointFields.H"
 
 #include "memInfo.H"
 
@@ -155,12 +156,29 @@ 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. No interpolated PointData."
+    );
+
+    // argList::addBoolOption
+    // (
+    //     "one-boundary",  // allPatches
+    //     "Combine all patches into a single part"
+    // );
+
     argList::addOption
     (
         "patches",
@@ -169,6 +187,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",
@@ -182,7 +208,6 @@ int main(int argc, char *argv[])
         "Specify single or multiple fields to write (all by default)\n"
         "Eg, 'T' or '( \"U.*\" )'"
     );
-#if 0
     argList::addOption
     (
         "cellZones",
@@ -192,9 +217,6 @@ int main(int argc, char *argv[])
         true  // mark as an advanced option
     );
     argList::addOptionCompat("cellZone", {"cellZones", 1912});
-#else
-    argList::ignoreOptionCompat({"cellZones", 1912}, true);  // has argument
-#endif
 
     argList::addOption
     (
@@ -264,16 +286,31 @@ int main(int argc, char *argv[])
     ensightMesh::options writeOpts(format);
     writeOpts.useBoundaryMesh(!args.found("no-boundary"));
     writeOpts.useInternalMesh(!args.found("no-internal"));
+    writeOpts.useCellZones(!args.found("no-cellZones"));
     const bool doLagrangian = !args.found("no-lagrangian");
+    const bool noPointValues = args.found("no-point-data");
 
     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)
@@ -320,17 +357,18 @@ 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());
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H
index 3d79fa6b467..19e1922a16d 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/readFields.H
@@ -64,6 +64,21 @@ tmp<GeoField> getField
 }
 
 
+//- Get the named field from the objects, or return nullptr.
+template<class GeoField>
+tmp<GeoField> getField
+(
+    const typename GeoField::Mesh& mesh,
+    const IOobjectList& objects,
+    const word& fieldName
+)
+{
+    // Can do something with syncPar on failure ...
+
+    return getField<GeoField>(objects.findObject(fieldName), mesh);
+}
+
+
 //- Get internal field and make it a zero-gradient volume field
 template<class GeoField>
 tmp<GeoField>
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/writePointFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writePointFields.H
new file mode 100644
index 00000000000..823f94b9f63
--- /dev/null
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writePointFields.H
@@ -0,0 +1,149 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 point fields from disk
+    and write with vtk::internalWriter and vtk::patchWriter
+
+SourceFiles
+    writePointFields.H
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ensight_writePointFields_H
+#define ensight_writePointFields_H
+
+#include "readFields.H"
+#include "fvMesh.H"
+// #include "foamVtkInternalWriter.H"
+// #include "foamVtkPatchWriter.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+template<class Type>
+bool writePointField
+(
+    ensightCase& ensCase,
+    const ensightMesh& ensMesh,
+    const tmp<GeometricField<Type, pointPatchField, pointMesh>>& tfield
+)
+{
+    if (!tfield.valid())
+    {
+        return false;
+    }
+
+    const auto& field = tfield();
+
+    // PointData = true
+    autoPtr<ensightFile> os = ensCase.newData<Type>(field.name(), true);
+
+    bool wrote = ensightOutput::Detail::writePointField<Type>
+    (
+        os.ref(),
+        field,
+        ensMesh
+    );
+
+    tfield.clear();
+    return wrote;
+}
+
+
+template<class Type>
+label writePointFields
+(
+    ensightCase& ensCase,
+    const ensightMesh& ensMesh,
+    const IOobjectList& objects
+)
+{
+    typedef GeometricField<Type, pointPatchField, pointMesh> GeoField;
+
+    const pointMesh& ptMesh = pointMesh::New(ensMesh.mesh());
+
+    label count = 0;
+
+    for (const word& fieldName : objects.sortedNames<GeoField>())
+    {
+        if
+        (
+            writePointField<Type>
+            (
+                ensCase,
+                ensMesh,
+                getField<GeoField>(ptMesh, objects, fieldName)
+            )
+        )
+        {
+            Info<< ' ' << fieldName;
+            ++count;
+        }
+    }
+
+    return count;
+}
+
+
+label writeAllPointFields
+(
+    ensightCase& ensCase,
+    const ensightMesh& ensMesh,
+    const IOobjectList& objects
+)
+{
+    #undef  foamToEnsight_WRITE_FIELD
+    #define foamToEnsight_WRITE_FIELD(PrimitiveType)    \
+        writePointFields<PrimitiveType>                 \
+        (                                               \
+            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);
+
+    #undef foamToEnsight_WRITE_FIELD
+    return count;
+}
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H
index a5da0ea9da7..9678f0d3eab 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsight/writeVolFields.H
@@ -67,9 +67,9 @@ bool writeVolField
 
     bool wrote = ensightOutput::writeVolField<Type>
     (
+        os.ref(),
         field,
         ensMesh,
-        os.ref(),
         nodeValues
     );
 
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
index 9903495d42d..b5a19124f1b 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/foamToEnsightParts.C
@@ -260,7 +260,7 @@ int main(int argc, char *argv[])
 
         info
             << "// Summary of ensight parts" << nl << nl;
-        ensParts.writeSummary(info);
+        ensParts.writeDict(info);
     }
 
     #include "checkMeshMoving.H"
diff --git a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeVolFields.H b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeVolFields.H
index 166408fed45..e886e16a5fb 100644
--- a/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeVolFields.H
+++ b/applications/utilities/postProcessing/dataConversion/foamToEnsightParts/writeVolFields.H
@@ -66,9 +66,10 @@ bool writeVolField
     // Currently serial only
     bool wrote = ensightOutput::Serial::writeVolField<Type>
     (
+        os.ref(),
         field,
-        ensParts,
-        os.ref()
+        ensParts
+        // serial
     );
 
     tfield.clear();
diff --git a/src/conversion/ensight/mesh/ensightMesh.C b/src/conversion/ensight/mesh/ensightMesh.C
index 0b9dd7a4c2f..aee2f36934b 100644
--- a/src/conversion/ensight/mesh/ensightMesh.C
+++ b/src/conversion/ensight/mesh/ensightMesh.C
@@ -6,7 +6,7 @@
      \\/     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.
@@ -33,21 +33,114 @@ License
 #include "emptyPolyPatch.H"
 #include "processorPolyPatch.H"
 #include "mapDistribute.H"
-#include "stringListOps.H"
 
 #include "ensightFile.H"
 #include "ensightGeoFile.H"
-#include "demandDrivenData.H"
+#include "ensightOutput.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;
+}
+
+} // End namespace Foam
+
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 void Foam::ensightMesh::clear()
 {
-    meshCells_.clear();
-    boundaryPatchFaces_.clear();
-    faceZoneFaces_.clear();
-    patchLookup_.clear();
-    globalPointsPtr_.clear();
+    cellZoneParts_.clear();
+    faceZoneParts_.clear();
+    boundaryParts_.clear();
+}
+
+
+void Foam::ensightMesh::renumber()
+{
+    label partNo = 0;
+
+    for (const label zoneId : cellZoneParts_.sortedToc())
+    {
+        cellZoneParts_[zoneId].index() = partNo++;
+    }
+
+    for (const label patchId : boundaryParts_.sortedToc())
+    {
+        boundaryParts_[patchId].index() = partNo++;
+    }
+
+    for (const label zoneId : faceZoneParts_.sortedToc())
+    {
+        faceZoneParts_[zoneId].index() = partNo++;
+    }
 }
 
 
@@ -72,7 +165,7 @@ Foam::ensightMesh::ensightMesh
 
 Foam::ensightMesh::ensightMesh(const fvMesh& mesh)
 :
-    ensightMesh(mesh, IOstream::streamFormat::BINARY)
+    ensightMesh(mesh, ensightMesh::options(IOstream::streamFormat::BINARY))
 {}
 
 
@@ -82,23 +175,8 @@ Foam::ensightMesh::ensightMesh
     const IOstream::streamFormat format
 )
 :
-    options_(new options(format)),
-    mesh_(mesh),
-    needsUpdate_(true)
-{
-    if (!option().lazy())
-    {
-        correct();
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::ensightMesh::~ensightMesh()
-{
-    deleteDemandDrivenData(options_);
-}
+    ensightMesh(mesh, ensightMesh::options(format))
+{}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
@@ -128,25 +206,103 @@ void Foam::ensightMesh::correct()
 {
     clear();
 
-    // Part number
-    label nParts = 0;
+    // Track which cells are in a zone or not
+    bitSet cellSelection;
+
+    // Boundary faces to be excluded from export
+    bitSet excludeFace;
+
 
-    if (useInternalMesh())
+    // All or specified cellZones first
+    const wordRes& czMatcher = option().cellZoneSelection();
+
+    if
+    (
+        option().useCellZones()
+     && (option().useInternalMesh() || !czMatcher.empty())
+    )
     {
-        meshCells_.index() = nParts++;
-        meshCells_.classify(mesh_);
+        const wordList zoneNames(mesh_.cellZones().names());
 
-        // Determine parallel shared points
-        globalPointsPtr_ = mesh_.globalData().mergePoints
+        const labelList zoneIds =
         (
-            pointToGlobal_,
-            uniquePointMap_
+            czMatcher.empty()
+          ? identity(zoneNames.size())     // Use all
+          : czMatcher.matching(zoneNames)  // Use selected names
         );
+
+        for (const label zoneId : zoneIds)
+        {
+            const word& zoneName = zoneNames[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())
+    {
+        // The internal mesh
+
+        if (!cellZoneParts_.empty())
+        {
+            // The 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 (czMatcher.empty())
+        {
+            // The entire mesh, but only if there were no zones.
+            // Skip this is any zones were specified, regardless of their existence
+
+            ensightCells& part = cellZoneParts_(internalZone);
+
+            part.clear();
+            part.identifier() = internalZone;
+            part.rename("internalMesh");
+
+            part.classify(mesh_);
+
+            // Finalize
+            part.reduce();
+
+            // Handled all cells
+            cellSelection.clearStorage();
+        }
     }
-    meshCells_.reduce();
 
 
-    if (useBoundaryMesh())
+    if (option().useBoundaryMesh())
     {
         // Patches are output. Check that they are synced.
         mesh_.boundaryMesh().checkParallelSync(true);
@@ -158,13 +314,14 @@ void Foam::ensightMesh::correct()
             patchNames.resize(mesh_.boundaryMesh().nNonProcessor());
         }
 
-        const wordRes& matcher = option().patchSelection();
-
-        const labelList patchIds =
+        const labelList patchIds
         (
-            matcher.empty()
-          ? identity(patchNames.size())         // Use all
-          : findStrings(matcher, patchNames)    // Use specified names
+            getSelected
+            (
+                patchNames,
+                option().patchSelection(),
+                option().patchExclude()
+            )
         );
 
         for (const label patchId : patchIds)
@@ -174,100 +331,105 @@ void Foam::ensightMesh::correct()
             // Use fvPatch (not polyPatch) to automatically remove empty patches
             const fvPatch& p = mesh_.boundary()[patchId];
 
-            ensightFaces& ensFaces = boundaryPatchFaces_(patchName);
-            ensFaces.clear();
+            ensightFaces& part = boundaryParts_(patchId);
+
+            part.clear();
+            part.identifier() = patchId;
+            part.rename(patchName);
 
             if (p.size())
             {
                 // Local face addressing (offset = 0),
                 // - this is what we'll need later when writing fields
-                ensFaces.classify(p.patch());
+                part.classify(p.patch());
             }
             else
             {
                 // The patch is empty (on this processor)
                 // or the patch is 'empty' (as fvPatch type)
-                ensFaces.clear();
+                part.clear();
             }
 
             // Finalize
-            ensFaces.reduce();
+            part.reduce();
 
-            if (ensFaces.total())
+            if (!part.total())
             {
-                patchLookup_.set(patchId, patchName);
-                ensFaces.index() = nParts++;
-            }
-            else
-            {
-                boundaryPatchFaces_.erase(patchName);
+                boundaryParts_.erase(patchId);
             }
         }
-
-        // 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());
+        const wordRes& fzMatcher = option().faceZoneSelection();
+
+        const wordList zoneNames(mesh_.faceZones().names());
+
+        const labelList zoneIds =
+        (
+            fzMatcher.empty()
+          ? identity(zoneNames.size())      // Use all
+          : fzMatcher.matching(zoneNames)   // Use selected names
+        );
+
 
-        for (const polyPatch& pp : mesh_.boundaryMesh())
+        if (zoneIds.size())
         {
-            const auto* procPatch = isA<processorPolyPatch>(pp);
+            excludeFace.resize(mesh_.nFaces());
 
-            if (isA<emptyPolyPatch>(pp))
-            {
-                excludeFace.set(pp.range());
-            }
-            else if (procPatch && !procPatch->owner())
+            for (const polyPatch& pp : mesh_.boundaryMesh())
             {
-                // Exclude neighbour-side, retain owner-side only
-                excludeFace.set(pp.range());
+                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)
+        for (const label zoneId : zoneIds)
         {
-            const label zoneID = mesh_.faceZones().findZoneID(zoneName);
-            const faceZone& fz = mesh_.faceZones()[zoneID];
+            const word& zoneName = zoneNames[zoneId];
+            const faceZone& zn = mesh_.faceZones()[zoneId];
 
-            ensightFaces& ensFaces = faceZoneFaces_(zoneName);
-            ensFaces.clear();
+            ensightFaces& part = faceZoneParts_(zoneId);
 
-            if (fz.size())
+            part.clear();
+            part.identifier() = zoneId;
+            part.rename(zoneName);
+
+            if (zn.size())
             {
-                ensFaces.classify
+                part.classify
                 (
                     mesh_.faces(),
-                    fz,
-                    fz.flipMap(),
+                    zn,
+                    zn.flipMap(),
                     excludeFace
                 );
             }
 
             // Finalize
-            ensFaces.reduce();
+            part.reduce();
 
-            if (ensFaces.total())
+            if (!part.total())
             {
-                ensFaces.index() = nParts++;
-            }
-            else
-            {
-                faceZoneFaces_.erase(zoneName);
+                faceZoneParts_.erase(zoneId);
             }
         }
     }
 
+    renumber();
+
     needsUpdate_ = false;
 }
 
@@ -275,78 +437,134 @@ void Foam::ensightMesh::correct()
 void Foam::ensightMesh::write(ensightGeoFile& os) const
 {
     //
-    // Write internalMesh
+    // Write cellZones / internalMesh
     //
-    if (useInternalMesh())
+    for (const label zoneId : cellZoneParts_.sortedToc())
     {
-        const label nPoints = globalPoints().size();
+        const ensightCells& part = cellZoneParts_[zoneId];
+
+        // Renumber the points/faces into unique points
+        autoPtr<globalIndex> globalPointsPtr;
+        labelList pointToGlobal;  // local point to unique global index
+        labelList uniqueMeshPointLabels;  // unique global points
+
+        const bool usesAllCells =
+            returnReduce((part.size() == mesh_.nCells()), andOp<bool>());
+
+        if (usesAllCells)
+        {
+            // All cells used, and thus all points
+
+            globalPointsPtr =
+                mesh_.globalData().mergePoints
+                (
+                    pointToGlobal,
+                    uniqueMeshPointLabels
+                );
+        }
+        else
+        {
+            // Map mesh point index to local (compact) point index
+            Map<label> meshPointMap(part.meshPointMap(mesh_));
+
+            labelList meshPoints(meshPointMap.sortedToc());
+
+            globalPointsPtr =
+                mesh_.globalData().mergePoints
+                (
+                    meshPoints,
+                    meshPointMap,
+                    pointToGlobal,
+                    uniqueMeshPointLabels
+                );
 
-        const pointField uniquePoints(mesh_.points(), uniquePointMap_);
+            meshPointMap.clear();
 
-        // writePartHeader(os, 0, "internalMesh");
-        // beginCoordinates(os, nPoints);
-        writeAllPoints
+            // The mergePoints returns pointToGlobal assuming local addressing
+            // (eg, patch localFaces).
+            // Recast as original mesh points to new global points
+
+            labelList oldToNew(mesh_.nPoints(), -1);
+
+            forAll(meshPoints, i)
+            {
+                const label orig = meshPoints[i];
+                const label glob = pointToGlobal[i];
+
+                oldToNew[orig] = glob;
+            }
+
+            pointToGlobal.transfer(oldToNew);
+        }
+
+        ensightOutput::Detail::writeCoordinates
         (
-            meshCells_.index(),
-            "internalMesh",
-            nPoints,
-            uniquePoints,
-            os
+            os,
+            part.index(),
+            part.name(),
+            globalPointsPtr().size(),   // nPoints (global)
+            UIndirectList<point>(mesh_.points(), uniqueMeshPointLabels),
+            Pstream::parRun()           //!< Collective write?
         );
 
-        writeCellConnectivity(meshCells_, pointToGlobal_, os);
+        writeCellConnectivity(os, part, pointToGlobal);
     }
 
 
     //
-    // Write patches - sorted by Id
+    // Write patches - sorted by index
     //
-    for (const label patchId : patchLookup_.sortedToc())
+    for (const label patchId : boundaryParts_.sortedToc())
     {
-        const word& patchName = patchLookup_[patchId];
-        const ensightFaces& ensFaces = boundaryPatchFaces_[patchName];
+        const ensightFaces& part = boundaryParts_[patchId];
 
         const polyPatch& pp = mesh_.boundaryMesh()[patchId];
 
         // Renumber the patch points/faces into unique points
-        labelList pointToGlobal;
-        labelList uniqueMeshPointLabels;
+        labelList pointToGlobal;  // local point to unique global index
+        labelList uniqueMeshPointLabels;  // unique global points
+
         autoPtr<globalIndex> globalPointsPtr =
             mesh_.globalData().mergePoints
             (
                 pp.meshPoints(),
                 pp.meshPointMap(),
-                pointToGlobal, // local point to unique global index
-                uniqueMeshPointLabels // unique global points
+                pointToGlobal,
+                uniqueMeshPointLabels
             );
 
+
+        ensightOutput::Detail::writeCoordinates
+        (
+            os,
+            part.index(),
+            part.name(),
+            globalPointsPtr().size(),  // nPoints (global)
+            UIndirectList<point>(mesh_.points(), uniqueMeshPointLabels),
+            Pstream::parRun() //!< Collective write?
+        );
+
         // Renumber the patch faces,
         // from local patch indexing to unique global index
         faceList patchFaces(pp.localFaces());
-        for (face& f : patchFaces)
-        {
-            inplaceRenumber(pointToGlobal, f);
-        }
+        ListListOps::inplaceRenumber(pointToGlobal, patchFaces);
 
-        writeAllPoints
+        ensightOutput::writeFaceConnectivity
         (
-            ensFaces.index(),
-            patchName,
-            globalPointsPtr().size(),
-            pointField(mesh_.points(), uniqueMeshPointLabels),
-            os
+            os,
+            part,
+            patchFaces,
+            Pstream::parRun()           //!< Collective write?
         );
-
-        writeFaceConnectivity(ensFaces, patchFaces, os);
     }
 
 
     //
-    // Write faceZones, if requested
+    // Write requested faceZones - sorted by index
     //
-    for (const word& zoneName : faceZoneFaces_.sortedToc())
+    for (const label zoneId : faceZoneParts_.sortedToc())
     {
-        const ensightFaces& ensFaces = faceZoneFaces_[zoneName];
+        const ensightFaces& part = faceZoneParts_[zoneId];
 
         // Use the properly sorted faceIds (ensightFaces) and do NOT use the
         // faceZone directly, otherwise the point-maps will not correspond.
@@ -354,27 +572,42 @@ void Foam::ensightMesh::write(ensightGeoFile& os) const
 
         indirectPrimitivePatch pp
         (
-            IndirectList<face>(mesh_.faces(), ensFaces.faceIds()),
+            IndirectList<face>(mesh_.faces(), part.faceIds()),
             mesh_.points()
         );
 
-        // Renumber the points/faces into unique points
-        labelList pointToGlobal;
-        labelList uniqueMeshPointLabels;
+        // Renumber the patch points/faces into unique points
+        labelList pointToGlobal;  // local point to unique global index
+        labelList uniqueMeshPointLabels;  // unique global points
+
         autoPtr<globalIndex> globalPointsPtr =
             mesh_.globalData().mergePoints
             (
                 pp.meshPoints(),
                 pp.meshPointMap(),
-                pointToGlobal, // local point to unique global index
-                uniqueMeshPointLabels // unique global points
+                pointToGlobal,
+                uniqueMeshPointLabels
             );
 
+        ensightOutput::Detail::writeCoordinates
+        (
+            os,
+            part.index(),
+            part.name(),
+            globalPointsPtr().size(),  // nPoints (global)
+            UIndirectList<point>(mesh_.points(), uniqueMeshPointLabels),
+            Pstream::parRun() //!< Collective write?
+        );
+
         // 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());
+        ListListOps::inplaceRenumber(pointToGlobal, patchFaces);
+
+        // Also a good place to perform face flipping
+        const boolList& flip = part.flipMap();
+
         forAll(patchFaces, facei)
         {
             face& f = patchFaces[facei];
@@ -383,20 +616,15 @@ void Foam::ensightMesh::write(ensightGeoFile& os) const
             {
                 f.flip();
             }
-
-            inplaceRenumber(pointToGlobal, f);
         }
 
-        writeAllPoints
+        ensightOutput::writeFaceConnectivityPresorted
         (
-            ensFaces.index(),
-            zoneName,
-            globalPointsPtr().size(),
-            pointField(mesh_.points(), uniqueMeshPointLabels),
-            os
+            os,
+            part,
+            patchFaces,
+            Pstream::parRun()           //!< Collective write?
         );
-
-        writeFaceConnectivity(ensFaces, patchFaces, os, true);
     }
 }
 
diff --git a/src/conversion/ensight/mesh/ensightMesh.H b/src/conversion/ensight/mesh/ensightMesh.H
index fe0b4f25dbd..41181811efc 100644
--- a/src/conversion/ensight/mesh/ensightMesh.H
+++ b/src/conversion/ensight/mesh/ensightMesh.H
@@ -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.
@@ -43,22 +43,18 @@ SourceFiles
 #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"
-
+#include <memory>
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class fvMesh;
 class ensightMesh;
 
@@ -70,220 +66,68 @@ class ensightMesh
 {
 public:
 
-    // Forward declarations
+    // Forward Declarations
     class options;
 
+    //- The zone-id for internal mesh or unzoned cells.
+    static const label internalZone;
+
 
 private:
 
-    // Private data
+    // Private Data
 
         //- Writer options
-        const options* options_;
+        const std::unique_ptr<options> options_;
 
         //- Reference to the OpenFOAM mesh
         const fvMesh& mesh_;
 
-        //- The volume cells (internalMesh)
-        ensightCells meshCells_;
-
-        //- Face elements per patch
-        HashTable<ensightFaces> boundaryPatchFaces_;
+        //- 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
-        HashTable<ensightFaces> faceZoneFaces_;
+        //- Face elements per faceZone, lookup by zone index.
+        Map<ensightFaces> faceZoneParts_;
 
-        //- The list of patches to be output
-        Map<word> patchLookup_;
+        //- Face elements per selected patch, lookup by patch index
+        Map<ensightFaces> boundaryParts_;
 
         //- 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
+        //- Clear all 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
-        );
+        //- Enforce consistent index/part numbering
+        void renumber();
 
         //- Write the poly connectivity
         void writePolysConnectivity
         (
+            ensightGeoFile& os,
             const labelUList& polys,
-            const labelList& pointToGlobal,
-            ensightGeoFile&
+            const labelList& pointToGlobal
         ) const;
 
-        //- Write the regular cell connectivity for all types
+        //- Write cell connectivity for specified type
         void writeCellConnectivity
         (
-            const ensightCells& ensCells,
-            const labelList& pointToGlobal,
-            ensightGeoFile& os
+            ensightGeoFile& os,
+            const ensightCells::elemType etype,
+            const ensightCells& part,
+            const labelList& pointToGlobal
         ) const;
 
-        //- Write the regular cell connectivity for specified type
+        //- Write the regular cell connectivity for all types
         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 ensightCells& part,
+            const labelList& pointToGlobal
         ) const;
 
 
@@ -308,10 +152,6 @@ public:
         ensightMesh(const fvMesh& mesh, const IOstream::streamFormat format);
 
 
-    //- Destructor
-    ~ensightMesh();
-
-
     // Member Functions
 
     // Access
@@ -325,45 +165,18 @@ public:
         //- 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;
-
+        //- 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;
 
-    // Parallel point merging
+        //- Face elements per faceZone, lookup by zone index.
+        //  Process in sorted order.
+        inline const Map<ensightFaces>& faceZoneParts() const;
 
-        //- 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_;
-        }
+        //- Face elements per selected patch, lookup by patch index
+        //  Process in sorted order.
+        inline const Map<ensightFaces>& boundaryParts() const;
 
 
     // Other
@@ -387,37 +200,47 @@ public:
 
         //- Write to file
         void write(ensightGeoFile& os) const;
-
 };
 
 
 //- Configuration options for the ensightMesh
 class ensightMesh::options
 {
-    //- Ascii/Binary file output
-    IOstream::streamFormat format_;
+    // Private Data
+
+        //- Ascii/Binary file output
+        IOstream::streamFormat format_;
+
+        //- Create in 'expired' mode
+        bool lazy_;
 
-    //- Create in 'expired' mode
-    bool lazy_;
+        //- Use the internal mesh
+        bool internal_;
 
-    //- Use the internal mesh
-    bool internal_;
+        //- Use the boundary mesh
+        bool boundary_;
 
-    //- Use the boundary mesh
-    bool boundary_;
+        //- Handle cellZones (if internal_ is true)
+        bool cellZones_;
 
-    //- Output of selected patches only
-    wordRes patchPatterns_;
+        //- Selected patches only
+        wordRes patchInclude_;
 
-    //- Output of selected faceZones
-    wordRes faceZonePatterns_;
+        //- Deselected patches
+        wordRes patchExclude_;
+
+        //- Selected cellZones
+        wordRes cellZoneInclude_;
+
+        //- Selected faceZones
+        wordRes faceZoneInclude_;
 
 
 public:
 
     // Constructors
 
-        //- Construct for binary output
+        //- Default construct, use binary output
         options();
 
         //- Construct for specified format
@@ -443,19 +266,28 @@ public:
         //- 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.
+        //- Lazy creation - ensightMesh starts as needsUpdate
         void lazy(bool beLazy);
 
         //- Alter the useBoundaryMesh state
@@ -464,29 +296,49 @@ public:
         //- 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);
+
+
+    // Edit
+
+        //- Report values
+        void print(Ostream& os) const;
+
 
     // Housekeeping
 
         //- Older name for useBoundaryMesh()
-        //  \deprecated OCT-2018
+        //  \deprecated(2018-10)
         bool usePatches() const { return useBoundaryMesh(); }
 
         //- Older name for useBoundaryMesh()
-        //  \deprecated OCT-2018
+        //  \deprecated(2018-10)
         void noPatches(bool off) { useBoundaryMesh(!off); }
-
 };
 
 
diff --git a/src/conversion/ensight/mesh/ensightMeshI.H b/src/conversion/ensight/mesh/ensightMeshI.H
index 8691435d298..ebd2fad67c1 100644
--- a/src/conversion/ensight/mesh/ensightMeshI.H
+++ b/src/conversion/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.
@@ -45,41 +45,24 @@ inline Foam::IOstream::streamFormat Foam::ensightMesh::format() const
 }
 
 
-inline bool Foam::ensightMesh::useInternalMesh() const
+inline const Foam::Map<Foam::ensightCells>&
+Foam::ensightMesh::cellZoneParts() const
 {
-    return options_->useInternalMesh();
+    return cellZoneParts_;
 }
 
 
-inline bool Foam::ensightMesh::useBoundaryMesh() const
+inline const Foam::Map<Foam::ensightFaces>&
+Foam::ensightMesh::faceZoneParts() const
 {
-    return options_->useBoundaryMesh();
+    return faceZoneParts_;
 }
 
 
-inline const Foam::ensightCells& Foam::ensightMesh::meshCells() const
+inline const Foam::Map<Foam::ensightFaces>&
+Foam::ensightMesh::boundaryParts() const
 {
-    return meshCells_;
-}
-
-
-inline const Foam::Map<Foam::word>& Foam::ensightMesh::patches() const
-{
-    return patchLookup_;
-}
-
-
-inline const Foam::HashTable<Foam::ensightFaces>&
-Foam::ensightMesh::boundaryPatchFaces() const
-{
-    return boundaryPatchFaces_;
-}
-
-
-inline const Foam::HashTable<Foam::ensightFaces>&
-Foam::ensightMesh::faceZoneFaces() const
-{
-    return faceZoneFaces_;
+    return boundaryParts_;
 }
 
 
diff --git a/src/conversion/ensight/mesh/ensightMeshIO.C b/src/conversion/ensight/mesh/ensightMeshIO.C
index 4e3bc534041..4afb1a2de03 100644
--- a/src/conversion/ensight/mesh/ensightMeshIO.C
+++ b/src/conversion/ensight/mesh/ensightMeshIO.C
@@ -5,8 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2016-2018 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,588 +26,182 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "ensightMesh.H"
+#include "ensightOutput.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"
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-// * * * * * * * * * * * * * 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
+void Foam::ensightMesh::writePolysConnectivity
 (
-    const cellShapeList& shapes,
+    ensightGeoFile& os,
     const labelUList& addr,
-    const labelUList& pointToGlobal
-)
-{
-    cellShapeList list(shapes, addr);
-
-    renumberShapes(list, pointToGlobal);
-
-    return list;
-}
-
-
-void Foam::ensightMesh::writeFaceList
-(
-    const faceList& faceLst,
-    ensightGeoFile& os
-)
+    const labelList& pointToGlobal
+) const
 {
-    for (const face& f : faceLst)
+    // Number of faces per polyhedral (1/line in ASCII)
     {
-        for (const label labi : f)
-        {
-            os.write(labi + 1);
-        }
-
-        os.newline();
-    }
-}
-
+        labelList send
+        (
+            ensightOutput::Detail::getPolysNFaces(mesh_, addr)
+        );
 
-void Foam::ensightMesh::writeFaceList
-(
-    const UIndirectList<face>& faceLst,
-    ensightGeoFile& os
-)
-{
-    for (const face& f : faceLst)
-    {
-        for (const label labi : f)
+        if (Pstream::master())
         {
-            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;
-    }
+            // Master
+            os.writeLabels(send);
 
-    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)
+            // Slaves
+            for (int slave=1; slave<Pstream::nProcs(); ++slave)
+            {
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
 
-        // In ASCII, write one cell per line
-        for (const label pointi : cellPoints)
-        {
-            os.write(pointi + 1);
+                labelList recv(fromSlave);
+                os.writeLabels(recv);
+            }
         }
-
-        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)
+        else
         {
-            *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];
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
 
-        for (const label facei : cf)
-        {
-            os.write(faces[facei].size());
-            os.newline();
+            toMaster << send;
         }
     }
-}
 
 
-// * * * * * * * * * * * * * * * 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)
+    // Number of points for each polyhedral face (1/line in ASCII)
     {
-        const labelUList& cf = cellFaces[cellId];
+        labelList send
+        (
+            ensightOutput::Detail::getPolysNPointsPerFace(mesh_, addr)
+        );
 
-        for (const label faceId : cf)
+        if (Pstream::master())
         {
-            const face& f = faces[faceId];  // face points (in global points)
+            // Master
+            os.writeLabels(send);
 
-            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
+            // Slaves
+            for (int slave=1; slave<Pstream::nProcs(); ++slave)
             {
-                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);
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
 
-            writePolysNFaces(addr, cellFaces, os);
+                labelList recv(fromSlave);
+                os.writeLabels(recv);
+            }
         }
-    }
-    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)
+        else
         {
-            IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-            labelList addr(fromSlave);
-            cellList  cellFaces(fromSlave);
-            faceList  meshFaces(fromSlave);
-
-            writePolysNPointsPerFace
+            OPstream toMaster
             (
-                addr,
-                cellFaces,
-                meshFaces,
-                os
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
             );
+
+            toMaster << send;
         }
     }
-    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
+        ensightOutput::writePolysPoints
         (
+            os,
+            mesh_,
             addr,
-            cellFaces,
-            faces,
-            faceOwner,
-            os
+            pointToGlobal
         );
 
         // Slaves
         for (int slave=1; slave<Pstream::nProcs(); ++slave)
         {
             IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+
+            cellList  cells(fromSlave);
             labelList addr(fromSlave);
-            cellList  cellFaces(fromSlave);
             faceList  faces(fromSlave);
-            labelList faceOwner(fromSlave);
+            labelList owner(fromSlave);
 
-            writePolysPoints
+            ensightOutput::writePolysPoints
             (
+                os,
+                cells,
                 addr,
-                cellFaces,
                 faces,
-                faceOwner,
-                os
+                owner
             );
         }
     }
     else
     {
-        OPstream toMaster(Pstream::commsTypes::scheduled, Pstream::masterNo());
+        // 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
-            << cellFaces
             << faces
-            << faceOwner;
+            << mesh_.faceOwner();
     }
 }
 
 
 void Foam::ensightMesh::writeCellConnectivity
 (
-    const ensightCells::elemType elemType,
-    const ensightCells& ensCells,
-    const labelList& pointToGlobal,
-    ensightGeoFile& os
+    ensightGeoFile& os,
+    const ensightCells::elemType etype,
+    const ensightCells& part,
+    const labelList& pointToGlobal
 ) const
 {
-    const label nTotal = ensCells.total(elemType);
+    const label nTotal = part.total(etype);
 
     if (nTotal)
     {
-        const labelUList& addr = ensCells.cellIds(elemType);
+        const labelUList& addr = part.cellIds(etype);
 
         if (Pstream::master())
         {
-            os.writeKeyword(ensightCells::key(elemType));
+            os.writeKeyword(ensightCells::key(etype));
             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())
+        if (etype == ensightCells::NFACED)
         {
-            os.writeKeyword(ensightFaces::key(elemType));
-            os.write(nTotal);
-            os.newline();
+            writePolysConnectivity(os, addr, pointToGlobal);
+            return;
         }
 
-        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;
-            }
-        }
+        // Primitive shape - get subset and renumber
+        cellShapeList shapes(mesh_.cellShapes(), addr);
 
+        ListListOps::inplaceRenumber(pointToGlobal, shapes);
 
-        // List of points id for each face
         if (Pstream::master())
         {
-            writeFaceList(faces, os);
+            ensightOutput::writeCellShapes(os, shapes);
 
             for (int slave=1; slave<Pstream::nProcs(); ++slave)
             {
                 IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
-                faceList recv(fromSlave);
+                cellShapeList recv(fromSlave);
 
-                writeFaceList(recv, os);
+                ensightOutput::writeCellShapes(os, recv);
             }
         }
         else
@@ -619,174 +212,24 @@ void Foam::ensightMesh::writeFaceConnectivity
                 Pstream::masterNo()
             );
 
-            toMaster
-                << faces;
+            toMaster << shapes;
         }
     }
 }
 
 
-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
+void Foam::ensightMesh::writeCellConnectivity
 (
-    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 ensightCells& part,
+    const labelList& pointToGlobal
 ) 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 (label typei=0; typei < ensightCells::nTypes; ++typei)
     {
-        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
-        {
-            OPstream toMaster
-            (
-                Pstream::commsTypes::scheduled,
-                Pstream::masterNo()
-            );
+        const auto etype = ensightCells::elemType(typei);
 
-            toMaster
-                << uniquePoints.component(cmpt);
-        }
+        writeCellConnectivity(os, etype, part, pointToGlobal);
     }
 }
 
diff --git a/src/conversion/ensight/mesh/ensightMeshOptions.C b/src/conversion/ensight/mesh/ensightMeshOptions.C
index 73ba0bd937e..a5019e6fe2c 100644
--- a/src/conversion/ensight/mesh/ensightMeshOptions.C
+++ b/src/conversion/ensight/mesh/ensightMeshOptions.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.
@@ -26,6 +26,34 @@ License
 \*---------------------------------------------------------------------------*/
 
 #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  * * * * * * * * * * * * * * //
 
@@ -41,8 +69,11 @@ Foam::ensightMesh::options::options(IOstream::streamFormat format)
     lazy_(false),
     internal_(true),
     boundary_(true),
-    patchPatterns_(),
-    faceZonePatterns_()
+    cellZones_(true),
+    patchInclude_(),
+    patchExclude_(),
+    cellZoneInclude_(),
+    faceZoneInclude_()
 {}
 
 
@@ -72,9 +103,15 @@ bool Foam::ensightMesh::options::useBoundaryMesh() const
 }
 
 
+bool Foam::ensightMesh::options::useCellZones() const
+{
+    return cellZones_;
+}
+
+
 bool Foam::ensightMesh::options::useFaceZones() const
 {
-    return faceZonePatterns_.size();
+    return faceZoneInclude_.size();
 }
 
 
@@ -82,8 +119,11 @@ void Foam::ensightMesh::options::reset()
 {
     internal_ = true;
     boundary_ = true;
-    patchPatterns_.clear();
-    faceZonePatterns_.clear();
+    cellZones_ = true;
+    patchInclude_.clear();
+    patchExclude_.clear();
+    faceZoneInclude_.clear();
+    cellZoneInclude_.clear();
 }
 
 
@@ -103,12 +143,30 @@ void Foam::ensightMesh::options::useBoundaryMesh(bool on)
 {
     boundary_ = on;
 
-    if (!boundary_ && patchPatterns_.size())
+    if (!boundary_)
     {
-        patchPatterns_.clear();
+        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 boundary and removing old patch selection"
+            << "Deactivating cellZones, removed old zone selection"
             << endl;
     }
 }
@@ -119,14 +177,14 @@ void Foam::ensightMesh::options::patchSelection
     const UList<wordRe>& patterns
 )
 {
-    patchPatterns_ = wordRes(patterns);
+    patchInclude_ = wordRes(patterns);
 
-    if (!boundary_ && patchPatterns_.size())
+    if (!boundary_ && patchInclude_.size())
     {
-        patchPatterns_.clear();
+        patchInclude_.clear();
 
         WarningInFunction
-            << "Ignoring patch selection, boundary is not active"
+            << "Ignoring patch selection, boundary is disabled"
             << endl;
     }
 }
@@ -137,25 +195,43 @@ void Foam::ensightMesh::options::patchSelection
     List<wordRe>&& patterns
 )
 {
-    patchPatterns_ = wordRes(std::move(patterns));
+    patchInclude_ = wordRes(std::move(patterns));
 
-    if (!boundary_ && patchPatterns_.size())
+    if (!boundary_ && patchInclude_.size())
     {
-        patchPatterns_.clear();
+        patchInclude_.clear();
 
         WarningInFunction
-            << "Ignoring patch selection, boundary is not active"
+            << "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
 )
 {
-    faceZonePatterns_ = wordRes(patterns);
+    faceZoneInclude_ = wordRes(patterns);
 }
 
 
@@ -164,19 +240,113 @@ void Foam::ensightMesh::options::faceZoneSelection
     List<wordRe>&& patterns
 )
 {
-    faceZonePatterns_ = wordRes(std::move(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 patchPatterns_;
+    return patchInclude_;
+}
+
+
+const Foam::wordRes& Foam::ensightMesh::options::patchExclude() const
+{
+    return patchExclude_;
 }
 
 
 const Foam::wordRes& Foam::ensightMesh::options::faceZoneSelection() const
 {
-    return faceZonePatterns_;
+    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/conversion/ensight/output/ensightOutputVolField.H b/src/conversion/ensight/output/ensightOutputVolField.H
index 21a570b4954..189d2162fef 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.
@@ -46,7 +46,7 @@ Description
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class ensightMesh;
 
 namespace ensightOutput
@@ -63,18 +63,18 @@ namespace Detail
 template<class Type>
 bool writeVolField
 (
+    ensightFile& os,
     const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
+    const ensightMesh& ensMesh
 );
 
 //- Write point field component-wise
 template<class Type>
 bool writePointField
 (
+    ensightFile& os,
     const GeometricField<Type, pointPatchField, pointMesh>& pf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
+    const ensightMesh& ensMesh
 );
 
 } // End namespace Detail
@@ -88,9 +88,9 @@ bool writePointField
 template<class Type>
 bool writeVolField
 (
-    const GeometricField<Type, fvPatchField, volMesh>&,
-    const ensightMesh& ensMesh,
     ensightFile& os,
+    const GeometricField<Type, fvPatchField, volMesh>& vf,
+    const ensightMesh& ensMesh,
     const bool nodeValues = false
 );
 
@@ -102,33 +102,13 @@ bool writeVolField
 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
-);
-
-
-//- Write volume field component-wise for specified cells
-template<class Type>
-bool writeVolField
-(
-    const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightPartCells& part,
-    ensightFile& os
-);
-
-
 //- Write volume field component-wise
 template<class Type>
 bool writeVolField
 (
+    ensightFile& os,
     const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightParts& list,
-    ensightFile& os
+    const ensightParts& list
 );
 
 } // End namespace Serial
diff --git a/src/conversion/ensight/output/ensightOutputVolFieldTemplates.C b/src/conversion/ensight/output/ensightOutputVolFieldTemplates.C
index de1238cf54f..ec227df1211 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.
@@ -31,9 +30,9 @@ License
 
 #include "fvMesh.H"
 #include "globalIndex.H"
+#include "linear.H"
 #include "volPointInterpolation.H"
 #include "interpolation.H"
-#include "linear.H"
 #include "processorFvPatch.H"
 #include "uindirectPrimitivePatch.H"
 
@@ -42,62 +41,59 @@ License
 template<class Type>
 bool Foam::ensightOutput::Detail::writeVolField
 (
+    ensightFile& os,
     const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
+    const ensightMesh& ensMesh
 )
 {
     constexpr bool parallel = true;
 
     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 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())
     {
-        Detail::writeCellField(vf, meshCells, os, parallel);
+        const ensightCells& part = cellZoneParts[zoneId];
+
+        Detail::writeField(os, vf, part, 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 ensightFaces& part = boundaryParts[patchId];
 
-        writeFaceField
+        writeField
         (
+            os,
             vf.boundaryField()[patchId],
             part,
-            os,
-            parallel
+            parallel,
+            true // localField
         );
     }
 
-
     //
-    // write faceZones, if requested
-    // use sortedToc for extra safety
+    // Write requested faceZones - sorted by index
     //
-    const wordList zoneNames = zoneFaces.sortedToc();
-    if (!zoneNames.empty())
+    if (!faceZoneParts.empty())
     {
-        // Interpolates cell values to faces - needed only when exporting
-        // faceZones...
+        // Interpolates cell values to faces
+        // - only needed when exporting faceZones...
         GeometricField<Type, fvsPatchField, surfaceMesh> sf
         (
             Foam::linearInterpolate(vf)
         );
 
-        // flat boundary field
+        // Flat boundary field
         // similar to volPointInterpolation::flatBoundaryField()
 
         Field<Type> flat(mesh.nBoundaryFaces(), Zero);
@@ -131,30 +127,33 @@ bool Foam::ensightOutput::Detail::writeVolField
             }
         }
 
-        for (const word& zoneName : zoneNames)
+        for (const label zoneId : faceZoneParts.sortedToc())
         {
-            const ensightFaces& part = zoneFaces[zoneName];
-
-            // Field (local size)
-            Field<Type> values(part.size());
+            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
-            forAll(part, i)
+
+            // Field (local size)
+            Field<Type> values(part.size());
+            auto valIter = values.begin();
+
+            for (const label faceId : part.faceIds())
             {
-                const label faceId = part[i];
-                values[i] =
+                *valIter =
                 (
                     mesh.isInternalFace(faceId)
                   ? sf[faceId]
                   : flat[faceId - mesh.nInternalFaces()]
                 );
+
+                ++valIter;
             }
 
-            // The field is already copied in the proper order
+            // The field is already in the proper element order
             // - just need its corresponding sub-fields
-            Detail::writeFaceSubField(values, part, os, parallel);
+            Detail::writeFaceSubField(os, values, part, parallel);
         }
     }
 
@@ -165,47 +164,82 @@ bool Foam::ensightOutput::Detail::writeVolField
 template<class Type>
 bool Foam::ensightOutput::Detail::writePointField
 (
+    ensightFile& os,
     const GeometricField<Type, pointPatchField, pointMesh>& pf,
-    const ensightMesh& ensMesh,
-    ensightFile& os
+    const ensightMesh& ensMesh
 )
 {
+    const char* coordKeyword = "coordinates";
+
     constexpr bool parallel = true;
 
     const fvMesh& mesh = ensMesh.mesh();
-    const Map<word>& patchLookup  = ensMesh.patches();
 
-    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];
+
+        const bool usesAllCells =
+            returnReduce((part.size() == mesh.nCells()), andOp<bool>());
+
+        // Renumber the points/faces into unique points
+        autoPtr<globalIndex> globalPointsPtr;
+        labelList pointToGlobal;  // local point to unique global index
+        labelList uniqueMeshPointLabels;  // unique global points
+
+        if (usesAllCells)
+        {
+            // All cells used, and thus all points
+
+            mesh.globalData().mergePoints
+            (
+                pointToGlobal,
+                uniqueMeshPointLabels
+            );
+        }
+        else
+        {
+            // Map mesh point index to local (compact) point index
+            Map<label> meshPointMap(part.meshPointMap(mesh));
+
+            globalPointsPtr =
+                mesh.globalData().mergePoints
+                (
+                    meshPointMap.sortedToc(),
+                    meshPointMap,
+                    pointToGlobal,
+                    uniqueMeshPointLabels
+                );
+        }
+
         if (Pstream::master())
         {
-            os.beginPart(0); // 0 = internalMesh
+            os.beginPart(part.index());
         }
 
         Detail::writeFieldComponents
         (
-            "coordinates",
-            Field<Type>(pf.internalField(), ensMesh.uniquePointMap()),
             os,
+            coordKeyword,
+            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 ensightFaces& part = boundaryParts[patchId];
 
         const fvPatch& p = mesh.boundary()[patchId];
 
@@ -228,20 +262,19 @@ bool Foam::ensightOutput::Detail::writePointField
 
         Detail::writeFieldComponents
         (
-            "coordinates",
-            Field<Type>(pf.internalField(), uniqueMeshPointLabels),
             os,
+            coordKeyword,
+            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];
+        const ensightFaces& part = faceZoneParts[zoneId];
 
         uindirectPrimitivePatch p
         (
@@ -272,9 +305,9 @@ bool Foam::ensightOutput::Detail::writePointField
 
         Detail::writeFieldComponents
         (
-            "coordinates",
-            Field<Type>(pf.internalField(), uniqueMeshPointLabels),
             os,
+            coordKeyword,
+            UIndirectList<Type>(pf.internalField(), uniqueMeshPointLabels),
             parallel
         );
     }
@@ -288,9 +321,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,10 +336,10 @@ bool Foam::ensightOutput::writeVolField
         pfld.ref().checkOut();
         pfld.ref().rename(vf.name());
 
-        return Detail::writePointField<Type>(pfld, ensMesh, os);
+        return Detail::writePointField<Type>(os, pfld, ensMesh);
     }
 
-    return Detail::writeVolField<Type>(vf, ensMesh, os);
+    return Detail::writeVolField<Type>(os, vf, ensMesh);
 }
 
 
@@ -315,73 +348,52 @@ bool Foam::ensightOutput::writeVolField
 template<class Type>
 bool Foam::ensightOutput::Serial::writeVolField
 (
+    ensightFile& os,
     const GeometricField<Type, fvPatchField, volMesh>& vf,
-    const ensightPartFaces& part,
-    ensightFile& os
+    const ensightParts& list
 )
 {
-    constexpr bool parallel = false; // serial
+    // serial only until the geometry is also in parallel
+    constexpr bool parallel = false;
 
-    const label patchi = part.patchIndex();
-
-    if (patchi >= 0 && patchi < vf.boundaryField().size())
+    for (const ensightPart& item : list)
     {
-        return ensightOutput::Detail::writeFaceField
-        (
-            vf.boundaryField()[patchi],
-            part,
-            os,
-            parallel
-        );
-    }
-
-    return false;
-}
+        const ensightCells* cptr = isA<ensightCells>(item);
 
+        if (cptr)
+        {
+            const ensightCells& part = *cptr;
 
-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
-    );
-}
+            ensightOutput::Detail::writeField
+            (
+                os,
+                vf.internalField(),
+                part,
+                parallel
+            );
+            continue;
+        }
 
 
-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);
+        const ensightFaces* fptr = isA<ensightFaces>(item);
 
         if (fptr)
         {
-            Serial::writeVolField(vf, *fptr, os);
-            continue;
-        }
+            const ensightFaces& part = *fptr;
 
-        const ensightPartCells* cptr = isA<ensightPartCells>(part);
+            const label patchi = part.identifier();
 
-        if (cptr)
-        {
-            Serial::writeVolField(vf, *cptr, os);
+            if (patchi >= 0 && patchi < vf.boundaryField().size())
+            {
+                ensightOutput::Detail::writeField
+                (
+                    os,
+                    vf.boundaryField()[patchi],
+                    part,
+                    parallel,
+                    true // localField
+                );
+            }
             continue;
         }
     }
diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 62f54aaba6c..56ce6a6e48b 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -8,11 +8,19 @@ ensight/file/ensightFile.C
 ensight/file/ensightGeoFile.C
 
 ensight/part/ensightCells.C
+ensight/part/ensightCellsAddr.C
 ensight/part/ensightFaces.C
 ensight/part/ensightPart.C
 ensight/part/ensightPartCells.C
+ensight/part/ensightPartCellsAddr.C
+ensight/part/ensightPartCellsIO.C
 ensight/part/ensightPartFaces.C
+ensight/part/ensightPartFacesAddr.C
+ensight/part/ensightPartFacesIO.C
 ensight/part/ensightParts.C
+ensight/part/ensightOutputSurface.C
+
+ensight/output/ensightOutput.C
 
 ensight/read/ensightReadFile.C
 ensight/type/ensightPTraits.C
diff --git a/src/fileFormats/ensight/file/ensightCase.C b/src/fileFormats/ensight/file/ensightCase.C
index 7e6e6be5f18..9c0e3595377 100644
--- a/src/fileFormats/ensight/file/ensightCase.C
+++ b/src/fileFormats/ensight/file/ensightCase.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.
@@ -26,15 +26,10 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "ensightCase.H"
-#include "stringListOps.H"
+#include "ensightGeoFile.H"
 #include "Time.H"
 #include "cloud.H"
 #include "IOmanip.H"
-#include "globalIndex.H"
-
-#include "ensightFile.H"
-#include "ensightGeoFile.H"
-#include "demandDrivenData.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -69,7 +64,7 @@ void Foam::ensightCase::initialize()
             else
             {
                 DetailInfo
-                    <<"Warning: re-using existing directory" << nl
+                    << "Warning: re-using existing directory" << nl
                     << "    " << ensightDir_ << endl;
             }
         }
@@ -78,7 +73,7 @@ void Foam::ensightCase::initialize()
         mkDir(dataDir());
 
         // The case file is always ASCII
-        os_ = new OFstream(ensightDir_/caseName_, IOstream::ASCII);
+        os_.reset(new OFstream(ensightDir_/caseName_, IOstream::ASCII));
 
         // Format options
         os_->setf(ios_base::left);
@@ -126,7 +121,7 @@ Foam::label Foam::ensightCase::checkTimeset(const labelHashSet& lookup) const
 
 void Foam::ensightCase::writeHeader() const
 {
-    if (os_)  // master only
+    if (os_)  // True on master only
     {
         this->rewind();
         *os_
@@ -218,7 +213,7 @@ void Foam::ensightCase::writeTimeset
     const scalar timeCorrection
 ) const
 {
-    // make a copy
+    // Make a copy
     labelHashSet hashed(lookup);
     hashed.erase(-1);
 
@@ -282,9 +277,10 @@ void Foam::ensightCase::noteGeometry(const bool moving) const
 
 void Foam::ensightCase::noteCloud(const word& cloudName) const
 {
+    // Force into existence
     if (!cloudVars_.found(cloudName))
     {
-        cloudVars_.insert(cloudName, HashTable<string>());
+        cloudVars_.emplace(cloudName);
     }
     cloudTimes_.insert(timeIndex_);
 
@@ -388,9 +384,9 @@ Foam::ensightCase::ensightCase
 )
 :
     options_(new options(opts)),
+    os_(nullptr),
     ensightDir_(ensightDir),
     caseName_(caseName + ".case"),
-    os_(nullptr),
     changed_(false),
     timeIndex_(0),
     timeValue_(0),
@@ -398,6 +394,7 @@ Foam::ensightCase::ensightCase
     geomTimes_(),
     cloudTimes_(),
     variables_(),
+    nodeVariables_(),
     cloudVars_()
 {
     initialize();
@@ -412,9 +409,9 @@ Foam::ensightCase::ensightCase
 )
 :
     options_(new options(format)),
+    os_(nullptr),
     ensightDir_(ensightDir),
     caseName_(caseName + ".case"),
-    os_(nullptr),
     changed_(false),
     timeIndex_(0),
     timeValue_(0),
@@ -422,21 +419,13 @@ Foam::ensightCase::ensightCase
     geomTimes_(),
     cloudTimes_(),
     variables_(),
+    nodeVariables_(),
     cloudVars_()
 {
     initialize();
 }
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::ensightCase::~ensightCase()
-{
-    deleteDemandDrivenData(options_);
-    deleteDemandDrivenData(os_);
-}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 void Foam::ensightCase::nextTime(const scalar value)
@@ -580,7 +569,7 @@ void Foam::ensightCase::write() const
     //
     if (variables_.size() || cloudVars_.size())
     {
-        // start of variables
+        // Start of variables
         *os_
             << nl
             << "VARIABLE" << nl;
@@ -598,7 +587,7 @@ void Foam::ensightCase::write() const
             << ensType.c_str()
             <<
             (
-                nodeValues()
+                (nodeVariables_.found(varName) || nodeValues())
               ? " per node:    1  "  // time-set 1
               : " per element: 1  "  // time-set 1
             )
@@ -678,7 +667,7 @@ void Foam::ensightCase::write() const
 Foam::autoPtr<Foam::ensightGeoFile>
 Foam::ensightCase::newGeometry
 (
-    const bool moving
+    bool moving
 ) const
 {
     autoPtr<Foam::ensightGeoFile> output;
@@ -749,8 +738,12 @@ Foam::Ostream& Foam::ensightCase::printInfo(Ostream& os) const
     os  << "Ensight case:" << nl
         << "   path: "   << ensightDir_ << nl
         << "   name: "   << caseName_   << nl
-        << "   format: " << format()    << nl
-        << "   values per " << (nodeValues() ? "node" : "element") << nl;
+        << "   format: " << format()    << nl;
+
+    if (nodeValues())
+    {
+        os << "   values per node" << nl;
+    }
 
     return os;
 }
diff --git a/src/fileFormats/ensight/file/ensightCase.H b/src/fileFormats/ensight/file/ensightCase.H
index 84791279397..d754fbee326 100644
--- a/src/fileFormats/ensight/file/ensightCase.H
+++ b/src/fileFormats/ensight/file/ensightCase.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.
@@ -32,6 +32,9 @@ Description
 
 SourceFiles
     ensightCase.C
+    ensightCaseI.H
+    ensightCaseOptions.C
+    ensightCaseTemplates.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -42,17 +45,18 @@ SourceFiles
 #include "HashSet.H"
 #include "InfoProxy.H"
 #include "Map.H"
+#include "HashSet.H"
 #include "OSspecific.H"
 #include "Pstream.H"
-
 #include "ensightGeoFile.H"
+#include <memory>
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class ensightCase;
 class instant;
 class Time;
@@ -65,7 +69,7 @@ class ensightCase
 {
 public:
 
-    // Forward declarations
+    // Forward Declarations
     class options;
 
     // Public Data
@@ -76,12 +80,16 @@ public:
         //- The name for geometry files
         static const char* geometryName;
 
+
 private:
 
-    // Private data
+    // Private Data
 
         //- Case writing options
-        const options* options_;
+        const std::unique_ptr<options> options_;
+
+        //- Output stream (master only)
+        mutable std::unique_ptr<OFstream> os_;
 
         //- Output path (absolute)
         fileName ensightDir_;
@@ -89,9 +97,6 @@ private:
         //- Case name (with ".case" ending)
         word caseName_;
 
-        //- Output stream (master only)
-        mutable OFstream* os_;
-
         //- Track state changes since last write
         mutable bool changed_;
 
@@ -119,6 +124,9 @@ private:
         //- Fields/Variables with the ensight type
         mutable HashTable<string> variables_;
 
+        //- Remember fields that are to be treated as point data
+        mutable HashSet<string> nodeVariables_;
+
         //- Cloud names and variables
         mutable HashTable<HashTable<string>> cloudVars_;
 
@@ -214,7 +222,7 @@ public:
 
 
     //- Destructor
-    ~ensightCase();
+    ~ensightCase() = default;
 
 
     // Member Functions
@@ -236,7 +244,7 @@ public:
         //- Consistent zero-padded integer value
         inline word padded(const label i) const;
 
-        //- Use values per nodes instead of per element
+        //- Force use of values per node instead of per element
         inline bool nodeValues() const;
 
         //- Write clouds into their own directory instead in "data" directory
@@ -269,8 +277,7 @@ public:
     // Addition of entries to case file
 
         //- Open stream for new geometry file (on master).
-        autoPtr<ensightGeoFile> newGeometry(const bool moving = false) const;
-
+        autoPtr<ensightGeoFile> newGeometry(bool moving = false) const;
 
         //- Open stream for new cloud positions (on master).
         //  Note the use of ensightFile, not ensightGeoFile.
@@ -279,11 +286,14 @@ public:
             const word& cloudName
         ) const;
 
-
         //- Open stream for new data file (on master), with current index.
+        //  Optionally marking as containing POINT_DATA
         template<class Type>
-        autoPtr<ensightFile> newData(const word& varName) const;
-
+        autoPtr<ensightFile> newData
+        (
+            const word& varName,
+            const bool isPointData = false
+        ) const;
 
         //- Open stream for new cloud data file (on master), with current index.
         template<class Type>
@@ -313,28 +323,29 @@ public:
 //- Configuration options for the ensightCase
 class ensightCase::options
 {
-private:
+    // Private Data
+
+        //- Ascii/Binary file output
+        IOstream::streamFormat format_;
 
-    //- Ascii/Binary file output
-    IOstream::streamFormat format_;
+        //- Remove existing directory and sub-directories on creation
+        bool overwrite_;
 
-    //- Width of mask for subdirectories
-    label width_;
+        //- Force use of values per node instead of per element
+        bool nodeValues_;
 
-    //- The '*' mask appropriate for subdirectories
-    word mask_;
+        //- Write clouds into their own directory
+        bool separateCloud_;
 
-    //- The printf format for zero-padded subdirectory numbers
-    string printf_;
+        //- Width of mask for subdirectories
+        label width_;
 
-    //- Remove existing directory and sub-directories on creation
-    bool overwrite_;
+        //- The '*' mask appropriate for subdirectories
+        word mask_;
 
-    //- Write values at nodes
-    bool nodeValues_;
+        //- The printf format for zero-padded subdirectory numbers
+        string printf_;
 
-    //- Write clouds into their own directory
-    bool separateCloud_;
 
 public:
 
@@ -363,9 +374,6 @@ public:
         //- Remove existing directory and sub-directories on creation
         bool overwrite() const;
 
-        //- Use values per nodes instead of per element
-        bool nodeValues() const;
-
         //- Write clouds into their own directory instead in "data" directory
         bool separateCloud() const;
 
@@ -379,12 +387,20 @@ public:
         //- Remove existing directory and sub-directories on creation
         void overwrite(bool);
 
-        //- Use values per nodes instead of per element
-        void nodeValues(bool);
-
         //- Write clouds into their own directory instead in "data" directory
         void separateCloud(bool);
 
+
+    // Housekeeping
+
+        //- Force use of values per node instead of per element
+        bool nodeValues() const;
+
+        //- Force use of values per node instead of per element
+        //  Deprecated(2020-02) - The newData() method with a second parameter
+        //  is more flexible.
+        //  \deprecated(2020-02) - newData() with second parameter
+        void nodeValues(bool);
 };
 
 
diff --git a/src/fileFormats/ensight/file/ensightCaseOptions.C b/src/fileFormats/ensight/file/ensightCaseOptions.C
index f74cc5a7e85..f3a17563851 100644
--- a/src/fileFormats/ensight/file/ensightCaseOptions.C
+++ b/src/fileFormats/ensight/file/ensightCaseOptions.C
@@ -32,14 +32,14 @@ License
 Foam::ensightCase::options::options(IOstream::streamFormat format)
 :
     format_(format),
-    width_(0),
-    mask_(),
-    printf_(),
     overwrite_(false),
     nodeValues_(false),
-    separateCloud_(false)
+    separateCloud_(false),
+    width_(0),
+    mask_(),
+    printf_()
 {
-    width(8); // Ensures that mask and printf-format are properly resized
+    width(8);  // Fill mask and setup printf-format
 }
 
 
diff --git a/src/fileFormats/ensight/file/ensightCaseTemplates.C b/src/fileFormats/ensight/file/ensightCaseTemplates.C
index ae496c95c25..c93332e2785 100644
--- a/src/fileFormats/ensight/file/ensightCaseTemplates.C
+++ b/src/fileFormats/ensight/file/ensightCaseTemplates.C
@@ -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.
@@ -34,7 +34,8 @@ template<class Type>
 Foam::autoPtr<Foam::ensightFile>
 Foam::ensightCase::newData
 (
-    const word& name
+    const word& name,
+    const bool isPointData
 ) const
 {
     autoPtr<ensightFile> output;
@@ -42,9 +43,10 @@ Foam::ensightCase::newData
     if (Pstream::master())
     {
         const ensight::VarName varName(name);
+
         output = createDataFile(varName);
 
-        // description
+        // Description
         output().write
         (
             string
@@ -55,8 +57,15 @@ Foam::ensightCase::newData
         );
         output().newline();
 
-        // note field variable for later use
+        // Remember the field variable for later use
         noteVariable(varName, ensightPTraits<Type>::typeName);
+
+
+        // Could warn about existing variables that changed representation
+        if (isPointData)
+        {
+            nodeVariables_.set(varName);
+        }
     }
 
     return output;
@@ -78,7 +87,7 @@ Foam::ensightCase::newCloudData
         const ensight::VarName varName(name);
         output = createCloudFile(cloudName, varName);
 
-        // description
+        // Description
         output().write
         (
             string
@@ -89,7 +98,7 @@ Foam::ensightCase::newCloudData
         );
         output().newline();
 
-        // note cloud variable for later use
+        // Remember the cloud variable for later use
         noteCloud(cloudName, varName, ensightPTraits<Type>::typeName);
     }
 
diff --git a/src/fileFormats/ensight/output/ensightOutput.C b/src/fileFormats/ensight/output/ensightOutput.C
new file mode 100644
index 00000000000..50ab425a8c2
--- /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];  // face points (in global points)
+
+            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])
+        {
+            // The face points (in global points)
+            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..35d3893bbe9 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,18 +39,173 @@ 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
 {
 
+//- Write list of faces
+void writeFaceList
+(
+    ensightGeoFile& os,
+    const UList<face>& faces
+);
+
+//- Write list of faces
+void writeFaceList
+(
+    ensightGeoFile& os,
+    const UIndirectList<face>& faces
+);
+
+//- Write cell connectivity via cell shapes
+void writeCellShapes
+(
+    ensightGeoFile& os,
+    const UList<cellShape>& shapes
+);
+
+
+//- Write the point ids per poly element.
+//  Points have been already renumbered
+void writePolysPoints
+(
+    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,
+    const faceUList& faces,
+    bool parallel               //!< Collective write?
+);
+
+
+//- 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
+(
+    ensightGeoFile& os,
+    const ensightFaces& part,
+    const faceUList& faces,
+    bool parallel               //!< Collective write?
+);
+
+
 /*---------------------------------------------------------------------------*\
                         Namespace ensightOutput::Detail
 \*---------------------------------------------------------------------------*/
@@ -60,53 +216,91 @@ namespace ensightOutput
 namespace Detail
 {
 
+//- 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,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
+    bool parallel               //!< Collective write?
 );
 
 
+//- Write a field of cell values as an indirect list,
+//- using the cell ids from ensightCells
+template<class Type>
+bool writeField
+(
+    ensightFile& os,
+    const Field<Type>& fld,
+    const ensightCells& part,
+    bool parallel,              //!< Collective write?
+    const bool localField=false //!< Use localIds instead?
+);
+
 //- Write a field of faces values as an indirect list,
 //- using the face ids from ensightFaces
 template<class Type>
-bool writeFaceField
+bool writeField
 (
+    ensightFile& os,
     const Field<Type>& fld,
     const ensightFaces& part,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
+    bool parallel,              //!< Collective write?
+    const bool localField=false //!< Use localIds instead?
 );
 
 
 //- Write a sub-field of faces values as an indirect list,
-//- using the sublist sizing information from ensightFaces
+//- using the sub-list sizing information from ensightFaces
 template<class Type>
 bool writeFaceSubField
 (
+    ensightFile& os,
     const Field<Type>& fld,
     const ensightFaces& part,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
+    bool parallel               //!< Collective write?
 );
 
 
-
-//- Write a field of cell values as an indirect list,
-//- using the cell ids from ensightCells
-template<class Type>
-bool writeCellField
-(
-    const Field<Type>& fld,
-    const ensightCells& part,
-    ensightFile& os,
-    bool parallel         //!< Collective write?
-);
-
 } // End namespace Detail
 
 
@@ -123,9 +317,9 @@ namespace Serial
 template<class Type>
 bool writePointField
 (
+    ensightFile& os,
     const Field<Type>& fld,
-    const ensightFaces& part,
-    ensightFile& os
+    const ensightFaces& part
 );
 
 } // End namespace Serial
diff --git a/src/fileFormats/ensight/output/ensightOutputTemplates.C b/src/fileFormats/ensight/output/ensightOutputTemplates.C
index 13a9eeaa01a..19b716252b8 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.
@@ -30,74 +30,150 @@ License
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+template<template<typename> class FieldContainer, class Type>
+void Foam::ensightOutput::Detail::copyComponent
+(
+    scalarField& res,
+    const FieldContainer<Type>& input,
+    const direction cmpt
+)
+{
+    res.resize(input.size());
+
+    auto iter = res.begin();
+
+    for (const Type& val : input)
+    {
+        *iter = component(val, cmpt);
+        ++iter;
+    }
+}
+
+
+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();
+
+    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())
+    {
+        // Serial output, or parallel (master)
+
+        os.beginPart(partId, partName);
+        os.beginCoordinates(nPoints);
+
+        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
+        {
+            copyComponent(send, fld, cmpt);
+            os.writeList(send);
+
+            for (int slave=1; slave < nSlaves; ++slave)
+            {
+                IPstream fromSlave(Pstream::commsTypes::scheduled, slave);
+                scalarField recv(fromSlave);
+                os.writeList(recv);
+            }
+        }
+    }
+    else if (nSlaves)
+    {
+        // Parallel (slaves)
+
+        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
+        {
+            copyComponent(send, fld, cmpt);
+
+            OPstream toMaster
+            (
+                Pstream::commsTypes::scheduled,
+                Pstream::masterNo()
+            );
+
+            toMaster << send;
+        }
+    }
+
+    return true;
+}
+
+
 template<template<typename> class FieldContainer, class Type>
 bool Foam::ensightOutput::Detail::writeFieldComponents
 (
+    ensightFile& os,
     const char* key,
     const FieldContainer<Type>& fld,
-    ensightFile& os,
     bool parallel
 )
 {
-    // Preliminary checks
-    // ~~~~~~~~~~~~~~~~~~
-
     parallel = parallel && Pstream::parRun();
 
-    bool hasField = !fld.empty();
+    const label nSlaves = (parallel ? Pstream::nProcs() : 0);
 
-    if (parallel)
+    // Preliminary checks
     {
-        reduce(hasField, orOp<bool>());
+        bool hasField = !fld.empty();
+
+        if (parallel)
+        {
+            reduce(hasField, orOp<bool>());
+        }
+
+        // No field
+        if (!hasField) return false;
     }
 
-    // Nothing to write
-    if (!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())
     {
+        // Serial output, or parallel (master)
+
         os.writeKeyword(key);
 
-        if (!parallel)
+        for (direction d=0; d < pTraits<Type>::nComponents; ++d)
         {
-            // Serial output
-            for (direction d=0; d < pTraits<Type>::nComponents; ++d)
-            {
-                const label cmpt = ensightPTraits<Type>::componentOrder[d];
+            const direction cmpt = ensightPTraits<Type>::componentOrder[d];
 
-                os.writeList(fld.component(cmpt));
-            }
-        }
-        else
-        {
-            // Parallel (master)
+            copyComponent(send, fld, cmpt);
+            os.writeList(send);
 
-            for (direction d=0; d < pTraits<Type>::nComponents; ++d)
+            for (label 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)
         {
-            const label cmpt = ensightPTraits<Type>::componentOrder[d];
+            const direction cmpt = ensightPTraits<Type>::componentOrder[d];
+
+            copyComponent(send, fld, cmpt);
 
             OPstream toMaster
             (
@@ -105,8 +181,7 @@ bool Foam::ensightOutput::Detail::writeFieldComponents
                 Pstream::masterNo()
             );
 
-            toMaster
-                << fld.component(cmpt);
+            toMaster << send;
         }
     }
 
@@ -114,50 +189,62 @@ bool Foam::ensightOutput::Detail::writeFieldComponents
 }
 
 
+
 template<class Type>
-bool Foam::ensightOutput::Detail::writeFaceField
+bool Foam::ensightOutput::Detail::writeField
 (
-    const Field<Type>& fld,
-    const ensightFaces& part,
     ensightFile& os,
-    bool parallel
+    const Field<Type>& fld,
+    const ensightCells& part,
+    bool parallel,
+    const bool localField
 )
 {
-    // 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())
     {
         os.beginPart(part.index());
     }
 
-    for (int typei=0; typei < ensightFaces::nTypes; ++typei)
+
+    labelList localAddr;
+
+    for (int typei=0; typei < ensightCells::nTypes; ++typei)
     {
-        const ensightFaces::elemType what = ensightFaces::elemType(typei);
+        const auto etype = ensightCells::elemType(typei);
 
-        writeFieldComponents
+        const labelUList ids(part.cellIds(etype));
+
+        const labelUList* idsPtr = &ids;
+
+        if (localField && part.start())
+        {
+            localAddr = std::move(part.localIds(etype));
+            idsPtr = &localAddr;
+        }
+
+        Detail::writeFieldComponents
         (
-            ensightFaces::key(what),
-            Field<Type>(fld, part.faceIds(what)),
             os,
+            ensightCells::key(etype),
+            UIndirectList<Type>(fld, *idsPtr),
             parallel
         );
     }
@@ -167,33 +254,32 @@ bool Foam::ensightOutput::Detail::writeFaceField
 
 
 template<class Type>
-bool Foam::ensightOutput::Detail::writeFaceSubField
+bool Foam::ensightOutput::Detail::writeField
 (
+    ensightFile& os,
     const Field<Type>& fld,
     const ensightFaces& part,
-    ensightFile& os,
-    bool parallel
+    bool parallel,
+    const bool localField
 )
 {
-    // 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 +287,29 @@ 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 auto etype = ensightFaces::elemType(typei);
+
+        const labelUList ids(part.faceIds(etype));
 
-        const label size = part.faceIds(what).size();
+        const labelUList* idsPtr = &ids;
+
+        if (localField && part.start())
+        {
+            localAddr = std::move(part.localIds(etype));
+            idsPtr = &localAddr;
+        }
 
         writeFieldComponents
         (
-            ensightFaces::key(what),
-            SubField<Type>(fld, size, start),
             os,
+            ensightFaces::key(etype),
+            UIndirectList<Type>(fld, *idsPtr),
             parallel
         );
-
-        start += size;  // Advance the start for next sub-list
     }
 
     return true;
@@ -225,32 +317,31 @@ bool Foam::ensightOutput::Detail::writeFaceSubField
 
 
 template<class Type>
-bool Foam::ensightOutput::Serial::writePointField
+bool Foam::ensightOutput::Detail::writeFaceSubField
 (
+    ensightFile& os,
     const Field<Type>& fld,
     const ensightFaces& part,
-    ensightFile& os
+    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();
+
+        if (parallel)
+        {
+            reduce(hasField, orOp<bool>());
+        }
 
-    // End preliminary checks
-    // ~~~~~~~~~~~~~~~~~~~~~~
+        // No field
+        if (!hasField) return false;
+    }
 
 
     if (Pstream::master())
@@ -258,46 +349,53 @@ bool Foam::ensightOutput::Serial::writePointField
         os.beginPart(part.index());
     }
 
-    ensightOutput::Detail::writeFieldComponents
-    (
-        "coordinates",
-        fld,
-        os,
-        parallel
-    );
+    labelList localAddr;
+
+    for (int typei=0; typei < ensightFaces::nTypes; ++typei)
+    {
+        const auto etype = ensightFaces::elemType(typei);
+
+        writeFieldComponents
+        (
+            os,
+            ensightFaces::key(etype),
+            SubField<Type>(fld, part.range(etype)),
+            parallel
+        );
+    }
 
     return true;
 }
 
 
 template<class Type>
-bool Foam::ensightOutput::Detail::writeCellField
+bool Foam::ensightOutput::Serial::writePointField
 (
-    const Field<Type>& fld,
-    const ensightCells& part,
     ensightFile& os,
-    bool parallel
+    const Field<Type>& fld,
+    const ensightFaces& part
 )
 {
-    // Preliminary checks
-    // ~~~~~~~~~~~~~~~~~~
-
-    parallel = parallel && Pstream::parRun();
+    const char* coordKeyword = "coordinates";
 
-    bool hasGeom = (parallel ? part.total() : part.size());
-    bool hasField = !fld.empty();
+    // bool parallel = false && Pstream::parRun();
+    constexpr bool parallel = false;
 
-    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,18 +403,13 @@ bool Foam::ensightOutput::Detail::writeCellField
         os.beginPart(part.index());
     }
 
-    for (int typei=0; typei < ensightCells::nTypes; ++typei)
-    {
-        const ensightCells::elemType what = ensightCells::elemType(typei);
-
-        Detail::writeFieldComponents
-        (
-            ensightCells::key(what),
-            Field<Type>(fld, part.cellIds(what)),
-            os,
-            parallel
-        );
-    }
+    ensightOutput::Detail::writeFieldComponents
+    (
+        os,
+        coordKeyword,
+        fld,
+        parallel
+    );
 
     return true;
 }
diff --git a/src/fileFormats/ensight/part/ensightCells.C b/src/fileFormats/ensight/part/ensightCells.C
index 728366cee1a..e7f59bedc6c 100644
--- a/src/fileFormats/ensight/part/ensightCells.C
+++ b/src/fileFormats/ensight/part/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,6 +33,13 @@ License
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
+namespace Foam
+{
+    defineTypeNameAndDebug(ensightCells, 0);
+}
+
+static_assert(Foam::ensightCells::nTypes == 5);
+
 const char* Foam::ensightCells::elemNames[5] =
     { "tetra4", "pyramid5", "penta6", "hexa8", "nfaced" };
 
@@ -41,23 +48,22 @@ const char* Foam::ensightCells::elemNames[5] =
 
 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,39 +71,34 @@ void Foam::ensightCells::resizeAll()
 
 Foam::ensightCells::ensightCells()
 :
-    ensightCells(0)
+    ensightPart(),
+    offsets_(Zero),
+    sizes_(Zero)
 {}
 
 
-Foam::ensightCells::ensightCells(const label partIndex)
+Foam::ensightCells::ensightCells(const string& description)
 :
-    index_(partIndex),
-    address_(),
-    slices_(),
-    sizes_(Zero)
+    ensightCells()
 {
-    resizeAll(); // adjust allocation/sizing
+    rename(description);
 }
 
 
-Foam::ensightCells::ensightCells(const ensightCells& obj)
+Foam::ensightCells::ensightCells(const label partIndex)
 :
-    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();
+    index() = partIndex;
+}
 
-    resizeAll(); // Adjust allocation/sizing
 
-    // Restore total (reduced) sizes
-    this->sizes_ = totSizes;
-}
+Foam::ensightCells::ensightCells(const ensightCells& rhs)
+:
+    ensightPart(rhs),
+    offsets_(rhs.offsets_),
+    sizes_(rhs.sizes_)
+{}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
@@ -105,9 +106,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 +129,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 +155,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 +191,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 +272,29 @@ 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.writeEntryIfDifferent<label>("start", start(), 0);
+    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/ensightCells.H b/src/fileFormats/ensight/part/ensightCells.H
index f2e3f3b9d3d..1bbee8c734a 100644
--- a/src/fileFormats/ensight/part/ensightCells.H
+++ b/src/fileFormats/ensight/part/ensightCells.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,15 +35,16 @@ Description
 #ifndef ensightCells_H
 #define ensightCells_H
 
-#include "labelList.H"
+#include "ensightPart.H"
 #include "FixedList.H"
+#include "Map.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class bitSet;
 class polyMesh;
 
@@ -52,55 +53,54 @@ class polyMesh;
 \*---------------------------------------------------------------------------*/
 
 class ensightCells
+:
+    public ensightPart
 {
 public:
 
-    // Public data
+    // Public Data
 
-        //- Addressable ensight element types
+        //- Supported ensight 'Cell' element types
+        //  Must be zero-based since they are also for internal bookkeeping.
         enum elemType
         {
-            TETRA4,     //!< "tetra4"
+            TETRA4 = 0, //!< "tetra4"
             PYRAMID5,   //!< "pyramid5"
             PENTA6,     //!< "penta6"
             HEXA8,      //!< "hexa8"
             NFACED      //!< "nfaced"
         };
 
-        //- Number of element types (5)
+        //- Number of 'Cell' element types (5)
         static constexpr int nTypes = 5;
 
-        //- The ensight element type names
-        static const char* elemNames[5];
+        //- The ensight 'Cell' element type names
+        static const char* elemNames[nTypes];
 
 
-    // Static Member Functions
+    // Static Functions
 
-        //- Return the ensight element name for the specified type
-        inline static const char* key(const enum elemType);
+        //- The ensight element name for the specified 'Cell' type
+        inline static const char* key(const elemType etype);
 
 
 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_;
+        //- 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, 5> sizes_;
+        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();
 
@@ -108,67 +108,89 @@ private:
         template<class Addressing>
         void classifyImpl(const polyMesh& mesh, const Addressing& cellIds);
 
+
+public:
+
+    //- Declare type-name, virtual type (with debug switch)
+    TypeName("ensightCells");
+
+
+    // Generated Methods
+
         //- No copy assignment
         void operator=(const ensightCells&) = delete;
 
+        //- Move construct
+        ensightCells(ensightCells&&) = default;
+
+        //- Move assignment
+        ensightCells& operator=(ensightCells&&) = default;
+
+        //- Destructor
+        virtual ~ensightCells() = default;
 
-public:
 
     // Constructors
 
-        //- Construct null, with part index 0
+        //- Default construct, 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);
+        //- Default construct, with description/partName
+        explicit ensightCells(const string& description);
 
+        //- Default construct, with specified part index
+        explicit ensightCells(const label partIndex);
 
-    //- Destructor
-    ~ensightCells() = default;
+        //- Copy construct
+        ensightCells(const ensightCells& rhs);
 
 
     // Member Functions
 
     // Access
 
-        //- The index in a list.
-        inline label index() const;
-
-        //- The index in a list, non-const access.
-        inline label& index();
+        //- Processor-local size of all elements.
+        using ensightPart::size;
 
-        //- The processor local size of all elements.
-        inline label size() const;
+        //- Processor-local size of the specified element type.
+        inline label size(const elemType etype) const;
 
-        //- The processor local size of the specified element type.
-        inline label size(const enum elemType) const;
+        //- Processor-local offset/size of element type.
+        inline labelRange range(const elemType etype) const;
 
-        //- The global number of the specified element type.
+        //- The global size of all element types.
         //  This value is only meaningful after a reduce operation.
-        inline label total(const enum elemType) const;
+        label total() const;
 
-        //- The global number of all element types.
+        //- The global size of the specified element type.
         //  This value is only meaningful after a reduce operation.
-        label total() const;
+        inline label total(const elemType etype) const;
 
-        //- The global numbers per element type.
+        //- The global sizes for each element type.
         //  This value is only meaningful after a reduce operation.
-        inline const FixedList<label, 5>& totals() const;
+        inline const FixedList<label, nTypes>& totals() const;
+
+        //- Processor-local sizes per element type.
+        FixedList<label, nTypes> sizes() const;
 
-        //- The processor local sizes per element type.
-        FixedList<label, 5> sizes() const;
+        //- Processor-local cell ids of all elements
+        inline const labelList& cellIds() const;
 
-        //- Processor local starting offset of element type.
-        inline label offset(const enum elemType what) const;
+        //- Processor-local cell ids of the specified element type
+        inline const labelUList cellIds(const elemType etype) const;
 
-        //- Return the (local) cell ids of the specified element type
-        inline const labelUList cellIds(const enum elemType) const;
+        //- Processor-local (cell) ids relative to start()
+        inline labelList localIds() const;
 
-        //- Return the cell ids of all elements
-        inline const labelUList& cellIds() const;
+        //- Processor-local (cell) ids relative to start()
+        inline labelList localIds(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
@@ -184,6 +206,10 @@ public:
         //- 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();
 
@@ -194,11 +220,11 @@ public:
         void sort();
 
 
-    // Member Operators
-
-        //- Return id from linear list of addressing.
-        inline label operator[](const label i) const;
+    // Output
 
+        //- Write information about the object as a dictionary,
+        //- optionally write all element addresses
+        virtual void writeDict(Ostream& os, const bool full=false) const;
 };
 
 
diff --git a/src/fileFormats/ensight/part/ensightCellsAddr.C b/src/fileFormats/ensight/part/ensightCellsAddr.C
new file mode 100644
index 00000000000..1c936a04ffe
--- /dev/null
+++ b/src/fileFormats/ensight/part/ensightCellsAddr.C
@@ -0,0 +1,64 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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"
+
+// * * * * * * * * * * * * * 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;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightCellsI.H b/src/fileFormats/ensight/part/ensightCellsI.H
index dcac7ab0030..d0ebb601b6f 100644
--- a/src/fileFormats/ensight/part/ensightCellsI.H
+++ b/src/fileFormats/ensight/part/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 Foam::labelList Foam::ensightCells::localIds() const
 {
-    return address_;
+    return localAddressing();
 }
 
 
-inline Foam::label Foam::ensightCells::operator[](const label i) const
+inline Foam::labelList Foam::ensightCells::localIds(const elemType etype) const
 {
-    return address_[i];
+    return localAddressing(range(etype));
 }
 
 
diff --git a/src/fileFormats/ensight/part/ensightFaces.C b/src/fileFormats/ensight/part/ensightFaces.C
index 425ebc6f968..009fa9bd523 100644
--- a/src/fileFormats/ensight/part/ensightFaces.C
+++ b/src/fileFormats/ensight/part/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,6 +31,13 @@ License
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
+namespace Foam
+{
+    defineTypeNameAndDebug(ensightFaces, 0);
+}
+
+static_assert(Foam::ensightFaces::nTypes == 3);
+
 const char* Foam::ensightFaces::elemNames[3] =
     { "tria3", "quad4", "nsided" };
 
@@ -40,7 +47,7 @@ const char* Foam::ensightFaces::elemNames[3] =
 namespace
 {
 
-// Simple shape classifier
+// Trivial shape classifier
 static inline Foam::ensightFaces::elemType whatType(const Foam::face& f)
 {
     return
@@ -58,48 +65,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);
-
-    // linear addressing:
-    const label index = offset(what) + sizes_[what]++;
-
-    address_[index] = id;
-    if (flipMap_.size())
-    {
-        flipMap_[index] = flip;
-    }
-}
+    // Assign sub-list offsets, determine overall size
 
+    label len = 0;
 
-void Foam::ensightFaces::resizeAll()
-{
-    // overall required size
-    label n = 0;
-    forAll(sizes_, typei)
-    {
-        n += sizes_[typei];
-    }
-    address_.setSize(n, Zero);
+    auto iter = offsets_.begin();
 
-    // assign corresponding sub-lists
-    n = 0;
-    forAll(sizes_, typei)
+    *iter = 0;
+    for (const label n : sizes_)
     {
-        slices_[typei].setStart(n);
-        slices_[typei].setSize(sizes_[typei]);
+        len += n;
 
-        n += sizes_[typei];
+        *(++iter) = len;
     }
 
-    // normally assume no flipMap
+    // The addressing space
+    addressing().resize(len, Zero);
+
+    // Normally assume no flipMap
     flipMap_.clear();
 }
 
@@ -108,40 +93,26 @@ void Foam::ensightFaces::resizeAll()
 
 Foam::ensightFaces::ensightFaces()
 :
-    ensightFaces(0)
+    ensightPart(),
+    flipMap_(),
+    offsets_(Zero),
+    sizes_(Zero)
 {}
 
 
-Foam::ensightFaces::ensightFaces(const label partIndex)
+Foam::ensightFaces::ensightFaces(const string& description)
 :
-    index_(partIndex),
-    address_(),
-    flipMap_(),
-    slices_(),
-    sizes_(Zero)
+    ensightFaces()
 {
-    resizeAll(); // adjust allocation/sizing
+    rename(description);
 }
 
 
-Foam::ensightFaces::ensightFaces(const ensightFaces& obj)
+Foam::ensightFaces::ensightFaces(const label partIndex)
 :
-    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;
+    index() = partIndex;
 }
 
 
@@ -150,9 +121,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 +144,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 +171,123 @@ 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()));
+
+    classify(SubList<face>(slice, faces));
+
+    // Adjust starting info
+    const label off = slice.start();
+
+    if (off)
+    {
+        for (label& val : addressing())
+        {
+            val += off;
+        }
+    }
+}
+
+
+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 +296,50 @@ void Foam::ensightFaces::classify
 
     if (useFlip)
     {
-        flipMap_.setSize(address_.size(), false);
+        flipMap_.resize(len);
         flipMap_ = false;
     }
 
     // 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.writeEntryIfDifferent<label>("start", start(), 0);
+    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/ensightFaces.H b/src/fileFormats/ensight/part/ensightFaces.H
index 307237279a8..359a79d7af8 100644
--- a/src/fileFormats/ensight/part/ensightFaces.H
+++ b/src/fileFormats/ensight/part/ensightFaces.H
@@ -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.
@@ -27,15 +27,18 @@ Class
     Foam::ensightFaces
 
 Description
-    Sorting/classification of faces (2D) into corresponding ensight types
+    Sorting/classification of faces (2D) into corresponding ensight types.
+
+    Some caution may be required when handling face addressing into fields.
+    If it is a boundaryField, the local face ids should be used.
 
 \*---------------------------------------------------------------------------*/
 
 #ifndef ensightFaces_H
 #define ensightFaces_H
 
+#include "ensightPart.H"
 #include "boolList.H"
-#include "labelList.H"
 #include "faceList.H"
 #include "FixedList.H"
 #include "bitSet.H"
@@ -50,148 +53,164 @@ namespace Foam
 \*---------------------------------------------------------------------------*/
 
 class ensightFaces
+:
+    public ensightPart
 {
 public:
 
     // Public Data
 
-        //- Addressable ensight element types
+        //- Supported ensight 'Face' element types.
+        //  Must be zero-based since they are also for internal bookkeeping.
         enum elemType
         {
-            TRIA3,      //!< "tria3"
+            TRIA3 = 0,  //!< "tria3"
             QUAD4,      //!< "quad4"
             NSIDED      //!< "nsided"
         };
 
-        //- Number of element types (3)
+        //- Number of 'Face' element types (3)
         static constexpr int nTypes = 3;
 
-        //- The ensight element type names
-        static const char* elemNames[3];
+        //- The ensight 'Face' element type names
+        static const char* elemNames[nTypes];
 
 
-    // Static Member Functions
+    // Static Functions
 
-        //- Return the ensight element name for the specified type
-        static inline const char* key(const enum elemType);
+        //- The ensight element name for the specified 'Face' type
+        static inline const char* key(const elemType etype);
 
 
 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_;
+    // Private Data
 
         //- Linear list of face-flips
         boolList flipMap_;
 
-        //- Slices (sub-lists) of the address and flips for each element type.
-        FixedList<labelRange, 3> slices_;
+        //- 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, 3> sizes_;
+        FixedList<label, nTypes> sizes_;
 
 
     // Private Member Functions
 
         //- Low-level internal addition routine
-        inline void add(const face& f, const label id, const bool flip = false);
+        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");
+
+
+    // Generated Methods
+
+        //- Copy construct
+        ensightFaces(const ensightFaces&) = default;
+
         //- No copy assignment
         void operator=(const ensightFaces&) = delete;
 
+        //- Move construct
+        ensightFaces(ensightFaces&&) = default;
 
-public:
+        //- Move assignment
+        ensightFaces& operator=(ensightFaces&&) = default;
 
-    // Constructors
+        //- Destructor
+        virtual ~ensightFaces() = default;
 
-        //- Construct null, with part index 0
-        ensightFaces();
 
-        //- Construct null, with specified part index
-        explicit ensightFaces(const label partIndex);
+    // Constructors
 
-        //- Copy constructor. Needed for lists etc.
-        ensightFaces(const ensightFaces& obj);
+        //- Default construct, with part index 0
+        ensightFaces();
 
+        //- Default construct, with description/partName
+        explicit ensightFaces(const string& description);
 
-    //- Destructor
-    ~ensightFaces() = default;
+        //- Default construct, with specified part index
+        explicit ensightFaces(const label partIndex);
 
 
     // Member Functions
 
     // Access
 
-        //- The index in a list.
-        inline label index() const;
+        //- Processor-local size of all elements.
+        using ensightPart::size;
 
-        //- The index in a list, non-const access.
-        inline label& index();
+        //- Processor-local size of the specified element type.
+        inline label size(const elemType etype) const;
 
-        //- The processor local size of all elements.
-        inline label size() const;
+        //- Processor-local offset/size of element type.
+        inline labelRange range(const elemType etype) const;
 
-        //- The processor local size of the specified element type.
-        inline label size(const enum elemType) const;
-
-        //- The global number of all element types.
+        //- The global size of all element types.
         //  This value is only meaningful after a reduce operation.
         label total() const;
 
-        //- The global number of the specified element type.
+        //- The global size of the specified element type.
         //  This value is only meaningful after a reduce operation.
-        inline label total(const enum elemType) const;
+        inline label total(const elemType etype) const;
 
-        //- The global numbers per element type.
+        //- The global sizes for each 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;
+        inline const FixedList<label, nTypes>& totals() const;
 
-        //- Processor local starting offset of element type.
-        inline label offset(const enum elemType what) const;
+        //- Processor-local sizes per element type.
+        FixedList<label, nTypes> sizes() const;
 
-        //- Return the (local) face ids of the specified element type
-        inline const labelUList faceIds(const enum elemType) const;
+        //- Processor-local face ids of all elements
+        inline const labelList& faceIds() const;
 
-        //- Return the processor local face ids of all elements
-        inline const labelUList& faceIds() const;
+        //- Processor-local face ids of the specified element type
+        inline const labelUList faceIds(const elemType etype) const;
 
-        //- Return the processor local flip-map of all elements
+        //- Processor-local flip-map of all elements
         inline const boolList& flipMap() const;
 
+        //- Processor-local (face) ids relative to start()
+        inline labelList localIds() const;
+
+        //- Processor-local (face) ids relative to start()
+        inline labelList localIds(const elemType etype) const;
+
 
     // Edit
 
-        //- Classify the face types, set element list.
-        void classify(const faceList& faces);
+        //- 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, set element list.
+        //- 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 faceList& faces,
-            const labelUList& addressing,
+            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();
 
@@ -202,10 +221,11 @@ public:
         void sort();
 
 
-    // Member Operators
+    // Output
 
-        //- Return element from linear-list.
-        inline label operator[](const label i) const;
+        //- Write information about the object as a dictionary,
+        //- optionally write all element addresses
+        virtual void writeDict(Ostream& os, const bool full=false) const;
 };
 
 
diff --git a/src/fileFormats/ensight/part/ensightFacesI.H b/src/fileFormats/ensight/part/ensightFacesI.H
index 15799e5610f..90fe6fc3276 100644
--- a/src/fileFormats/ensight/part/ensightFacesI.H
+++ b/src/fileFormats/ensight/part/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,31 +25,27 @@ 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;
 
-inline Foam::label Foam::ensightFaces::index() const
-{
-    return index_;
+    if (flipMap_.size())
+    {
+        flipMap_[index] = flip;
+    }
 }
 
 
-inline Foam::label& Foam::ensightFaces::index()
-{
-    return index_;
-}
-
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline Foam::label Foam::ensightFaces::size() const
+inline const char* Foam::ensightFaces::key(const elemType etype)
 {
-    return address_.size();
+    return elemNames[etype];
 }
 
 
@@ -59,36 +55,34 @@ inline const Foam::FixedList<Foam::label,3>& Foam::ensightFaces::totals() const
 }
 
 
-inline Foam::label Foam::ensightFaces::total(const enum elemType what) const
+inline Foam::label Foam::ensightFaces::total(const elemType etype) const
 {
-    return sizes_[what];
+    return sizes_[etype];
 }
 
 
-inline Foam::label Foam::ensightFaces::size(const enum elemType what) const
+inline Foam::label Foam::ensightFaces::size(const elemType etype) const
 {
-    return slices_[what].size();
+    return (offsets_[etype+1] - offsets_[etype]);
 }
 
 
-inline Foam::label Foam::ensightFaces::offset(const enum elemType what) const
+inline Foam::labelRange Foam::ensightFaces::range(const elemType etype) const
 {
-    return slices_[what].start();
+    return labelRange(offsets_[etype], offsets_[etype+1] - offsets_[etype]);
 }
 
 
-inline const Foam::labelUList Foam::ensightFaces::faceIds
-(
-    const enum elemType what
-) const
+inline const Foam::labelList& Foam::ensightFaces::faceIds() const
 {
-    return address_[slices_[what]];
+    return addressing();
 }
 
 
-inline const Foam::labelUList& Foam::ensightFaces::faceIds() const
+inline const Foam::labelUList
+Foam::ensightFaces::faceIds(const elemType etype) const
 {
-    return address_;
+    return addressing()[range(etype)];
 }
 
 
@@ -98,9 +92,15 @@ inline const Foam::boolList& Foam::ensightFaces::flipMap() const
 }
 
 
-inline Foam::label Foam::ensightFaces::operator[](const label i) const
+inline Foam::labelList Foam::ensightFaces::localIds() const
+{
+    return localAddressing();
+}
+
+
+inline Foam::labelList Foam::ensightFaces::localIds(const elemType etype) const
 {
-    return address_[i];
+    return localAddressing(range(etype));
 }
 
 
diff --git a/src/fileFormats/ensight/part/ensightOutputSurface.C b/src/fileFormats/ensight/part/ensightOutputSurface.C
new file mode 100644
index 00000000000..7627019caed
--- /dev/null
+++ b/src/fileFormats/ensight/part/ensightOutputSurface.C
@@ -0,0 +1,92 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 "ensightOutputSurface.H"
+#include "ensightOutput.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ensightOutputSurface::ensightOutputSurface
+(
+    const pointField& points,
+    const faceList& faces
+)
+:
+    ensightFaces(),
+    points_(points),
+    faces_(faces)
+{
+    // Classify face types
+    classify(faces);
+}
+
+
+Foam::ensightOutputSurface::ensightOutputSurface
+(
+    const string& description,
+    const pointField& points,
+    const faceList& faces
+)
+:
+    ensightOutputSurface(points, faces)
+{
+    rename(description);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::ensightOutputSurface::write(ensightGeoFile& os) const
+{
+    if (!total())
+    {
+        return;
+    }
+
+    // Coordinates
+    ensightOutput::Detail::writeCoordinates
+    (
+        os,
+        index(),
+        name(),
+        points_.size(),
+        points_,
+        false // serial
+    );
+
+    // Faces
+    ensightOutput::writeFaceConnectivity
+    (
+        os,
+        *this,
+        faces_,
+        false  // serial
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightOutputSurface.H b/src/fileFormats/ensight/part/ensightOutputSurface.H
new file mode 100644
index 00000000000..c85f6e4f7e5
--- /dev/null
+++ b/src/fileFormats/ensight/part/ensightOutputSurface.H
@@ -0,0 +1,114 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 continuous
+    points/faces for writing out. The surface is assumed to have been merged
+    prior so the output is serial-only.
+
+SourceFiles
+    ensightOutputSurface.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.
+        ensightOutputSurface
+        (
+            const pointField& points,
+            const faceList& faces
+        );
+
+        //- Construct named from points and faces.
+        ensightOutputSurface
+        (
+            const string& description,
+            const pointField& points,
+            const faceList& faces
+        );
+
+
+    //- Destructor
+    virtual ~ensightOutputSurface() = default;
+
+
+    // Member Functions
+
+        //- Write processor-local (serial) geometry
+        virtual void write(ensightGeoFile& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPart.C b/src/fileFormats/ensight/part/ensightPart.C
index 2092a33f396..1ec55759cc4 100644
--- a/src/fileFormats/ensight/part/ensightPart.C
+++ b/src/fileFormats/ensight/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,64 @@ License
 
 namespace Foam
 {
-    defineTypeNameAndDebug(ensightPart, 0);
+    defineTypeName(ensightPart);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+Foam::labelList Foam::ensightPart::localAddressing() const
+{
+    labelList output(address_);
+
+    if (start_)
+    {
+        for (label& val : output)
+        {
+            val += start_;
+        }
+    }
+
+    return output;
+}
+
+
+Foam::labelList Foam::ensightPart::localAddressing(const labelRange& r) const
+{
+    labelList output(labelSubList(r, address_));
+
+    if (start_)
+    {
+        for (label& val : output)
+        {
+            val += start_;
+        }
+    }
+
+    return output;
 }
 
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::ensightPart::ensightPart(const string& description)
+Foam::ensightPart::ensightPart() noexcept
 :
-    name_(description)
+    index_(0),
+    identifier_(-1),
+    start_(0),
+    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/ensightPart.H b/src/fileFormats/ensight/part/ensightPart.H
index 2b9d3f9d3b9..42de12f010f 100644
--- a/src/fileFormats/ensight/part/ensightPart.H
+++ b/src/fileFormats/ensight/part/ensightPart.H
@@ -6,7 +6,7 @@
      \\/     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.
@@ -28,7 +28,8 @@ Class
     Foam::ensightPart
 
 Description
-    Base class for ensightPartCells and ensightPartFaces
+    Base class for ensightCells, ensightFaces,
+    ensightPartCells, ensightPartFaces.
 
 SourceFiles
     ensightPart.C
@@ -39,11 +40,8 @@ SourceFiles
 #define ensightPart_H
 
 #include "ensightGeoFile.H"
-#include "typeInfo.H"
 #include "labelList.H"
-#include "polyMesh.H"
-#include "Field.H"
-#include "IOstream.H"
+#include "typeInfo.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -58,56 +56,64 @@ class ensightPart
 {
     // Private Data
 
-        //- Part name (or description)
-        string name_;
+        //- 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_;
 
-    // Private Member Functions
+        //- Start face-offset (for patch)
+        label start_;
 
-        //- No copy construct
-        ensightPart(const ensightPart&) = delete;
+        //- Part name (or description)
+        string name_;
 
-        //- No copy assignment
-        void operator=(const ensightPart&) = delete;
+        //- Linear list of element ids (face/cell)
+        //  Sub-sectioning by element type is done by derived classes
+        labelList address_;
 
 
 protected:
 
-    // Protected Classes
+        //- Element addressing
+        const labelList& addressing() const
+        {
+            return address_;
+        }
+
+        //- Element addressing
+        labelList& addressing()
+        {
+            return address_;
+        }
 
-        //- Track the points used by the part and map global to local indices
-        struct localPoints
+        //- Clear element addressing
+        void clear()
         {
-            //- 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)
-            {}
-        };
+            address_.clear();
+        }
+
+        //- Element local addressing (relattive to start)
+        labelList localAddressing() const;
+
+        //- Element local addressing (relattive to start)
+        labelList localAddressing(const labelRange& range) const;
+
 
 public:
 
-    //- Runtime type information
-    TypeName("ensightPart");
+    //- Declare type-name, virtual type (without debug switch)
+    TypeNameNoDebug("ensightPart");
 
 
     // Constructors
 
-        //- Construct with description
+        //- Default construct. Index=0, identifier = -1
+        ensightPart() noexcept;
+
+        //- Default construct, with description/partName
         explicit ensightPart(const string& description);
 
 
@@ -115,25 +121,70 @@ public:
     virtual ~ensightPart() = default;
 
 
-    // Access
+    // 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();
+        }
+
+        //- Start face-offset (for patch)
+        label start() const
+        {
+            return start_;
+        }
 
-        //- Part index (0-based)
-        virtual label index() const = 0;
+        //- Start face-offset (for patch)
+        label& start()
+        {
+            return start_;
+        }
 
-        //- Number of elements in this part
-        virtual label size() const
+        //- Processor-local size of all elements.
+        label size() const
         {
-            return 0;
+            return address_.size();
         }
 
-        //- Return the part name or description
+        //- The part name or description
         const string& name() const
         {
             return name_;
         }
 
         //- Change the part name or description
-        void rename(string value)
+        void rename(const string& value)
+        {
+            name_ = value;
+        }
+
+        //- Change the part name or description
+        void rename(string&& value)
         {
             name_ = std::move(value);
         }
@@ -141,35 +192,31 @@ public:
 
     // 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 information about the object as a dictionary,
+        //- optionally write all element addresses
+        virtual void writeDict(Ostream& os, const bool full=false) const {}
 
         //- Write geometry
-        virtual void write(ensightGeoFile& os) const = 0;
-
-        //- Helper: write geometry with given pointField
-        virtual void write(ensightGeoFile& os, const pointField&) const = 0;
+        virtual void write(ensightGeoFile& os) const {}
 
 
-    // Housekeeping
+    // Member Operators
 
-        //- Deprecated(2019-12) - use rename() method
-        //  \deprecated(2019-12) - use rename() method
-        void FOAM_DEPRECATED_FOR(2019-12, "rename() method")
-        name(string value)
+        //- Processor-local element id from linear-list of addresses.
+        label operator[](const label i) const
         {
-            name_ = std::move(value);
+            return address_[i];
         }
 };
 
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+// Housekeeping
 
-//- IOstream Operator to write geometry
-ensightGeoFile& operator<<(ensightGeoFile& os, const ensightPart& part);
+//- Deprecated(2020-02) - use ensightOutput or member write() methods
+//  \deprecated(2020-02) - use ensightOutput or member write() methods
+void operator<<(ensightGeoFile&, const ensightPart&) = delete;
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/fileFormats/ensight/part/ensightPartCells.C b/src/fileFormats/ensight/part/ensightPartCells.C
index 8a206be91f3..7f5bb5f1d22 100644
--- a/src/fileFormats/ensight/part/ensightPartCells.C
+++ b/src/fileFormats/ensight/part/ensightPartCells.C
@@ -6,7 +6,7 @@
      \\/     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.
@@ -28,110 +28,75 @@ License
 
 #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)
+    ensightCells(),
+    mesh_(mesh),
+    contiguousPoints_(true),
+    nPointsUsed_(0),
+    localPointMap_(nullptr)
 {
+    if (!partName.empty())
+    {
+        rename(partName);
+    }
+
     classify(mesh);
 }
 
 
 Foam::ensightPartCells::ensightPartCells
 (
-    label partIndex,
     const polyMesh& mesh,
     const labelUList& cellIds,
     const string& partName
 )
 :
-    ensightCells(partIndex),
-    ensightPart(partName),
-    mesh_(mesh)
+    ensightCells(),
+    mesh_(mesh),
+    contiguousPoints_(false),
+    nPointsUsed_(0),
+    localPointMap_(nullptr)
 {
+    if (!partName.empty())
+    {
+        rename(partName);
+    }
+
     classify(mesh, cellIds);
 }
 
 
 Foam::ensightPartCells::ensightPartCells
 (
-    label partIndex,
     const polyMesh& mesh,
     const bitSet& selection,
     const string& partName
 )
 :
-    ensightCells(partIndex),
-    ensightPart(partName),
-    mesh_(mesh)
+    ensightCells(),
+    mesh_(mesh),
+    contiguousPoints_(false),
+    nPointsUsed_(0),
+    localPointMap_(nullptr)
 {
+    if (!partName.empty())
+    {
+        rename(partName);
+    }
+
     classify(mesh, selection);
 }
 
 
 Foam::ensightPartCells::ensightPartCells
 (
-    label partIndex,
     const polyMesh& mesh,
     const cellZone& zn,
     const string& partName
@@ -139,12 +104,13 @@ Foam::ensightPartCells::ensightPartCells
 :
     ensightPartCells
     (
-        partIndex,
         mesh,
         static_cast<const labelList&>(zn),
         zn.name()
     )
 {
+    identifier() = zn.index();
+
     if (!partName.empty())
     {
         rename(partName);
@@ -154,190 +120,12 @@ Foam::ensightPartCells::ensightPartCells
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-void Foam::ensightPartCells::writeConnectivity
-(
-    ensightGeoFile& os,
-    const word& key,
-    const labelUList& idList,
-    const labelUList& pointMap
-) const
+void Foam::ensightPartCells::clearOut()
 {
-    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
-    }
+    ensightCells::clearOut();
 
-    os.endBlock();
+    nPointsUsed_ = 0;
+    localPointMap_.reset(nullptr);
 }
 
 
diff --git a/src/fileFormats/ensight/part/ensightPartCells.H b/src/fileFormats/ensight/part/ensightPartCells.H
index b0ddad0d4d7..07ea22f17fd 100644
--- a/src/fileFormats/ensight/part/ensightPartCells.H
+++ b/src/fileFormats/ensight/part/ensightPartCells.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,15 +32,17 @@ Description
 
 SourceFiles
     ensightPartCells.C
+    ensightPartCellsAddr.C
+    ensightPartCellsIO.C
 
 \*---------------------------------------------------------------------------*/
 
 #ifndef ensightPartCells_H
 #define ensightPartCells_H
 
-#include "ensightPart.H"
 #include "ensightCells.H"
-#include "typeInfo.H"
+#include "polyMesh.H"
+#include <memory>
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -53,29 +55,57 @@ namespace Foam
 
 class ensightPartCells
 :
-    public ensightCells,
-    public ensightPart
+    public ensightCells
 {
     // Private Data
 
         //- The referenced mesh
         const polyMesh& mesh_;
 
+        //- Can skip local point renumbering when points are contiguous
+        bool contiguousPoints_;
+
+
+    // Demand-driven data
+
+        //- The number of points used
+        mutable label nPointsUsed_;
+
+        //- Map global to local indices, with -1 for ununsed points
+        mutable std::unique_ptr<labelList> localPointMap_;
+
 
     // Private Member Functions
 
-        //- Track points used
-        localPoints calcLocalPoints() const;
+        //- Calculate the points used
+        void calcLocalPoints() const;
+
+        //- Return the number of unique points (demand-driven)
+        label getPointsUsed() const;
+
+        //- Return the global to local indices (demand-driven)
+        labelList& getPointMap() const;
+
+        //- Element connectivity for polyhedrals
+        void writePolysConnectivity
+        (
+            ensightGeoFile& os,
+            const labelUList& addr,
+            const labelUList& pointMap
+        ) const;
 
         //- Element connectivity
         void writeConnectivity
         (
-            ensightGeoFile&,
-            const word& key,
+            ensightGeoFile& os,
+            const ensightCells::elemType etype,
             const labelUList& idList,
             const labelUList& pointMap
         ) const;
 
+        //- Write processor-local (serial) geometry
+        void writeSerial(ensightGeoFile& os) const;
+
 
         //- No copy construct
         ensightPartCells(const ensightPartCells&) = delete;
@@ -86,17 +116,12 @@ class ensightPartCells
 
 public:
 
-    //- Runtime type information
-    TypeName("ensightCells");
-
-
     // Constructors
 
         //- Construct from entire polyMesh without zones.
         //- Part receives the specified name (default: "cells").
-        ensightPartCells
+        explicit ensightPartCells
         (
-            label partIndex,
             const polyMesh& mesh,
             const string& partName = "cells"
         );
@@ -105,7 +130,6 @@ public:
         //- Part receives the specified name (default: "cells").
         ensightPartCells
         (
-            label partIndex,
             const polyMesh& mesh,
             const labelUList& cellIds,
             const string& partName = "cells"
@@ -115,20 +139,18 @@ public:
         //- 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.
+        //- Construct from cellZone.
         //- Part receives the name of the zone unless otherwise specified.
         ensightPartCells
         (
-            label partIndex,
             const polyMesh& mesh,
             const cellZone& zn,
-            const string& partName = ""
+            const string& partName = string::null
         );
 
 
@@ -138,34 +160,11 @@ public:
 
     // 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();
-        }
+        //- Clear any demand-driven data
+        void clearOut();
 
-
-    // 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
+        //- Write geometry (serial only)
         virtual void write(ensightGeoFile& os) const;
-
-        //- Helper: write geometry with given pointField
-        virtual void write(ensightGeoFile& os, const pointField& points) const;
 };
 
 
diff --git a/src/fileFormats/ensight/part/ensightPartCellsAddr.C b/src/fileFormats/ensight/part/ensightPartCellsAddr.C
new file mode 100644
index 00000000000..6ea54f3ef4e
--- /dev/null
+++ b/src/fileFormats/ensight/part/ensightPartCellsAddr.C
@@ -0,0 +1,113 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 "ensightPartCells.H"
+#include "ListOps.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightPartCells::calcLocalPoints() const
+{
+    if (localPointMap_)
+    {
+        FatalErrorInFunction
+            << "localPointMap_ already allocated"
+            << abort(FatalError);
+    }
+
+    localPointMap_.reset(new labelList());
+
+    labelList& usedPoints = *localPointMap_;
+
+    usedPoints.resize(mesh_.points().size());
+
+    if (contiguousPoints_)
+    {
+        nPointsUsed_ = usedPoints.size();
+
+        ListOps::identity(usedPoints);
+
+        return;
+    }
+
+
+    // Mark up with -1 for unused entries
+    usedPoints = -1;
+    label nPoints = 0;
+
+    // Add all points from cells
+    for (const label celli : this->cellIds())
+    {
+        for (const label facei : mesh_.cells()[celli])
+        {
+            for (const label pointi : mesh_.faces()[facei])
+            {
+                if (usedPoints[pointi] == -1)
+                {
+                    usedPoints[pointi] = nPoints++;
+                }
+            }
+        }
+    }
+
+    nPointsUsed_ = nPoints;
+
+    // Compact point numbering, preserving the original order
+    nPoints = 0;
+    for (label& pointi : usedPoints)
+    {
+        if (pointi > -1)
+        {
+            pointi = nPoints++;
+        }
+    }
+}
+
+
+Foam::label Foam::ensightPartCells::getPointsUsed() const
+{
+    if (!localPointMap_)
+    {
+        calcLocalPoints();
+    }
+
+    return nPointsUsed_;
+}
+
+
+Foam::labelList& Foam::ensightPartCells::getPointMap() const
+{
+    if (!localPointMap_)
+    {
+        calcLocalPoints();
+    }
+
+    return *localPointMap_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPartCellsIO.C b/src/fileFormats/ensight/part/ensightPartCellsIO.C
new file mode 100644
index 00000000000..02764a73f6b
--- /dev/null
+++ b/src/fileFormats/ensight/part/ensightPartCellsIO.C
@@ -0,0 +1,152 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 "ensightPartCells.H"
+#include "ensightOutput.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightPartCells::writePolysConnectivity
+(
+    ensightGeoFile& os,
+    const labelUList& addr,
+    const labelUList& pointMap
+) const
+{
+    // The number of faces per element
+    {
+        labelList send
+        (
+            ensightOutput::Detail::getPolysNFaces(mesh_, addr)
+        );
+
+        os.writeLabels(send);
+    }
+
+    // The number of points per element face
+    {
+        labelList send
+        (
+            ensightOutput::Detail::getPolysNPointsPerFace(mesh_, addr)
+        );
+
+        os.writeLabels(send);
+    }
+
+    ensightOutput::writePolysPoints
+    (
+        os,
+        mesh_,
+        addr,
+        pointMap  // Remap point-ids
+    );
+}
+
+
+void Foam::ensightPartCells::writeConnectivity
+(
+    ensightGeoFile& os,
+    const ensightCells::elemType etype,
+    const labelUList& addr,
+    const labelUList& pointMap
+) const
+{
+    if (addr.empty()) return;
+
+    os.writeKeyword(ensightCells::key(etype));
+    os.write(addr.size());
+    os.newline();
+
+    if (etype == ensightCells::NFACED)
+    {
+        writePolysConnectivity(os, addr, pointMap);
+        return;
+    }
+
+
+    // Primitive shape - get subset and renumber
+    cellShapeList shapes(mesh_.cellShapes(), addr);
+
+    ListListOps::inplaceRenumber(pointMap, shapes);
+
+    ensightOutput::writeCellShapes(os, shapes);
+}
+
+
+void Foam::ensightPartCells::writeSerial
+(
+    ensightGeoFile& os
+) const
+{
+    const pointField& points = mesh_.points();
+
+    if (size())
+    {
+        const labelUList& pointMap = getPointMap();
+        const label nPoints = getPointsUsed();
+
+        os.beginPart(index(), name());
+        os.beginCoordinates(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 auto etype = ensightCells::elemType(typei);
+
+            writeConnectivity
+            (
+                os,
+                etype,
+                cellIds(etype),
+                pointMap
+            );
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::ensightPartCells::write(ensightGeoFile& os) const
+{
+    writeSerial(os);
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPartFaces.C b/src/fileFormats/ensight/part/ensightPartFaces.C
index 6e9d2747c49..1aedd69a281 100644
--- a/src/fileFormats/ensight/part/ensightPartFaces.C
+++ b/src/fileFormats/ensight/part/ensightPartFaces.C
@@ -6,7 +6,7 @@
      \\/     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.
@@ -28,274 +28,83 @@ License
 
 #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),
+    ensightFaces(),
     points_(points),
-    contiguousPoints_(contiguousPoints)
+    faces_(faces),
+    contiguousPoints_(contiguousPoints),
+    nPointsUsed_(0),
+    localPointMap_(nullptr)
 {
-    // Classify the face shapes
+    if (!description.empty())
+    {
+        rename(description);
+    }
+
+    // Classify face types
     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()),
+    ensightFaces(),
     points_(mesh.points()),
-    contiguousPoints_(false)
+    faces_(mesh.faces()),
+    contiguousPoints_(false),
+    nPointsUsed_(0),
+    localPointMap_(nullptr)
 {
+    identifier() = patch.index();
+
+    start() = patch.start();
+
     if (!partName.empty())
     {
         rename(partName);
     }
+    else
+    {
+        rename(patch.name());
+    }
 
-    // Classify the face shapes
-    classify(patch);
+    // Classify face types for patch region
+    classify(mesh.faces(), patch.range());
 }
 
 
 Foam::ensightPartFaces::ensightPartFaces
 (
-    label partIndex,
     const polyPatch& patch,
     const string& partName
 )
 :
-    ensightPartFaces(partIndex, patch.boundaryMesh().mesh(), patch, partName)
+    ensightPartFaces(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
+void Foam::ensightPartFaces::clearOut()
 {
-    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
-    }
+    ensightFaces::clearOut();
 
-    os.endBlock();
+    nPointsUsed_ = 0;
+    localPointMap_.reset(nullptr);
 }
 
 
diff --git a/src/fileFormats/ensight/part/ensightPartFaces.H b/src/fileFormats/ensight/part/ensightPartFaces.H
index 08fb6af73b7..de39712f555 100644
--- a/src/fileFormats/ensight/part/ensightPartFaces.H
+++ b/src/fileFormats/ensight/part/ensightPartFaces.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,6 +32,8 @@ Description
 
 SourceFiles
     ensightPartFaces.C
+    ensightPartFacesAddr.C
+    ensightPartFacesIO.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -40,7 +42,8 @@ SourceFiles
 
 #include "ensightPart.H"
 #include "ensightFaces.H"
-#include "typeInfo.H"
+#include "polyMesh.H"
+#include <memory>
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -53,51 +56,52 @@ namespace Foam
 
 class ensightPartFaces
 :
-    public ensightFaces,
-    public ensightPart
+    public ensightFaces
 {
-    // Private data
+    // Private Data
 
-        //- Start offset for patch
-        const label start_;
-
-        //- Patch index
-        const label patchIndex_;
+        //- The referenced pointField
+        const pointField& points_;
 
         //- The referenced faces
         const faceList& faces_;
 
-        //- The referenced pointField
-        const pointField& points_;
-
         //- Can skip local point renumbering when points are contiguous
-        const bool contiguousPoints_;
+        bool contiguousPoints_;
+
+
+    // Demand-driven data
+
+        //- The number of points used
+        mutable label nPointsUsed_;
+
+        //- Map global to local indices, with -1 for ununsed points
+        mutable std::unique_ptr<labelList> localPointMap_;
 
 
     // Private Member Functions
 
-        //- Track points used
-        localPoints calcLocalPoints() const;
+        //- Calculate the points used
+        void calcLocalPoints() const;
 
-        //- Element connectivity
-        void writeConnectivity
-        (
-            ensightGeoFile&,
-            const word& key,
-            const labelUList& idList,
-            const labelUList& pointMap
-        ) const;
+        //- Return the number of unique points (demand-driven)
+        label getPointsUsed() const;
+
+        //- Return the global to local indices (demand-driven)
+        labelList& getPointMap() const;
 
 
         //- Helper: write connectivity
-        void writeConnectivity
+        static void writeConnectivity
         (
-            ensightGeoFile&,
-            const word& key,
-            const faceList&,
-            const labelUList& idList,
+            ensightGeoFile& os,
+            const ensightFaces::elemType etype,
+            const UIndirectList<face>& faces,
             const labelUList& pointMap
-        ) const;
+        );
+
+        //- Write processor-local (serial) geometry
+        void writeSerial(ensightGeoFile& os) const;
 
 
         //- No copy construct
@@ -109,17 +113,12 @@ class ensightPartFaces
 
 public:
 
-    //- Runtime type information
-    TypeName("ensightFaces");
-
-
     // Constructors
 
-        //- Construct part with 0-based index, description, points and faces
+        //- Construct from points and faces.
         //  Can skip local point renumbering when points are contiguous
         ensightPartFaces
         (
-            label partIndex,
             const string& description,
             const pointField& points,
             const faceList& faces,
@@ -130,19 +129,17 @@ public:
         //- Part receives the name of the patch unless otherwise specified.
         ensightPartFaces
         (
-            label partIndex,
             const polyMesh& mesh,
             const polyPatch& patch,
-            const string& partName = ""
+            const string& partName = string::null
         );
 
         //- Construct from polyPatch
         //- Part receives the name of the patch unless otherwise specified.
-        ensightPartFaces
+        explicit ensightPartFaces
         (
-            label partIndex,
             const polyPatch& patch,
-            const string& partName = ""
+            const string& partName = string::null
         );
 
 
@@ -152,42 +149,11 @@ public:
 
     // 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();
-        }
+        //- Clear any demand-driven data
+        void clearOut();
 
-
-        //- 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
+        //- Write geometry (serial only)
         virtual void write(ensightGeoFile& os) const;
-
-        //- Helper: write geometry given the pointField
-        virtual void write(ensightGeoFile& os, const pointField& points) const;
 };
 
 
diff --git a/src/fileFormats/ensight/part/ensightPartFacesAddr.C b/src/fileFormats/ensight/part/ensightPartFacesAddr.C
new file mode 100644
index 00000000000..c9870d2a916
--- /dev/null
+++ b/src/fileFormats/ensight/part/ensightPartFacesAddr.C
@@ -0,0 +1,110 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 "ensightPartFaces.H"
+#include "ListOps.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightPartFaces::calcLocalPoints() const
+{
+    if (localPointMap_)
+    {
+        FatalErrorInFunction
+            << "localPointMap_ already allocated"
+            << abort(FatalError);
+    }
+
+    localPointMap_.reset(new labelList());
+
+    labelList& usedPoints = *localPointMap_;
+
+    usedPoints.resize(points_.size());
+
+    if (contiguousPoints_)
+    {
+        nPointsUsed_ = usedPoints.size();
+
+        ListOps::identity(usedPoints);
+
+        return;
+    }
+
+
+    // Mark up with -1 for unused entries
+    usedPoints = -1;
+    label nPoints = 0;
+
+    // Add all points from faces
+    for (const label facei : this->faceIds())
+    {
+        for (const label pointi : faces_[facei])
+        {
+            if (usedPoints[pointi] == -1)
+            {
+                usedPoints[pointi] = nPoints++;
+            }
+        }
+    }
+
+    nPointsUsed_ = nPoints;
+
+    // Compact point numbering, preserving the original order
+    nPoints = 0;
+    for (label& pointi : usedPoints)
+    {
+        if (pointi > -1)
+        {
+            pointi = nPoints++;
+        }
+    }
+}
+
+
+Foam::label Foam::ensightPartFaces::getPointsUsed() const
+{
+    if (!localPointMap_)
+    {
+        calcLocalPoints();
+    }
+
+    return nPointsUsed_;
+}
+
+
+Foam::labelList& Foam::ensightPartFaces::getPointMap() const
+{
+    if (!localPointMap_)
+    {
+        calcLocalPoints();
+    }
+
+    return *localPointMap_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightPartFacesIO.C b/src/fileFormats/ensight/part/ensightPartFacesIO.C
new file mode 100644
index 00000000000..63de361fb29
--- /dev/null
+++ b/src/fileFormats/ensight/part/ensightPartFacesIO.C
@@ -0,0 +1,120 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 "ensightPartFaces.H"
+#include "ListOps.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ensightPartFaces::writeConnectivity
+(
+    ensightGeoFile& os,
+    const ensightFaces::elemType etype,
+    const UIndirectList<face>& faces,
+    const labelUList& pointMap
+)
+{
+    if (faces.empty()) return;
+
+    os.writeKeyword(ensightFaces::key(etype));
+    os.write(faces.size());
+    os.newline();
+
+    // Write (polygon) face sizes
+    if (etype == ensightFaces::NSIDED)
+    {
+        // Write the number of points per face
+        for (const face& f : faces)
+        {
+            os.write(f.size());
+            os.newline();
+        }
+    }
+
+    // Write the points describing the face
+    // Convert global -> local index
+    // (note: Ensight indices start with 1)
+    for (const face& f : faces)
+    {
+        for (const label pointi : f)
+        {
+            os.write(pointMap[pointi] + 1);
+        }
+        os.newline();
+    }
+}
+
+
+void Foam::ensightPartFaces::writeSerial(ensightGeoFile& os) const
+{
+    if (size())
+    {
+        const labelUList& pointMap = getPointMap();
+        const label nPoints = getPointsUsed();
+
+        os.beginPart(index(), name());
+        os.beginCoordinates(nPoints);
+
+        for (direction cmpt=0; cmpt < point::nComponents; ++cmpt)
+        {
+            forAll(pointMap, pointi)
+            {
+                if (pointMap[pointi] > -1)
+                {
+                    os.write(points_[pointi].component(cmpt));
+                    os.newline();
+                }
+            }
+        }
+
+        // Write part
+        for (int typei=0; typei < ensightFaces::nTypes; ++typei)
+        {
+            const auto etype = ensightFaces::elemType(typei);
+
+            writeConnectivity
+            (
+                os,
+                etype,
+                UIndirectList<face>(faces_, faceIds(etype)),
+                pointMap
+            );
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::ensightPartFaces::write(ensightGeoFile& os) const
+{
+    writeSerial(os);
+}
+
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/part/ensightParts.C b/src/fileFormats/ensight/part/ensightParts.C
index 70baac46477..404d14cc26a 100644
--- a/src/fileFormats/ensight/part/ensightParts.C
+++ b/src/fileFormats/ensight/part/ensightParts.C
@@ -6,7 +6,7 @@
      \\/     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.
@@ -34,8 +34,6 @@ License
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::ensightParts::ensightParts(const polyMesh& mesh)
-:
-    StorageType()
 {
     recalculate(mesh);
 }
@@ -45,9 +43,7 @@ Foam::ensightParts::ensightParts(const polyMesh& mesh)
 
 void Foam::ensightParts::recalculate(const polyMesh& mesh)
 {
-    StorageType::clear();
-
-    label nPart = 0;
+    this->clear();
 
     // Track which cells are in a zone or not
     bitSet selection(mesh.nCells());
@@ -58,14 +54,14 @@ void Foam::ensightParts::recalculate(const polyMesh& mesh)
         if (returnReduce(!zn.empty(), orOp<bool>()))
         {
             selection.set(zn);
-            this->append(new ensightPartCells(nPart++, mesh, zn));
+            this->append(new ensightPartCells(mesh, zn));
         }
     }
 
-    if (!nPart)
+    if (this->empty())
     {
-        // No zones at all? - do entire mesh. Name as per ensightMesh
-        this->append(new ensightPartCells(nPart++, mesh, "internalMesh"));
+        // No zones? - do entire mesh. Name as per ensightMesh
+        this->insert(new ensightPartCells(mesh, "internalMesh"));
     }
     else
     {
@@ -74,28 +70,47 @@ void Foam::ensightParts::recalculate(const polyMesh& mesh)
 
         if (returnReduce(selection.any(), orOp<bool>()))
         {
-            this->append
+            // Place as first in the list
+            this->insert
             (
-                new ensightPartCells(nPart++, mesh, selection, "__internal__")
+                new ensightPartCells(mesh, selection, "__internal__")
             );
         }
     }
 
 
-    // Do boundaries, skipping empty and processor patches
     for (const polyPatch& p : mesh.boundaryMesh())
     {
+        // Only do real (non-processor) boundaries.
         if (isA<processorPolyPatch>(p))
         {
-            // No processor patches
             break;
         }
 
-        if (returnReduce(!p.empty(), orOp<bool>()))
+        // Skip empty patch types and zero-sized patches
+        // Would ideally like to check for emptyFvPatch,
+        // but that is not available here
+        if
+        (
+            !isA<emptyPolyPatch>(p)
+         && returnReduce(!p.empty(), orOp<bool>())
+        )
         {
-            this->append(new ensightPartFaces(nPart++, mesh, p));
+            this->append(new ensightPartFaces(p));
         }
     }
+
+    renumber();
+}
+
+
+void Foam::ensightParts::renumber(label start)
+{
+    for (ensightPart& part : *this)
+    {
+        part.index() = start;
+        ++start;
+    }
 }
 
 
@@ -109,24 +124,16 @@ void Foam::ensightParts::write(ensightGeoFile& os) const
         Info<< ' ' << part.index() << flush;
         part.write(os);
     }
-    Info<< " )" << endl;
-}
 
-
-void Foam::ensightParts::writeSummary(Ostream& os) const
-{
-    for (const ensightPart& part : *this)
-    {
-        part.writeSummary(os);
-    }
+    Info<< " )" << endl;
 }
 
 
-void Foam::ensightParts::dumpInfo(Ostream& os) const
+void Foam::ensightParts::writeDict(Ostream& os, const bool full) const
 {
     for (const ensightPart& part : *this)
     {
-        part.dumpInfo(os);
+        part.writeDict(os, full);
     }
 }
 
diff --git a/src/fileFormats/ensight/part/ensightParts.H b/src/fileFormats/ensight/part/ensightParts.H
index 6ee3b3b1f14..df8f31d171b 100644
--- a/src/fileFormats/ensight/part/ensightParts.H
+++ b/src/fileFormats/ensight/part/ensightParts.H
@@ -6,7 +6,7 @@
      \\/     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.
@@ -39,7 +39,6 @@ SourceFiles
 #define ensightParts_H
 
 #include "SLPtrList.H"
-#include "ensightPart.H"
 #include "ensightPartFaces.H"
 #include "ensightPartCells.H"
 
@@ -67,36 +66,32 @@ class ensightParts
 
 public:
 
-    //- Storage type used
-    typedef SLPtrList<ensightPart> StorageType;
-
-
     // Constructors
 
         //- Construct from polyMesh
         explicit ensightParts(const polyMesh& mesh);
 
 
-    //- Destructor
-    ~ensightParts() = default;
-
-
     // Member Functions
 
+        //- Any parts?
+        using SLPtrList<ensightPart>::empty;
+
         //- Number of parts
-        using StorageType::size;
+        using SLPtrList<ensightPart>::size;
 
         //- Clear old information and construct anew from polyMesh
         void recalculate(const polyMesh& mesh);
 
+        //- Renumber each entry, starting at given index (default: 0)
+        void renumber(label start=0);
 
-    // Output
 
-        //- Write summary information about the objects
-        void writeSummary(Ostream& os) const;
+    // Output
 
-        //- Print various types of debugging information
-        void dumpInfo(Ostream& os) const;
+        //- Write information about the object as a dictionary,
+        //- optionally with all elements
+        void writeDict(Ostream& os, const bool full=false) const;
 
         //- Write the geometry to file
         void write(autoPtr<ensightGeoFile>& os) const
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..7f9f41c8e15 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,15 +405,14 @@ 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
+            surf.faces()
         );
+
         if (!exists(meshFile))
         {
             if (verbose_)
@@ -427,7 +426,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
                 meshFile.name(),
                 writeFormat_
             );
-            osGeom << ensPart;
+            part.write(osGeom); // serial
         }
 
         // Write field
@@ -450,19 +449,19 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
         {
             ensightOutput::Serial::writePointField
             (
+                osField,
                 tfield(),
-                ensPart,
-                osField
+                part
                 // serial
             );
         }
         else
         {
-            ensightOutput::Detail::writeFaceField
+            ensightOutput::Detail::writeField
             (
-                tfield(),
-                ensPart,
                 osField,
+                tfield(),
+                part,
                 false // serial
             );
         }
diff --git a/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C b/src/surfMesh/writers/ensight/ensightSurfaceWriterUncollated.C
index e5c60b99947..c3347f7885d 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
+            surf.faces()
         );
-        osGeom << ensPart;
+        part.write(osGeom); // serial
     }
 
     wroteGeom_ = true;
@@ -212,15 +210,14 @@ 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
+            surf.faces()
         );
-        osGeom << ensPart;
+        part.write(osGeom); // serial
 
         // Write field
         osField.writeKeyword(ensightPTraits<Type>::typeName);
@@ -229,19 +226,19 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated
         {
             ensightOutput::Serial::writePointField
             (
+                osField,
                 tfield(),
-                ensPart,
-                osField
+                part
                 // serial
             );
         }
         else
         {
-            ensightOutput::Detail::writeFaceField
+            ensightOutput::Detail::writeField
             (
-                tfield(),
-                ensPart,
                 osField,
+                tfield(),
+                part,
                 false // serial
             );
         }
-- 
GitLab