diff --git a/applications/test/foamCellZoneToVTK/Make/files b/applications/test/foamCellZoneToVTK/Make/files
new file mode 100644
index 0000000000000000000000000000000000000000..a4be1f0e87117526b908dddf157a154f09296645
--- /dev/null
+++ b/applications/test/foamCellZoneToVTK/Make/files
@@ -0,0 +1,3 @@
+foamCellZoneToVTK.C
+
+EXE = $(FOAM_USER_APPBIN)/foamCellZoneToVTK
diff --git a/applications/test/foamCellZoneToVTK/Make/options b/applications/test/foamCellZoneToVTK/Make/options
new file mode 100644
index 0000000000000000000000000000000000000000..d820876f443dc7df696fce2681559e9249c223bc
--- /dev/null
+++ b/applications/test/foamCellZoneToVTK/Make/options
@@ -0,0 +1,7 @@
+EXE_INC = \
+    -I$(LIB_SRC)/fileFormats/lnInclude \
+    -I$(LIB_SRC)/meshTools/lnInclude
+
+EXE_LIBS = \
+    -lfileFormats \
+    -lmeshTools
diff --git a/applications/test/foamCellZoneToVTK/foamCellZoneToVTK.C b/applications/test/foamCellZoneToVTK/foamCellZoneToVTK.C
new file mode 100644
index 0000000000000000000000000000000000000000..a2f265db03001af8cf62564cd36e3821677a6a43
--- /dev/null
+++ b/applications/test/foamCellZoneToVTK/foamCellZoneToVTK.C
@@ -0,0 +1,161 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Application
+    foamCellZoneToVTK.C
+
+Description
+    Write tet-decomposed OpenFOAM mesh in VTK format.
+    For diagnostic purposes.
+
+\*---------------------------------------------------------------------------*/
+
+#include "argList.H"
+#include "timeSelector.H"
+#include "Time.H"
+#include "polyMesh.H"
+#include "foamVtkInternalMeshWriter.H"
+
+using namespace Foam;
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+    argList::addNote
+    (
+        "Write OpenFOAM cellZone mesh to VTK"
+    );
+    argList::addOption
+    (
+        "cellZone",
+        "name",
+        "Convert mesh subset corresponding to specified cellZone"
+    );
+    argList::addBoolOption
+    (
+        "list",
+        "List names of cellZones and exit"
+    );
+    timeSelector::addOptions();
+
+    #include "setRootCase.H"
+
+    word cellZoneName;
+    args.readIfPresent("cellZone", cellZoneName);
+
+    const bool optList = args.found("list");
+
+    if (optList)
+    {
+        if (!cellZoneName.empty())
+        {
+            Info<< "specify -list or -cellZone, but not both!" << nl;
+            return 1;
+        }
+    }
+    else if (cellZoneName.empty())
+    {
+        Info<< "Did not specify a cellZone!!" << nl;
+        return 1;
+    }
+
+    #include "createTime.H"
+
+    instantList timeDirs = timeSelector::select0(runTime, args);
+
+    fileName exportName("zonemesh-" + cellZoneName);
+
+    #include "createPolyMesh.H"
+
+    forAll(timeDirs, timei)
+    {
+        runTime.setTime(timeDirs[timei], timei);
+
+        polyMesh::readUpdateState state = mesh.readUpdate();
+
+        if (!timei || state != polyMesh::UNCHANGED)
+        {
+            fileName meshName(exportName);
+            if (state != polyMesh::UNCHANGED)
+            {
+                meshName += '_' + runTime.timeName();
+            }
+
+            if (optList)
+            {
+                Info<< "cellZones:" << nl;
+                for (const word& name : mesh.cellZones().names())
+                {
+                    Info<< "    " << name << nl;
+                }
+            }
+            else
+            {
+                const cellZone* zonePtr =
+                    mesh.cellZones().cfindZone(cellZoneName);
+
+                Info<< "cellZone " << cellZoneName;
+                if (!zonePtr)
+                {
+                    Info<< " ... not found" << nl;
+                    continue;
+                }
+                Info<< nl;
+
+                const cellZone& zn = *zonePtr;
+
+                // Define a subset
+                vtk::vtuCells vtuCells;
+                vtuCells.reset(mesh, zn);
+
+                vtk::internalMeshWriter writer
+                (
+                    mesh,
+                    vtuCells,
+                    fileName
+                    (
+                        mesh.time().globalPath() / meshName
+                    )
+                );
+
+                writer.writeGeometry();
+
+                writer.beginCellData();
+                writer.writeProcIDs();
+
+                Info<< "Wrote " << writer.output().name() << nl;
+            }
+        }
+    }
+
+    Info<< "\nEnd\n" << endl;
+
+    return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/applications/test/foamMeshToTet-vtk/Make/files b/applications/test/foamMeshToTet-vtk/Make/files
new file mode 100644
index 0000000000000000000000000000000000000000..576d08e8c11d9d6d7396a0ee9ac2a9213f159ac0
--- /dev/null
+++ b/applications/test/foamMeshToTet-vtk/Make/files
@@ -0,0 +1,4 @@
+foamMeshToTet-vtk.C
+writeVTKtetMesh.C
+
+EXE = $(FOAM_USER_APPBIN)/foamMeshToTet-vtk
diff --git a/applications/test/foamMeshToTet-vtk/Make/options b/applications/test/foamMeshToTet-vtk/Make/options
new file mode 100644
index 0000000000000000000000000000000000000000..d820876f443dc7df696fce2681559e9249c223bc
--- /dev/null
+++ b/applications/test/foamMeshToTet-vtk/Make/options
@@ -0,0 +1,7 @@
+EXE_INC = \
+    -I$(LIB_SRC)/fileFormats/lnInclude \
+    -I$(LIB_SRC)/meshTools/lnInclude
+
+EXE_LIBS = \
+    -lfileFormats \
+    -lmeshTools
diff --git a/applications/test/foamMeshToTet-vtk/foamMeshToTet-vtk.C b/applications/test/foamMeshToTet-vtk/foamMeshToTet-vtk.C
new file mode 100644
index 0000000000000000000000000000000000000000..d1d078949e7ced50204708b6c83bb94936d2dc2b
--- /dev/null
+++ b/applications/test/foamMeshToTet-vtk/foamMeshToTet-vtk.C
@@ -0,0 +1,96 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Application
+    foamMeshToTet-vtk
+
+Description
+    Write tet-decomposed OpenFOAM mesh in VTK format.
+    For diagnostic purposes.
+
+\*---------------------------------------------------------------------------*/
+
+#include "argList.H"
+#include "timeSelector.H"
+#include "Time.H"
+#include "polyMesh.H"
+
+namespace Foam
+{
+    void writeVTKtetMesh(const fileName& output, const polyMesh& mesh);
+}
+
+using namespace Foam;
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+    argList::addNote
+    (
+        "Write tet-decomposed OpenFOAM mesh in VTK"
+    );
+    argList::noParallel();
+    timeSelector::addOptions();
+
+    #include "setRootCase.H"
+    #include "createTime.H"
+
+    instantList timeDirs = timeSelector::select0(runTime, args);
+
+    fileName exportName = "tetmesh";
+    if (args.found("case"))
+    {
+        exportName += '-' + args.globalCaseName();
+    }
+
+    #include "createPolyMesh.H"
+
+    forAll(timeDirs, timei)
+    {
+        runTime.setTime(timeDirs[timei], timei);
+
+        polyMesh::readUpdateState state = mesh.readUpdate();
+
+        if (!timei || state != polyMesh::UNCHANGED)
+        {
+            fileName meshName(exportName);
+            if (state != polyMesh::UNCHANGED)
+            {
+                meshName += '_' + runTime.timeName();
+            }
+
+            writeVTKtetMesh(meshName, mesh);
+        }
+    }
+
+    Info<< "\nEnd\n" << endl;
+
+    return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/applications/test/foamMeshToTet-vtk/writeVTKtetMesh.C b/applications/test/foamMeshToTet-vtk/writeVTKtetMesh.C
new file mode 100644
index 0000000000000000000000000000000000000000..ea7157fac0df70a94724d595d06083d3eccc5c3d
--- /dev/null
+++ b/applications/test/foamMeshToTet-vtk/writeVTKtetMesh.C
@@ -0,0 +1,204 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 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 "polyMesh.H"
+#include "Fstream.H"
+#include "tetMatcher.H"
+#include "foamVtkInternalMeshWriter.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+void writeVTKtetMesh(const fileName& output, const polyMesh& mesh_)
+{
+    #if WM_LABEL_SIZE == 64
+    # define FOAM_VTK_LABEL_NAME "vtktypeint64"
+    #else
+    # define FOAM_VTK_LABEL_NAME "int"
+    #endif
+
+    const faceList& faces = mesh_.faces();
+    const labelList& faceOwner = mesh_.faceOwner();
+
+    label nTets = 0;
+
+    for (label facei = 0; facei < mesh_.nInternalFaces(); ++facei)
+    {
+        nTets += 2 * faces[facei].nTriangles();
+    }
+    for (label facei = mesh_.nInternalFaces(); facei < mesh_.nFaces(); ++facei)
+    {
+        nTets += faces[facei].nTriangles();
+    }
+    const label nPoints = (mesh_.nPoints() + mesh_.nCells());
+
+    OFstream os(output + ".vtk");
+
+    Info<< "Write: " << os.name() << nl;
+
+    os  << "# vtk DataFile Version 5.1" << nl
+        << "tet-mesh" << nl
+        << "ASCII" << nl
+        << "DATASET UNSTRUCTURED_GRID" << nl
+        << nl;
+
+    os  << "POINTS " << nPoints << " float" << nl;
+    for (const point& p : mesh_.points())
+    {
+        os  << float(p.x()) << ' '
+            << float(p.y()) << ' '
+            << float(p.z()) << nl;
+    }
+    for (const point& p : mesh_.cellCentres())
+    {
+        os  << float(p.x()) << ' '
+            << float(p.y()) << ' '
+            << float(p.z()) << nl;
+    }
+    os  << nl;
+
+    os  << "CELLS " << (1 + nTets) << ' ' << (4 * nTets) << nl;
+
+    os  << "OFFSETS " << FOAM_VTK_LABEL_NAME << nl
+        << 0;  // begin offset = 0
+    {
+        label offset = 0;
+        for (label teti = 0; teti < nTets; ++teti)
+        {
+            offset += 4;
+            os << ' ' << offset;
+        }
+        os << nl << nl;
+    }
+
+    labelList nLocalTets(mesh_.nCells(), Zero);
+
+    os  << nl
+        << "CONNECTIVITY " << FOAM_VTK_LABEL_NAME << nl;
+
+    for (label celli = 0; celli < mesh_.nCells(); ++celli)
+    {
+        const cell& cFaces = mesh_.cells()[celli];
+
+        if (tetMatcher::test(mesh_, celli))
+        {
+            // Tet: no cell-centre decomposition
+
+            const label facei = cFaces[0];
+            const face& f0 = faces[facei];
+
+            // Get the other point from f1. Tbd: check if not duplicate face
+            // (ACMI / ignoreBoundaryFaces_).
+            const face& f1 = faces[cFaces[1]];
+            label apexi = -1;
+            forAll(f1, fp)
+            {
+                apexi = f1[fp];
+                if (!f0.found(apexi))
+                {
+                    break;
+                }
+            }
+
+            const label p0 = f0[0];
+            label p1 = f0[1];
+            label p2 = f0[2];
+
+            if (faceOwner[facei] == celli)
+            {
+                std::swap(p1, p2);
+            }
+
+            ++nLocalTets[celli];
+            os << p0 << ' ' << p1 << ' ' << p2 << ' ' << apexi << nl;
+        }
+        else
+        {
+            for (const label facei : cFaces)
+            {
+                const face& f = faces[facei];
+
+                label fp0 = mesh_.tetBasePtIs()[facei];
+
+                // Fallback
+                if (fp0 < 0)
+                {
+                    fp0 = 0;
+                }
+
+                const label p0 = f[fp0];
+                label fp = f.fcIndex(fp0);
+                for (label i = 2; i < f.size(); ++i)
+                {
+                    label p1 = f[fp];
+                    fp = f.fcIndex(fp);
+                    label p2 = f[fp];
+
+                    if (faceOwner[facei] == celli)
+                    {
+                        std::swap(p1, p2);
+                    }
+
+                    ++nLocalTets[celli];
+                    os  << p0 << ' ' << p1 << ' ' << p2 << ' '
+                        << (mesh_.nPoints() + celli) << nl;
+                }
+            }
+        }
+    }
+
+    os  << nl
+        << "CELL_TYPES " << nTets << nl;
+
+    for (label teti = 0; teti < nTets; ++teti)
+    {
+        if (teti) os  << ' ';
+        os  << vtk::cellType::VTK_TETRA;
+    }
+    os << nl;
+
+    os << nl << "CELL_DATA " << nTets << nl
+       << "FIELD FieldData " << 1 << nl;
+
+    os << "cellID " << 1 << ' ' << nTets << " int" << nl;
+    forAll(nLocalTets, celli)
+    {
+        label n = nLocalTets[celli];
+        while (n--)
+        {
+            os << ' ' << celli;
+        }
+        os << nl;
+    }
+}
+
+} // End namespace Foam
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/vtk/part/foamVtkMeshMaps.H b/src/fileFormats/vtk/part/foamVtkMeshMaps.H
index c2c54cc0498606581145ab38c1202f162fa26611..474c2d158bdf9de0d8f254f6b8e55bae04149f01 100644
--- a/src/fileFormats/vtk/part/foamVtkMeshMaps.H
+++ b/src/fileFormats/vtk/part/foamVtkMeshMaps.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -75,11 +75,8 @@ public:
 
     // Constructors
 
-        //- Default construct: zero-sized, no reserved size
-        inline foamVtkMeshMaps();
-
-        //- Construct with reserved size
-        inline explicit foamVtkMeshMaps(const label size);
+        //- Default construct: zero-sized, no reserved sizes
+        foamVtkMeshMaps() = default;
 
 
     // Member Functions
@@ -87,26 +84,26 @@ public:
     // Access
 
         //- Original cell ids for all cells (regular and decomposed).
-        //  A regular mesh comprising only primitive cell types, this will just
+        //  For regular mesh comprising only primitive cell types, this will
         //  be an identity list. However, for subsetted meshes and decomposed
         //  cells this becomes a useful means of mapping from the original mesh.
-        inline const labelList& cellMap() const;
+        inline const labelList& cellMap() const noexcept;
 
         //- Write access to original cell ids
-        inline DynamicList<label>& cellMap();
+        inline DynamicList<label>& cellMap() noexcept;
 
         //- Point labels for subsetted meshes
-        inline const labelList& pointMap() const;
+        inline const labelList& pointMap() const noexcept;
 
         //- Write access to point labels for subsetted meshes
-        inline DynamicList<label>& pointMap();
+        inline DynamicList<label>& pointMap() noexcept;
 
         //- Any additional (user) labels.
         //  Eg, cell-centre labels for additional points of decomposed cells
-        inline const labelList& additionalIds() const;
+        inline const labelList& additionalIds() const noexcept;
 
         //- Write access to additional (user) labels.
-        inline DynamicList<label>& additionalIds();
+        inline DynamicList<label>& additionalIds() noexcept;
 
 
     // Edit
@@ -115,7 +112,7 @@ public:
         inline void clear();
 
         //- Renumber cell ids (cellMap and additionalIds) to account for
-        //  subset meshes
+        //- subset meshes
         void renumberCells(const labelUList& mapping);
 
         //- Renumber point ids (pointMap) to account for subset meshes
diff --git a/src/fileFormats/vtk/part/foamVtkMeshMapsI.H b/src/fileFormats/vtk/part/foamVtkMeshMapsI.H
index 3934a7f9461e5ce08df0a882b44fb445fc13df2c..2e39034ab1ee17b9dd2286352d221effdf1f2289 100644
--- a/src/fileFormats/vtk/part/foamVtkMeshMapsI.H
+++ b/src/fileFormats/vtk/part/foamVtkMeshMapsI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,24 +27,6 @@ License
 
 #include "foamVtkMeshMaps.H"
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-inline Foam::foamVtkMeshMaps::foamVtkMeshMaps()
-:
-    cellMap_(0),
-    pointMap_(0),
-    additionalIds_(0)
-{}
-
-
-inline Foam::foamVtkMeshMaps::foamVtkMeshMaps(const label size)
-:
-    cellMap_(size),
-    pointMap_(size),
-    additionalIds_(size)
-{}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 inline void Foam::foamVtkMeshMaps::clear()
@@ -56,42 +38,42 @@ inline void Foam::foamVtkMeshMaps::clear()
 
 
 inline const Foam::labelList&
-Foam::foamVtkMeshMaps::cellMap() const
+Foam::foamVtkMeshMaps::cellMap() const noexcept
 {
     return cellMap_;
 }
 
 
 inline Foam::DynamicList<Foam::label>&
-Foam::foamVtkMeshMaps::cellMap()
+Foam::foamVtkMeshMaps::cellMap() noexcept
 {
     return cellMap_;
 }
 
 
 inline const Foam::labelList&
-Foam::foamVtkMeshMaps::pointMap() const
+Foam::foamVtkMeshMaps::pointMap() const noexcept
 {
     return pointMap_;
 }
 
 
 inline Foam::DynamicList<Foam::label>&
-Foam::foamVtkMeshMaps::pointMap()
+Foam::foamVtkMeshMaps::pointMap() noexcept
 {
     return pointMap_;
 }
 
 
 inline const Foam::labelList&
-Foam::foamVtkMeshMaps::additionalIds() const
+Foam::foamVtkMeshMaps::additionalIds() const noexcept
 {
     return additionalIds_;
 }
 
 
 inline Foam::DynamicList<Foam::label>&
-Foam::foamVtkMeshMaps::additionalIds()
+Foam::foamVtkMeshMaps::additionalIds() noexcept
 {
     return additionalIds_;
 }
diff --git a/src/fileFormats/vtk/part/foamVtuCells.C b/src/fileFormats/vtk/part/foamVtuCells.C
index 1bbf7c8d520f75d237f73e730b8d0fd4dd7c6b1a..8248de41c6b455cbe4e0a3abc816fd581528995c 100644
--- a/src/fileFormats/vtk/part/foamVtuCells.C
+++ b/src/fileFormats/vtk/part/foamVtuCells.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -89,30 +89,23 @@ Foam::vtk::vtuCells::vtuCells
 }
 
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-void Foam::vtk::vtuCells::clear()
+void Foam::vtk::vtuCells::resize_all()
 {
-    vtuSizing::clear();
-    cellTypes_.clear();
-    vertLabels_.clear();
-    vertOffset_.clear();
-    faceLabels_.clear();
-    faceOffset_.clear();
-
-    maps_.clear();
-}
-
-
-void Foam::vtk::vtuCells::repopulate(const polyMesh& mesh)
-{
-    // vtuSizing::reset() called prior to this method
-
     cellTypes_.resize(nFieldCells());
     vertLabels_.resize(sizeOf(output_, slotType::CELLS));
     vertOffset_.resize(sizeOf(output_, slotType::CELLS_OFFSETS));
     faceLabels_.resize(sizeOf(output_, slotType::FACES));
     faceOffset_.resize(sizeOf(output_, slotType::FACES_OFFSETS));
+}
+
+
+void Foam::vtk::vtuCells::populateOutput(const polyMesh& mesh)
+{
+    // Already called
+    // - vtuSizing::reset
+    // - resize_all();
 
     switch (output_)
     {
@@ -163,10 +156,103 @@ void Foam::vtk::vtuCells::repopulate(const polyMesh& mesh)
 }
 
 
+void Foam::vtk::vtuCells::populateOutput(const UList<cellShape>& shapes)
+{
+    if (output_ != contentType::LEGACY && output_ != contentType::XML)
+    {
+        WarningInFunction
+            << "Internal formats not supported for shape cells - using XML"
+            << nl << nl;
+
+        output_ = contentType::XML;
+    }
+
+    vtuSizing::resetShapes(shapes);
+
+    maps_.clear();
+    resize_all();
+    // Done in populate routine:
+    /// maps_.cellMap() = identity(vtuSizing::nCells());
+
+    switch (output_)
+    {
+        case contentType::LEGACY:
+        {
+            populateShapesLegacy
+            (
+                shapes,
+                cellTypes_,
+                vertLabels_,
+                maps_
+            );
+            break;
+        }
+
+        case contentType::XML:
+        {
+            populateShapesXml
+            (
+                shapes,
+                cellTypes_,
+                vertLabels_,
+                vertOffset_,
+                faceLabels_,
+                faceOffset_,
+                maps_
+            );
+            break;
+        }
+
+        default:
+        {
+            FatalErrorInFunction
+                << "Unhandled VTK format " << int(output_) << nl
+                << exit(FatalError);
+            break;
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::vtk::vtuCells::clear()
+{
+    vtuSizing::clear();
+    cellTypes_.clear();
+    vertLabels_.clear();
+    vertOffset_.clear();
+    faceLabels_.clear();
+    faceOffset_.clear();
+
+    maps_.clear();
+}
+
+
 void Foam::vtk::vtuCells::reset(const polyMesh& mesh)
 {
     vtuSizing::reset(mesh, decomposeRequest_);
-    repopulate(mesh);
+    resize_all();
+
+    populateOutput(mesh);
+}
+
+
+void Foam::vtk::vtuCells::reset
+(
+    const polyMesh& mesh,
+    const labelUList& subsetCellsIds
+)
+{
+    vtuSizing::reset(mesh, subsetCellsIds, decomposeRequest_);
+    resize_all();
+
+    if (selectionMode() == selectionModeType::SUBSET_MESH)
+    {
+        maps_.cellMap() = subsetCellsIds;
+    }
+
+    populateOutput(mesh);
 }
 
 
@@ -184,6 +270,75 @@ void Foam::vtk::vtuCells::reset
 }
 
 
+void Foam::vtk::vtuCells::resetShapes
+(
+    const UList<cellShape>& shapes
+)
+{
+    if (output_ != contentType::LEGACY && output_ != contentType::XML)
+    {
+        WarningInFunction
+            << "VTK internal format is not supported for shape cells"
+            << " switching to xml" << nl << nl;
+
+        output_ = contentType::XML;
+    }
+
+    decomposeRequest_ = false;
+
+    vtuSizing::resetShapes(shapes);
+
+    maps_.clear();
+    resize_all();
+    maps_.cellMap() = identity(vtuSizing::nCells());
+
+    switch (output_)
+    {
+        case contentType::LEGACY:
+        {
+            populateShapesLegacy
+            (
+                shapes,
+                cellTypes_,
+                vertLabels_,
+                maps_
+            );
+            break;
+        }
+
+        case contentType::XML:
+        {
+            populateShapesXml
+            (
+                shapes,
+                cellTypes_,
+                vertLabels_,
+                vertOffset_,
+                faceLabels_,
+                faceOffset_,
+                maps_
+            );
+            break;
+        }
+
+        default:
+        {
+            FatalErrorInFunction
+                << "Unhandled VTK format " << int(output_) << nl
+                << exit(FatalError);
+            break;
+        }
+    }
+}
+
+
+void Foam::vtk::vtuCells::addPointCellLabels(const labelUList& cellIds)
+{
+    maps_.additionalIds() = cellIds;
+    setNumAddPoints(maps_.additionalIds().size());
+}
+
+
 void Foam::vtk::vtuCells::renumberCells(const labelUList& mapping)
 {
     maps_.renumberCells(mapping);
diff --git a/src/fileFormats/vtk/part/foamVtuCells.H b/src/fileFormats/vtk/part/foamVtuCells.H
index 8718caa796103d8023924ded4d7fc3d4025e178a..197190c6d5245a8410803432f0a7f6448f86d51c 100644
--- a/src/fileFormats/vtk/part/foamVtuCells.H
+++ b/src/fileFormats/vtk/part/foamVtuCells.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2014-2020 OpenCFD Ltd.
+    Copyright (C) 2014-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -59,9 +59,6 @@ SourceFiles
 namespace Foam
 {
 
-// Forward Declarations
-class polyMesh;
-
 namespace vtk
 {
 // Forward Declarations
@@ -110,9 +107,14 @@ class vtuCells
 
     // Private Member Functions
 
-        //- Create the geometry using the previously requested output and
-        //- decomposition types.
-        void repopulate(const polyMesh& mesh);
+        //- Resize storage
+        void resize_all();
+
+        //- Create the geometry entries
+        void populateOutput(const polyMesh& mesh);
+
+        //- Create the geometry entries from shapes
+        void populateOutput(const UList<cellShape>& shapes);
 
         //- No copy construct
         vtuCells(const vtuCells&) = delete;
@@ -162,10 +164,10 @@ public:
     // Access
 
         //- The output content type
-        inline enum contentType content() const;
+        inline enum contentType content() const noexcept;
 
         //- Query the polyhedral decompose requested flag.
-        inline bool decomposeRequested() const;
+        inline bool decomposeRequested() const noexcept;
 
         //- True if no cellTypes are populated.
         inline bool empty() const noexcept;
@@ -183,6 +185,14 @@ public:
         //- decomposition types.
         void reset(const polyMesh& mesh);
 
+        //- Create the geometry for a mesh subset,
+        //- using previously requested output and decomposition types.
+        void reset
+        (
+            const polyMesh& mesh,
+            const labelUList& subsetCellsIds
+        );
+
         //- Respecify requested output and decomposition type prior to
         //- creating the geometry
         void reset
@@ -192,35 +202,42 @@ public:
             const bool decompose
         );
 
+        //- Reset sizing using primitive shapes only (ADVANCED USAGE)
+        //  Effectively removes any polyhedrals!
+        void resetShapes(const UList<cellShape>& shapes);
+
         //- Renumber cell ids to account for subset meshes
         void renumberCells(const labelUList& mapping);
 
         //- Renumber point ids to account for subset meshes
         void renumberPoints(const labelUList& mapping);
 
+        //- Define which additional cell-centres are to be used (ADVANCED!)
+        void addPointCellLabels(const labelUList& cellIds);
+
 
     // Storage Access
 
         //- Values for "types" (XML) and "CELL_TYPES" (legacy)
-        inline const List<uint8_t>& cellTypes() const;
+        inline const List<uint8_t>& cellTypes() const noexcept;
 
         //- Values for "connectivity" (XML) or "CELLS" (legacy)
-        inline const labelList& vertLabels() const;
+        inline const labelList& vertLabels() const noexcept;
 
         //- Values for "offsets" (XML only)
-        inline const labelList& vertOffsets() const;
+        inline const labelList& vertOffsets() const noexcept;
 
         //- Values for "faces" (XML only)
-        inline const labelList& faceLabels() const;
+        inline const labelList& faceLabels() const noexcept;
 
         //- Values for "faceoffset" (XML only)
-        inline const labelList& faceOffsets() const;
+        inline const labelList& faceOffsets() const noexcept;
 
         //- Additional point addressing (from added point to original cell)
-        inline const labelList& addPointCellLabels() const;
+        inline const labelList& addPointCellLabels() const noexcept;
 
         //- Original cell ids for all cells (regular and decomposed).
-        inline const labelList& cellMap() const;
+        inline const labelList& cellMap() const noexcept;
 };
 
 
diff --git a/src/fileFormats/vtk/part/foamVtuCellsI.H b/src/fileFormats/vtk/part/foamVtuCellsI.H
index e6c4d91436e0e041bf3d470eaba1eb9fad9b5f41..2734351e54389e26ec51beaef309aa951a60aea0 100644
--- a/src/fileFormats/vtk/part/foamVtuCellsI.H
+++ b/src/fileFormats/vtk/part/foamVtuCellsI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -30,13 +30,13 @@ License
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 inline enum Foam::vtk::vtuCells::contentType
-Foam::vtk::vtuCells::content() const
+Foam::vtk::vtuCells::content() const noexcept
 {
     return output_;
 }
 
 
-inline bool Foam::vtk::vtuCells::decomposeRequested() const
+inline bool Foam::vtk::vtuCells::decomposeRequested() const noexcept
 {
     return decomposeRequest_;
 }
@@ -55,49 +55,49 @@ inline Foam::label Foam::vtk::vtuCells::size() const noexcept
 
 
 inline const Foam::List<uint8_t>&
-Foam::vtk::vtuCells::cellTypes() const
+Foam::vtk::vtuCells::cellTypes() const noexcept
 {
     return cellTypes_;
 }
 
 
 inline const Foam::labelList&
-Foam::vtk::vtuCells::vertLabels() const
+Foam::vtk::vtuCells::vertLabels() const noexcept
 {
     return vertLabels_;
 }
 
 
 inline const Foam::labelList&
-Foam::vtk::vtuCells::vertOffsets() const
+Foam::vtk::vtuCells::vertOffsets() const noexcept
 {
     return vertOffset_;
 }
 
 
 inline const Foam::labelList&
-Foam::vtk::vtuCells::faceLabels() const
+Foam::vtk::vtuCells::faceLabels() const noexcept
 {
     return faceLabels_;
 }
 
 
 inline const Foam::labelList&
-Foam::vtk::vtuCells::faceOffsets() const
+Foam::vtk::vtuCells::faceOffsets() const noexcept
 {
     return faceOffset_;
 }
 
 
 inline const Foam::labelList&
-Foam::vtk::vtuCells::addPointCellLabels() const
+Foam::vtk::vtuCells::addPointCellLabels() const noexcept
 {
     return maps_.additionalIds();
 }
 
 
 inline const Foam::labelList&
-Foam::vtk::vtuCells::cellMap() const
+Foam::vtk::vtuCells::cellMap() const noexcept
 {
     return maps_.cellMap();
 }
diff --git a/src/fileFormats/vtk/part/foamVtuSizing.C b/src/fileFormats/vtk/part/foamVtuSizing.C
index 369b358b1ea16d39245896eae9fb928c5b1527f8..c1a1688e7abe907e25e5e6749cf651dbfc5fe5b1 100644
--- a/src/fileFormats/vtk/part/foamVtuSizing.C
+++ b/src/fileFormats/vtk/part/foamVtuSizing.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,7 +31,7 @@ License
 #include "cellShape.H"
 
 // Only used in this file
-#include "foamVtuSizingTemplates.C"
+#include "foamVtuSizingImpl.C"
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
@@ -42,6 +42,162 @@ void Foam::vtk::vtuSizing::presizeMaps(foamVtkMeshMaps& maps) const
 }
 
 
+void Foam::vtk::vtuSizing::checkSizes
+(
+    const vtk::vtuSizing& sizing,
+
+    const label cellTypes_size,
+    const label vertLabels_size,
+    const label vertOffset_size,
+    const label faceLabels_size,
+    const label faceOffset_size,
+
+    const enum contentType output,
+    const label cellMap_size,
+    const label addPointsIds_size
+)
+{
+    label nErrors = 0;
+
+    #undef  CHECK_SIZING
+    #define CHECK_SIZING(what, sizeInput, sizeExpected)        \
+    if (sizeInput != sizeExpected)                             \
+    {                                                          \
+        if (!nErrors++)                                        \
+        {                                                      \
+            FatalErrorInFunction << "VTK sizing error" << nl;  \
+        }                                                      \
+        FatalError                                             \
+            << "    " << what << " size=" << sizeInput         \
+            << " expected " << sizeExpected << nl;             \
+    }
+
+
+    CHECK_SIZING("cellTypes", cellTypes_size, sizing.nFieldCells());
+    CHECK_SIZING("cellMap", cellMap_size, sizing.nFieldCells());
+    CHECK_SIZING("addPointsIds", addPointsIds_size, sizing.nAddPoints());
+
+    switch (output)
+    {
+        case contentType::LEGACY:
+        {
+            CHECK_SIZING("legacy", vertLabels_size, sizing.sizeLegacy());
+            break;
+        }
+
+        case contentType::XML:
+        {
+            // XML uses connectivity/offset pair.
+            CHECK_SIZING
+            (
+                "connectivity",
+                vertLabels_size,
+                sizing.sizeXml(slotType::CELLS)
+            );
+            CHECK_SIZING
+            (
+                "offsets",
+                vertOffset_size,
+                sizing.sizeXml(slotType::CELLS_OFFSETS)
+            );
+            if (sizing.nFaceLabels())
+            {
+                CHECK_SIZING
+                (
+                    "faces",
+                    faceLabels_size,
+                    sizing.sizeXml(slotType::FACES)
+                );
+
+                CHECK_SIZING
+                (
+                    "faceOffsets",
+                    faceOffset_size,
+                    sizing.sizeXml(slotType::FACES_OFFSETS)
+                );
+            }
+            break;
+        }
+
+        case contentType::INTERNAL1:
+        {
+            // VTK-internal1 connectivity/offset pair.
+            CHECK_SIZING
+            (
+                "connectivity",
+                vertLabels_size,
+                sizing.sizeInternal1(slotType::CELLS)
+            );
+            CHECK_SIZING
+            (
+                "offsets",
+                vertOffset_size,
+                sizing.sizeInternal1(slotType::CELLS_OFFSETS)
+            );
+            if (sizing.nFaceLabels())
+            {
+                CHECK_SIZING
+                (
+                    "faces",
+                    faceLabels_size,
+                    sizing.sizeInternal1(slotType::FACES)
+                );
+                CHECK_SIZING
+                (
+                    "faceOffsets",
+                    faceOffset_size,
+                    sizing.sizeInternal1(slotType::FACES_OFFSETS)
+                );
+            }
+            break;
+        }
+
+        case contentType::INTERNAL2:
+        {
+            // VTK-internal2 connectivity/offset pair.
+            CHECK_SIZING
+            (
+                "connectivity",
+                vertLabels_size,
+                sizing.sizeInternal2(slotType::CELLS)
+            );
+            CHECK_SIZING
+            (
+                "offsets",
+                vertOffset_size,
+                sizing.sizeInternal2(slotType::CELLS_OFFSETS)
+            );
+            if (sizing.nFaceLabels())
+            {
+                CHECK_SIZING
+                (
+                    "faces",
+                    faceLabels_size,
+                    sizing.sizeInternal2(slotType::FACES)
+                );
+                CHECK_SIZING
+                (
+                    "faceOffsets",
+                    faceOffset_size,
+                    sizing.sizeInternal2(slotType::FACES_OFFSETS)
+                );
+            }
+            break;
+        }
+    }
+
+    if (nErrors)
+    {
+        FatalError
+            << nl
+            << "Total of " << nErrors << " sizing errors encountered!"
+            << exit(FatalError);
+    }
+
+    #undef CHECK_SIZING
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::vtk::vtuSizing::vtuSizing() noexcept
@@ -66,6 +222,7 @@ Foam::vtk::vtuSizing::vtuSizing
 void Foam::vtk::vtuSizing::clear() noexcept
 {
     decompose_   = false;
+    selectionMode_ = FULL_MESH;
     nCells_      = 0;
     nPoints_     = 0;
     nVertLabels_ = 0;
@@ -86,20 +243,56 @@ void Foam::vtk::vtuSizing::reset
     const bool decompose
 )
 {
-    const cellModel& tet      = cellModel::ref(cellModel::TET);
-    const cellModel& pyr      = cellModel::ref(cellModel::PYR);
-    const cellModel& prism    = cellModel::ref(cellModel::PRISM);
+    reset(mesh, labelUList::null(), decompose);
+}
+
+
+void Foam::vtk::vtuSizing::reset
+(
+    const polyMesh& mesh,
+    const labelUList& subsetCellsIds,
+    const bool decompose
+)
+{
+    // References to cell shape models
+    const cellModel& tet   = cellModel::ref(cellModel::TET);
+    const cellModel& pyr   = cellModel::ref(cellModel::PYR);
+    const cellModel& prism = cellModel::ref(cellModel::PRISM);
+    const cellModel& hex   = cellModel::ref(cellModel::HEX);
     const cellModel& wedge    = cellModel::ref(cellModel::WEDGE);
     const cellModel& tetWedge = cellModel::ref(cellModel::TETWEDGE);
-    const cellModel& hex      = cellModel::ref(cellModel::HEX);
 
     const cellShapeList& shapes = mesh.cellShapes();
 
     // Unique vertex labels per polyhedral
     labelHashSet hashUniqId(2*256);
 
-    decompose_ = decompose;
-    nCells_    = mesh.nCells();
+
+    // Special treatment for mesh subsets.
+    const bool isSubsetMesh
+    (
+        notNull(subsetCellsIds)
+    );
+
+    if (isSubsetMesh)
+    {
+        decompose_  = false;  // Disallow decomposition for subset mode
+        selectionMode_ = selectionModeType::SUBSET_MESH;
+    }
+    else
+    {
+        decompose_  = decompose;  // Disallow decomposition
+        selectionMode_ = selectionModeType::FULL_MESH;
+    }
+
+    const label nInputCells =
+    (
+        isSubsetMesh
+      ? subsetCellsIds.size()
+      : shapes.size()
+    );
+
+    nCells_    = nInputCells;
     nPoints_   = mesh.nPoints();
     nAddCells_ = 0;
     nAddVerts_ = 0;
@@ -109,10 +302,10 @@ void Foam::vtk::vtuSizing::reset
     nFaceLabels_ = 0;
     nVertPoly_   = 0;
 
-    const label len = mesh.nCells();
-
-    for (label celli=0; celli < len; ++celli)
+    for (label inputi = 0; inputi < nInputCells; ++inputi)
     {
+        const label celli(isSubsetMesh ? subsetCellsIds[inputi] : inputi);
+
         const cellShape& shape = shapes[celli];
         const cellModel& model = shape.model();
 
@@ -128,15 +321,15 @@ void Foam::vtk::vtuSizing::reset
             --nCellsPoly_;
             nVertLabels_ += shape.size();
         }
-        else if (model == tetWedge && decompose)
+        else if (model == tetWedge && decompose_)
         {
             nVertLabels_ += 6;  // Treat as squeezed prism (VTK_WEDGE)
         }
-        else if (model == wedge && decompose)
+        else if (model == wedge && decompose_)
         {
             nVertLabels_ += 8;  // Treat as squeezed hex
         }
-        else if (decompose)
+        else if (decompose_)
         {
             // Polyhedral: Decompose into tets + pyramids.
             ++nAddPoints_;
@@ -199,7 +392,78 @@ void Foam::vtk::vtuSizing::reset
     }
 
     // Requested and actually required
-    decompose_ = (decompose && nCellsPoly_);
+    decompose_ = (decompose_ && nCellsPoly_);
+}
+
+
+// Synchronize changes here with the following:
+// - vtuSizing::resetShapes
+// - vtuSizing::populateArrays
+//
+void Foam::vtk::vtuSizing::resetShapes
+(
+    const UList<cellShape>& shapes
+)
+{
+    const cellModel& tet      = cellModel::ref(cellModel::TET);
+    const cellModel& pyr      = cellModel::ref(cellModel::PYR);
+    const cellModel& prism    = cellModel::ref(cellModel::PRISM);
+    const cellModel& hex      = cellModel::ref(cellModel::HEX);
+
+    decompose_ = false;  // Disallow decomposition
+    selectionMode_ = SHAPE_MESH;
+
+    const label nInputCells = shapes.size();
+
+    nCells_    = nInputCells;
+    nPoints_   = 0;
+    nAddCells_ = 0;
+    nAddVerts_ = 0;
+
+    nCellsPoly_  = 0;
+    nVertLabels_ = 0;
+    nFaceLabels_ = 0;
+    nVertPoly_   = 0;
+
+    label nIgnored = 0;
+
+    for (label inputi = 0; inputi < nInputCells; ++inputi)
+    {
+        const cellShape& shape = shapes[inputi];
+        const cellModel& model = shape.model();
+
+        if
+        (
+            model == tet
+         || model == pyr
+         || model == prism
+         || model == hex
+        )
+        {
+            nVertLabels_ += shape.size();
+
+            // Guess for number of addressed points
+            nPoints_ = max(nPoints_, max(shape));
+        }
+        else
+        {
+            --nCells_;
+            ++nIgnored;
+        }
+    }
+
+    if (nIgnored)
+    {
+        FatalErrorInFunction
+            << "Encountered " << nIgnored << " unsupported cell shapes"
+            << " ... this is likely not good" << nl
+            << exit(FatalError);
+    }
+
+    if (nCells_)
+    {
+        ++nPoints_;
+    }
 }
 
 
@@ -339,6 +603,35 @@ void Foam::vtk::vtuSizing::populateLegacy
 }
 
 
+void Foam::vtk::vtuSizing::populateShapesLegacy
+(
+    const UList<cellShape>& shapes,
+    UList<uint8_t>& cellTypes,
+    labelUList& vertLabels,
+    foamVtkMeshMaps& maps
+) const
+{
+    // Leave as zero-sized so that populateArrays doesn't fill it.
+    List<label> unused;
+
+    presizeMaps(maps);
+
+    populateArrays
+    (
+        shapes,
+        *this,
+        cellTypes,
+        vertLabels,
+        unused, // offsets
+        unused, // faces
+        unused, // facesOffsets
+        contentType::LEGACY,
+        maps.cellMap(),
+        maps.additionalIds()
+    );
+}
+
+
 void Foam::vtk::vtuSizing::populateXml
 (
     const polyMesh& mesh,
@@ -368,6 +661,38 @@ void Foam::vtk::vtuSizing::populateXml
 }
 
 
+void Foam::vtk::vtuSizing::populateShapesXml
+(
+    const UList<cellShape>& shapes,
+    UList<uint8_t>& cellTypes,
+    labelUList& connectivity,
+    labelUList& offsets,
+    labelUList& faces,
+    labelUList& facesOffsets,
+    foamVtkMeshMaps& maps
+) const
+{
+    // Leave as zero-sized so that populateArrays doesn't fill it.
+    List<label> unused;
+
+    presizeMaps(maps);
+
+    populateArrays
+    (
+        shapes,
+        *this,
+        cellTypes,
+        connectivity,
+        offsets,
+        unused, // faces
+        unused, // facesOffsets
+        contentType::XML,
+        maps.cellMap(),
+        maps.additionalIds()
+    );
+}
+
+
 #undef  definePopulateInternalMethod
 #define definePopulateInternalMethod(Type)                                   \
                                                                              \
diff --git a/src/fileFormats/vtk/part/foamVtuSizing.H b/src/fileFormats/vtk/part/foamVtuSizing.H
index 826d7f7a023eab47768ee38cf4322fbfa5d4783f..7b2c5a6081a9f6694b272b22ca7f8370b1e32ded 100644
--- a/src/fileFormats/vtk/part/foamVtuSizing.H
+++ b/src/fileFormats/vtk/part/foamVtuSizing.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -49,7 +49,7 @@ Description
 
     These are the storage entries normally associate with each output-type:
     \table
-        legacy output
+        legacy output (vtk DataFile Version 2.0)
         \c types    | vtk cell type (1-255)
         \c cells    | nLabels and unique vertex labels used by the cell, or
                     | [nLabels nFaces, nFace0Pts, id1, id2, ..., nFace1Pts, id1, id2, ...]
@@ -118,6 +118,7 @@ namespace Foam
 {
 
 // Forward Declarations
+class cellShape;
 class polyMesh;
 
 namespace vtk
@@ -134,7 +135,7 @@ public:
     // Public Data
 
     //- Types of content that the storage may represent
-    enum contentType
+    enum contentType : char
     {
         LEGACY,     //!< Legacy VTK content
         XML,        //!< XML (VTU) content
@@ -143,7 +144,7 @@ public:
     };
 
     //- The possible storage 'slots' that can be used
-    enum slotType
+    enum slotType : char
     {
         CELLS,         //!< Cell connectivity (ALL)
         CELLS_OFFSETS, //!< Cell end-offsets (XML), locations (INTERNAL1)
@@ -152,6 +153,14 @@ public:
         FACES_OFFSETS  //!< Faces end-offsets (XML) or locations (INTERNAL1)
     };
 
+    //- How the mesh cells have been selected or defined
+    enum selectionModeType : char
+    {
+        FULL_MESH,
+        SUBSET_MESH,
+        SHAPE_MESH
+    };
+
 
 private:
 
@@ -160,6 +169,9 @@ private:
         //- Polyhedral decomposition requested
         bool decompose_;
 
+        //- How the mesh cells have been selected or defined
+        selectionModeType selectionMode_;
+
         //- Number of cells in the mesh
         label nCells_;
 
@@ -199,8 +211,35 @@ private:
         //- set-size for cellMap and additionalIds
         void presizeMaps(foamVtkMeshMaps& maps) const;
 
+        //- Verify list sizes
+        static void checkSizes
+        (
+            const vtk::vtuSizing& sizing,
+
+            const label cellTypes_size,
+            const label vertLabels_size,
+            const label vertOffset_size,
+            const label faceLabels_size,
+            const label faceOffset_size,
+
+            const enum contentType output,
+
+            const label cellMap_size,
+            const label addPointsIds_size
+        );
+
+        //- Adjust cell/face offsets
+        template<class LabelType>
+        static void adjustOffsets
+        (
+            UList<LabelType>& vertOffset,
+            UList<LabelType>& faceOffset,
+            const enum contentType output,
+            const bool hasFaceStream
+        );
+
         //- Populate lists. For (legacy | xml | internal) VTK representations
-        template<class LabelType, class LabelType2>
+        template<class LabelType>
         static void populateArrays
         (
             const polyMesh& mesh,
@@ -211,11 +250,53 @@ private:
             UList<LabelType>& faceLabels,
             UList<LabelType>& faceOffset,
             const enum contentType output,
-            UList<LabelType2>& cellMap,
-            UList<LabelType2>& addPointsIds
+            labelUList& cellMap,
+            labelUList& addPointsIds
+        );
+
+        //- Populate lists - Primitive mesh shapes only!!
+        template<class LabelType>
+        static void populateArrays
+        (
+            const UList<cellShape>& shapes,
+            const vtk::vtuSizing& sizing,
+            UList<uint8_t>& cellTypes,
+            UList<LabelType>& vertLabels,
+            UList<LabelType>& vertOffset,
+            UList<LabelType>& faceLabels,    // unused
+            UList<LabelType>& faceOffset,    // unused
+            const enum contentType output,
+            labelUList& cellMap,
+            labelUList& addPointsIds  // unused
         );
 
 
+protected:
+
+    // Protected Member Functions
+
+        //- Reset list for primitive shapes only (ADVANCED USAGE)
+        void populateShapesLegacy
+        (
+            const UList<cellShape>& shapes,
+            UList<uint8_t>& cellTypes,
+            labelUList& connectivity,
+            foamVtkMeshMaps& maps
+        ) const;
+
+        //- Reset list for primitive shapes only (ADVANCED USAGE)
+        void populateShapesXml
+        (
+            const UList<cellShape>& shapes,
+            UList<uint8_t>& cellTypes,
+            labelUList& connectivity,
+            labelUList& offsets,
+            labelUList& faces,
+            labelUList& facesOffsets,
+            foamVtkMeshMaps& maps
+        ) const;
+
+
 public:
 
     // Constructors
@@ -240,6 +321,20 @@ public:
         //  Optionally with polyhedral decomposition.
         void reset(const polyMesh& mesh, const bool decompose=false);
 
+        //- Reset sizing by analyzing a subset of the mesh.
+        //  A labelUList::null() selection corresponds to the entire mesh.
+        //  Polyhedral decomposition is disabled for subsets.
+        void reset
+        (
+            const polyMesh& mesh,
+            const labelUList& subsetCellsIds,
+            const bool decompose = false
+        );
+
+        //- Reset sizing using primitive shapes only (ADVANCED USAGE)
+        //  Effectively removes any polyhedrals!
+        void resetShapes(const UList<cellShape>& shapes);
+
         //- Reset all sizes to zero.
         void clear() noexcept;
 
@@ -247,44 +342,56 @@ public:
     // Access
 
         //- Query the decompose flag (normally off)
-        inline bool decompose() const;
+        inline bool decompose() const noexcept;
+
+        //- Query how the mesh cells have been selected or defined
+        inline selectionModeType selectionMode() const noexcept;
 
         //- Number of cells for the mesh
-        inline label nCells() const;
+        inline label nCells() const noexcept;
 
         //- Number of points for the mesh
-        inline label nPoints() const;
+        inline label nPoints() const noexcept;
 
         //- Number of vertex labels for the mesh
-        inline label nVertLabels() const;
+        inline label nVertLabels() const noexcept;
 
         //- Number of polyhedral face labels for the mesh
-        inline label nFaceLabels() const;
+        inline label nFaceLabels() const noexcept;
 
         //- Number of polyhedral cells for the mesh
-        inline label nCellsPoly() const;
+        inline label nCellsPoly() const noexcept;
 
         //- Number of vertex labels for polyhedral cells of the mesh
-        inline label nVertPoly() const;
+        inline label nVertPoly() const noexcept;
 
         //- Number of additional (decomposed) cells for the mesh
-        inline label nAddCells() const;
+        inline label nAddCells() const noexcept;
 
         //- Number of additional (decomposed) points for the mesh
-        inline label nAddPoints() const;
+        inline label nAddPoints() const noexcept;
 
         //- Number of additional (decomposed) vertices for the mesh
-        inline label nAddVerts() const;
+        inline label nAddVerts() const noexcept;
 
 
         //- Number of field cells = nCells + nAddCells
-        inline label nFieldCells() const;
+        inline label nFieldCells() const noexcept;
 
         //- Number of field points = nPoints + nAddPoints
-        inline label nFieldPoints() const;
+        inline label nFieldPoints() const noexcept;
+
+
+    // Edit
+
+        //- Alter number of mesh points (ADVANCED USAGE)
+        void setNumPoints(label n) noexcept { nPoints_ = n; }
+
+        //- Alter number of additional (cell-centre) points (ADVANCED USAGE)
+        void setNumAddPoints(label n) noexcept { nAddPoints_ = n; }
 
 
-    // Derived sizes
+    // Derived Sizes
 
         //- Return the required size for the storage slot
         label sizeOf
diff --git a/src/fileFormats/vtk/part/foamVtuSizingI.H b/src/fileFormats/vtk/part/foamVtuSizingI.H
index e3c2cc55e8083d19d5d9f4b665cc605ada94f9be..6c205714879d1eee3f4a840a17cc0fae87ad044a 100644
--- a/src/fileFormats/vtk/part/foamVtuSizingI.H
+++ b/src/fileFormats/vtk/part/foamVtuSizingI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -29,75 +29,82 @@ License
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline bool Foam::vtk::vtuSizing::decompose() const
+inline bool Foam::vtk::vtuSizing::decompose() const noexcept
 {
     return decompose_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nCells() const
+inline Foam::vtk::vtuSizing::selectionModeType
+Foam::vtk::vtuSizing::selectionMode() const noexcept
+{
+    return selectionMode_;
+}
+
+
+inline Foam::label Foam::vtk::vtuSizing::nCells() const noexcept
 {
     return nCells_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nPoints() const
+inline Foam::label Foam::vtk::vtuSizing::nPoints() const noexcept
 {
     return nPoints_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nVertLabels() const
+inline Foam::label Foam::vtk::vtuSizing::nVertLabels() const noexcept
 {
     return nVertLabels_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nFaceLabels() const
+inline Foam::label Foam::vtk::vtuSizing::nFaceLabels() const noexcept
 {
     return nFaceLabels_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nCellsPoly() const
+inline Foam::label Foam::vtk::vtuSizing::nCellsPoly() const noexcept
 {
     return nCellsPoly_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nVertPoly() const
+inline Foam::label Foam::vtk::vtuSizing::nVertPoly() const noexcept
 {
     return nVertPoly_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nAddCells() const
+inline Foam::label Foam::vtk::vtuSizing::nAddCells() const noexcept
 {
     return nAddCells_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nAddPoints() const
+inline Foam::label Foam::vtk::vtuSizing::nAddPoints() const noexcept
 {
     return nAddPoints_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nAddVerts() const
+inline Foam::label Foam::vtk::vtuSizing::nAddVerts() const noexcept
 {
     return nAddVerts_;
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nFieldCells() const
+inline Foam::label Foam::vtk::vtuSizing::nFieldCells() const noexcept
 {
-    return nCells_ + nAddCells_;
+    return (nCells_ + nAddCells_);
 }
 
 
-inline Foam::label Foam::vtk::vtuSizing::nFieldPoints() const
+inline Foam::label Foam::vtk::vtuSizing::nFieldPoints() const noexcept
 {
-    return nPoints_ + nAddPoints_;
+    return (nPoints_ + nAddPoints_);
 }
 
 
diff --git a/src/fileFormats/vtk/part/foamVtuSizingTemplates.C b/src/fileFormats/vtk/part/foamVtuSizingImpl.C
similarity index 68%
rename from src/fileFormats/vtk/part/foamVtuSizingTemplates.C
rename to src/fileFormats/vtk/part/foamVtuSizingImpl.C
index 7770f2a2579c202cd1746347b5efbff1703cf19c..69abf42df56203f6d8496da6c8a5dbe883fe7631 100644
--- a/src/fileFormats/vtk/part/foamVtuSizingTemplates.C
+++ b/src/fileFormats/vtk/part/foamVtuSizingImpl.C
@@ -32,127 +32,54 @@ License
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-template<class LabelType, class LabelType2>
-void Foam::vtk::vtuSizing::populateArrays
+template<class LabelType>
+void Foam::vtk::vtuSizing::adjustOffsets
 (
-    const polyMesh& mesh,
-    const vtk::vtuSizing& sizing,
-    UList<uint8_t>& cellTypes,
-    UList<LabelType>& vertLabels,
     UList<LabelType>& vertOffset,
-    UList<LabelType>& faceLabels,
     UList<LabelType>& faceOffset,
     const enum contentType output,
-    UList<LabelType2>& cellMap,
-    UList<LabelType2>& addPointsIds
+    const bool hasFaceStream
 )
 {
-    // Characteristics
-
-    // Are vertLabels prefixed with the size?
-    // Also use as the size of the prefixed information
-    const int prefix =
-    (
-        output == contentType::LEGACY
-     || output == contentType::INTERNAL1
-    ) ? 1 : 0;
-
-
-    // STAGE 1: Verify storage sizes
-
-    if (cellTypes.size() != sizing.nFieldCells())
-    {
-        FatalErrorInFunction
-            << " cellTypes size=" << cellTypes.size()
-            << " expected " << sizing.nFieldCells()
-            << exit(FatalError);
-    }
-
-    if (cellMap.size() != sizing.nFieldCells())
-    {
-        FatalErrorInFunction
-            << " cellMap size=" << cellMap.size()
-            << " expected " << sizing.nFieldCells()
-            << exit(FatalError);
-    }
-
-    if (addPointsIds.size() != sizing.nAddPoints())
-    {
-        FatalErrorInFunction
-            << " addPointsIds size=" << addPointsIds.size()
-            << " expected " << sizing.nAddPoints()
-            << exit(FatalError);
-    }
+    // ===========================================
+    // Adjust vertOffset for all cells
+    // A second pass is needed for several reasons.
+    // - Additional (decomposed) cells are placed out of sequence
+    // - INTERNAL1 connectivity has size prefixed
+    //
+    // Cell offsets:
+    // - XML format expects end-offsets,
+    // - INTERNAL1 expects begin-offsets
+    // - INTERNAL2 expects begin/end-offsets
 
     switch (output)
     {
-        case contentType::LEGACY:
-        {
-            if (vertLabels.size() != sizing.sizeLegacy())
-            {
-                FatalErrorInFunction
-                    << " legacy size=" << vertLabels.size()
-                    << " expected " << sizing.sizeLegacy()
-                    << exit(FatalError);
-            }
+        case contentType::LEGACY: // Nothing to do
             break;
-        }
 
         case contentType::XML:
         {
-            // XML uses connectivity/offset pair.
-            if
-            (
-                vertLabels.size()
-             != sizing.sizeXml(slotType::CELLS)
-            )
-            {
-                FatalErrorInFunction
-                    << " connectivity size=" << vertLabels.size()
-                    << " expected "
-                    << sizing.sizeXml(slotType::CELLS)
-                    << exit(FatalError);
-            }
-
-            if
-            (
-                vertOffset.size()
-             != sizing.sizeXml(slotType::CELLS_OFFSETS)
-            )
-            {
-                FatalErrorInFunction
-                    << " offsets size=" << vertOffset.size()
-                    << " expected "
-                    << sizing.sizeXml(slotType::CELLS_OFFSETS)
-                    << exit(FatalError);
-            }
-
-            if (sizing.nFaceLabels())
-            {
-                if
-                (
-                    faceLabels.size()
-                 != sizing.sizeXml(slotType::FACES)
-                )
-                {
-                    FatalErrorInFunction
-                        << " faces size=" << faceLabels.size()
-                        << " expected "
-                        << sizing.sizeXml(slotType::FACES)
-                        << exit(FatalError);
-                }
+            // Transform cell sizes (vertOffset) into begin offsets
+
+            // vertOffset[0] already contains its size, leave untouched
+            for (label i = 1; i < vertOffset.size(); ++i)
+            {
+                vertOffset[i] += vertOffset[i-1];
+            }
+
+            // The end face offsets, leaving -1 untouched
+            if (hasFaceStream)
+            {
+                LabelType prev(0);
 
-                if
-                (
-                    faceOffset.size()
-                 != sizing.sizeXml(slotType::FACES_OFFSETS)
-                )
+                for (LabelType& off : faceOffset)
                 {
-                    FatalErrorInFunction
-                        << " facesOffsets size=" << faceOffset.size()
-                        << " expected "
-                        << sizing.sizeXml(slotType::FACES_OFFSETS)
-                        << exit(FatalError);
+                    const LabelType sz(off);
+                    if (sz > 0)
+                    {
+                        prev += sz;
+                        off = prev;
+                    }
                 }
             }
             break;
@@ -160,59 +87,31 @@ void Foam::vtk::vtuSizing::populateArrays
 
         case contentType::INTERNAL1:
         {
-            // VTK-internal1 connectivity/offset pair.
-            if
-            (
-                vertLabels.size()
-             != sizing.sizeInternal1(slotType::CELLS)
-            )
-            {
-                FatalErrorInFunction
-                    << " connectivity size=" << vertLabels.size()
-                    << " expected "
-                    << sizing.sizeInternal1(slotType::CELLS)
-                    << exit(FatalError);
-            }
-
-            if
-            (
-                vertOffset.size()
-             != sizing.sizeInternal1(slotType::CELLS_OFFSETS)
-            )
-            {
-                FatalErrorInFunction
-                    << " offsets size=" << vertOffset.size()
-                    << " expected "
-                    << sizing.sizeInternal1(slotType::CELLS_OFFSETS)
-                    << exit(FatalError);
-            }
-
-            if (sizing.nFaceLabels())
-            {
-                if
-                (
-                    faceLabels.size()
-                 != sizing.sizeInternal1(slotType::FACES)
-                )
+            // Transform cell sizes (vertOffset) into begin offsets
+            {
+                LabelType beg(0);
+
+                for (LabelType& off : vertOffset)
                 {
-                    FatalErrorInFunction
-                        << " faces size=" << faceLabels.size()
-                        << " expected "
-                        << sizing.sizeInternal1(slotType::FACES)
-                        << exit(FatalError);
+                    const LabelType sz(off);
+                    off = beg;
+                    beg += 1 + sz;  // Additional 1 to skip embedded prefix
                 }
+            }
+
+            // The begin face offsets, leaving -1 untouched
+            if (hasFaceStream)
+            {
+                LabelType beg(0);
 
-                if
-                (
-                    faceOffset.size()
-                 != sizing.sizeInternal1(slotType::FACES_OFFSETS)
-                )
+                for (LabelType& off : faceOffset)
                 {
-                    FatalErrorInFunction
-                        << " facesOffsets size=" << faceOffset.size()
-                        << " expected "
-                        << sizing.sizeInternal1(slotType::FACES_OFFSETS)
-                        << exit(FatalError);
+                    const LabelType sz(off);
+                    if (sz > 0)
+                    {
+                        off = beg;
+                        beg += sz;
+                    }
                 }
             }
             break;
@@ -220,64 +119,92 @@ void Foam::vtk::vtuSizing::populateArrays
 
         case contentType::INTERNAL2:
         {
-            // VTK-internal2 connectivity/offset pair.
-            if
-            (
-                vertLabels.size()
-             != sizing.sizeInternal2(slotType::CELLS)
-            )
-            {
-                FatalErrorInFunction
-                    << " connectivity size=" << vertLabels.size()
-                    << " expected "
-                    << sizing.sizeInternal2(slotType::CELLS)
-                    << exit(FatalError);
-            }
-
-            if
-            (
-                vertOffset.size()
-             != sizing.sizeInternal2(slotType::CELLS_OFFSETS)
-            )
-            {
-                FatalErrorInFunction
-                    << " offsets size=" << vertOffset.size()
-                    << " expected "
-                    << sizing.sizeInternal2(slotType::CELLS_OFFSETS)
-                    << exit(FatalError);
-            }
-
-            if (sizing.nFaceLabels())
-            {
-                if
-                (
-                    faceLabels.size()
-                 != sizing.sizeInternal2(slotType::FACES)
-                )
+            // Transform cell sizes (vertOffset) into begin/end offsets
+            // input    [n1, n2, n3, ..., 0]
+            // becomes  [0, n1, n1+n2, n1+n2+n3, ..., nTotal]
+
+            // The last entry of vertOffset was initialized as zero and
+            // never revisited, so the following loop is OK
+            {
+                LabelType total(0);
+
+                for (LabelType& off : vertOffset)
                 {
-                    FatalErrorInFunction
-                        << " faces size=" << faceLabels.size()
-                        << " expected "
-                        << sizing.sizeInternal2(slotType::FACES)
-                        << exit(FatalError);
+                    const LabelType sz(off);
+                    off = total;
+                    total += sz;
                 }
+            }
+
+            // The begin face offsets, leaving -1 untouched
+            if (hasFaceStream)
+            {
+                LabelType beg(0);
 
-                if
-                (
-                    faceOffset.size()
-                 != sizing.sizeInternal2(slotType::FACES_OFFSETS)
-                )
+                for (LabelType& off : faceOffset)
                 {
-                    FatalErrorInFunction
-                        << " facesOffsets size=" << faceOffset.size()
-                        << " expected "
-                        << sizing.sizeInternal2(slotType::FACES_OFFSETS)
-                        << exit(FatalError);
+                    const LabelType sz(off);
+                    if (sz > 0)
+                    {
+                        off = beg;
+                        beg += sz;
+                    }
                 }
             }
             break;
         }
     }
+}
+
+
+template<class LabelType>
+void Foam::vtk::vtuSizing::populateArrays
+(
+    const polyMesh& mesh,
+    const vtk::vtuSizing& sizing,
+
+    UList<uint8_t>& cellTypes,
+    UList<LabelType>& vertLabels,
+    UList<LabelType>& vertOffset,
+    UList<LabelType>& faceLabels,
+    UList<LabelType>& faceOffset,
+    const enum contentType output,
+    labelUList& cellMap,
+    labelUList& addPointsIds
+)
+{
+    if (sizing.selectionMode() == selectionModeType::SHAPE_MESH)
+    {
+        FatalErrorInFunction
+            << "Programming error ... attempting to populate a VTU mesh"
+            << " but it was originally sized using independent cell shapes"
+            << exit(FatalError);
+    }
+
+    // Verify storage sizes
+    checkSizes
+    (
+        sizing,
+
+        cellTypes.size(),
+        vertLabels.size(), vertOffset.size(),
+        faceLabels.size(), faceOffset.size(),
+
+        output,
+
+        cellMap.size(),
+        addPointsIds.size()
+    );
+
+    // Characteristics
+
+    // Are vertLabels prefixed with the size?
+    // Also use as the size of the prefixed information
+    const int prefix =
+    (
+        output == contentType::LEGACY
+     || output == contentType::INTERNAL1
+    ) ? 1 : 0;
 
 
     // Initialization
@@ -297,9 +224,9 @@ void Foam::vtk::vtuSizing::populateArrays
     const cellModel& tet      = cellModel::ref(cellModel::TET);
     const cellModel& pyr      = cellModel::ref(cellModel::PYR);
     const cellModel& prism    = cellModel::ref(cellModel::PRISM);
+    const cellModel& hex      = cellModel::ref(cellModel::HEX);
     const cellModel& wedge    = cellModel::ref(cellModel::WEDGE);
     const cellModel& tetWedge = cellModel::ref(cellModel::TETWEDGE);
-    const cellModel& hex      = cellModel::ref(cellModel::HEX);
 
     const cellShapeList& shapes = mesh.cellShapes();
 
@@ -316,11 +243,11 @@ void Foam::vtk::vtuSizing::populateArrays
     // Index into vertLabels for decomposed polys
     label nVertDecomp = sizing.nVertLabels() + prefix*sizing.nCells();
 
-    // Placement of decomposed cells
+    // Placement of additional decomposed cells
     label nCellDecomp = mesh.nCells();
 
     // Placement of additional point labels
-    label nPointDecomp = 0;
+    label nPointDecomp = mesh.nPoints();
 
     // Non-decomposed polyhedral are represented as a face-stream.
     // For legacy format, this stream replaces the normal connectivity
@@ -346,23 +273,48 @@ void Foam::vtk::vtuSizing::populateArrays
     // the per-cell vertLabels entries, and the faceOffset contains the *size*
     // associated with the per-cell faceLabels.
 
-    const label len = shapes.size();
 
-    for (label celli=0; celli < len; ++celli)
+    // Special treatment for mesh subsets
+    // Here the cellMap is the list of input cells!
+
+    const bool isSubsetMesh
+    (
+        sizing.selectionMode() == selectionModeType::SUBSET_MESH
+    );
+
+    const label nInputCells =
+    (
+        isSubsetMesh
+      ? cellMap.size()
+      : shapes.size()
+    );
+
+
+    for
+    (
+        label inputi = 0, cellIndex = 0; // cellIndex: the ouput location
+        inputi < nInputCells;
+        ++inputi, ++cellIndex
+    )
     {
+        const label celli(isSubsetMesh ? cellMap[inputi] : inputi);
+
         const cellShape& shape = shapes[celli];
         const cellModel& model = shape.model();
 
-        cellMap[celli] = celli;
+        if (!isSubsetMesh)
+        {
+            cellMap[cellIndex] = celli;
+        }
 
         if (model == tet)
         {
-            cellTypes[celli] = vtk::cellType::VTK_TETRA;
+            cellTypes[cellIndex] = vtk::cellType::VTK_TETRA;
             constexpr label nShapePoints = 4; // OR shape.size();
 
             if (vertOffset.size())
             {
-                vertOffset[celli] = nShapePoints;
+                vertOffset[cellIndex] = nShapePoints;
             }
             if (prefix)
             {
@@ -376,12 +328,12 @@ void Foam::vtk::vtuSizing::populateArrays
         }
         else if (model == pyr)
         {
-            cellTypes[celli] = vtk::cellType::VTK_PYRAMID;
+            cellTypes[cellIndex] = vtk::cellType::VTK_PYRAMID;
             constexpr label nShapePoints = 5; // OR shape.size();
 
             if (vertOffset.size())
             {
-                vertOffset[celli] = nShapePoints;
+                vertOffset[cellIndex] = nShapePoints;
             }
             if (prefix)
             {
@@ -395,12 +347,12 @@ void Foam::vtk::vtuSizing::populateArrays
         }
         else if (model == hex)
         {
-            cellTypes[celli] = vtk::cellType::VTK_HEXAHEDRON;
+            cellTypes[cellIndex] = vtk::cellType::VTK_HEXAHEDRON;
             constexpr label nShapePoints = 8; // OR shape.size();
 
             if (vertOffset.size())
             {
-                vertOffset[celli] = nShapePoints;
+                vertOffset[cellIndex] = nShapePoints;
             }
             if (prefix)
             {
@@ -414,12 +366,12 @@ void Foam::vtk::vtuSizing::populateArrays
         }
         else if (model == prism)
         {
-            cellTypes[celli] = vtk::cellType::VTK_WEDGE;
+            cellTypes[cellIndex] = vtk::cellType::VTK_WEDGE;
             constexpr label nShapePoints = 6; // OR shape.size();
 
             if (vertOffset.size())
             {
-                vertOffset[celli] = nShapePoints;
+                vertOffset[cellIndex] = nShapePoints;
             }
             if (prefix)
             {
@@ -437,12 +389,12 @@ void Foam::vtk::vtuSizing::populateArrays
         else if (model == tetWedge && sizing.decompose())
         {
             // Treat as squeezed prism
-            cellTypes[celli] = vtk::cellType::VTK_WEDGE;
+            cellTypes[cellIndex] = vtk::cellType::VTK_WEDGE;
             constexpr label nShapePoints = 6;
 
             if (vertOffset.size())
             {
-                vertOffset[celli] = nShapePoints;
+                vertOffset[cellIndex] = nShapePoints;
             }
             if (prefix)
             {
@@ -459,12 +411,12 @@ void Foam::vtk::vtuSizing::populateArrays
         else if (model == wedge && sizing.decompose())
         {
             // Treat as squeezed hex
-            cellTypes[celli] = vtk::cellType::VTK_HEXAHEDRON;
+            cellTypes[cellIndex] = vtk::cellType::VTK_HEXAHEDRON;
             constexpr label nShapePoints = 8;
 
             if (vertOffset.size())
             {
-                vertOffset[celli] = nShapePoints;
+                vertOffset[cellIndex] = nShapePoints;
             }
             if (prefix)
             {
@@ -492,7 +444,7 @@ void Foam::vtk::vtuSizing::populateArrays
 
             // Mapping from additional point to cell, and the new vertex from
             // the cell-centre
-            const label newVertexLabel = mesh.nPoints() + nPointDecomp;
+            const label newVertexLabel = nPointDecomp;
 
             addPointsIds[nPointDecomp++] = celli;
 
@@ -526,7 +478,7 @@ void Foam::vtk::vtuSizing::populateArrays
                     if (firstCell)
                     {
                         firstCell = false;
-                        celLoc = celli;
+                        celLoc = cellIndex;
                         vrtLoc = nVertLabels;
                         nVertLabels += prefix + nShapePoints;
                     }
@@ -578,7 +530,7 @@ void Foam::vtk::vtuSizing::populateArrays
                     if (firstCell)
                     {
                         firstCell = false;
-                        celLoc = celli;
+                        celLoc = cellIndex;
                         vrtLoc = nVertLabels;
                         nVertLabels += prefix + nShapePoints;
                     }
@@ -626,7 +578,7 @@ void Foam::vtk::vtuSizing::populateArrays
 
             // face-stream
             //   [nFaces, nFace0Pts, id1, id2, ..., nFace1Pts, id1, id2, ...]
-            cellTypes[celli] = vtk::cellType::VTK_POLYHEDRON;
+            cellTypes[cellIndex] = vtk::cellType::VTK_POLYHEDRON;
             const labelList& cFaces = mesh.cells()[celli];
 
             const label startLabel = faceIndexer;
@@ -676,9 +628,9 @@ void Foam::vtk::vtuSizing::populateArrays
             else
             {
                 // Size for face stream
-                faceOffset[celli] = (faceIndexer - startLabel);
+                faceOffset[cellIndex] = (faceIndexer - startLabel);
 
-                vertOffset[celli] = hashUniqId.size();
+                vertOffset[cellIndex] = hashUniqId.size();
                 if (prefix)
                 {
                     vertLabels[nVertLabels++] = hashUniqId.size();
@@ -703,108 +655,224 @@ void Foam::vtk::vtuSizing::populateArrays
     // - INTERNAL1 expects begin-offsets
     // - INTERNAL2 expects begin/end-offsets
 
-    switch (output)
+    adjustOffsets<LabelType>
+    (
+        vertOffset,
+        faceOffset,
+        output,
+        sizing.nFaceLabels()  // hasFaceStream
+    );
+}
+
+
+
+// Synchronize changes here with the following:
+// - vtuSizing::resetShapes
+// - vtuSizing::populateArrays
+
+template<class LabelType>
+void Foam::vtk::vtuSizing::populateArrays
+(
+    const UList<cellShape>& shapes,
+    const vtk::vtuSizing& sizing,
+
+    UList<uint8_t>& cellTypes,
+    UList<LabelType>& vertLabels,
+    UList<LabelType>& vertOffset,
+    UList<LabelType>& faceLabels,
+    UList<LabelType>& faceOffset,
+    const enum contentType output,
+    labelUList& cellMap,
+    labelUList& addPointsIds
+)
+{
+    if (sizing.selectionMode() != selectionModeType::SHAPE_MESH)
     {
-        case contentType::LEGACY: // nothing to do
-            break;
+        FatalErrorInFunction
+            << "Programming error ... attempting to populate a VTU mesh"
+            << " from cell shapes, but sizing originated from a different"
+            << " representation" << nl
+            << exit(FatalError);
+    }
 
-        case contentType::XML:
+    // Verify storage sizes
+    checkSizes
+    (
+        sizing,
+
+        cellTypes.size(),
+        vertLabels.size(), vertOffset.size(),
+        faceLabels.size(), faceOffset.size(),
+
+        output,
+
+        cellMap.size(),
+        addPointsIds.size()
+    );
+
+    // Characteristics
+
+    // Are vertLabels prefixed with the size?
+    // Also use as the size of the prefixed information
+    const int prefix =
+    (
+        output == contentType::LEGACY
+     || output == contentType::INTERNAL1
+    ) ? 1 : 0;
+
+
+    // Initialization
+
+    faceOffset = -1;
+
+    // For INTERNAL2, the vertOffset is (nFieldCells+1), which means that
+    // the last entry is never visited. Set as zero now.
+
+    if (vertOffset.size())
+    {
+        vertOffset.first() = 0;
+        vertOffset.last() = 0;
+    }
+
+
+    const cellModel& tet      = cellModel::ref(cellModel::TET);
+    const cellModel& pyr      = cellModel::ref(cellModel::PYR);
+    const cellModel& prism    = cellModel::ref(cellModel::PRISM);
+    const cellModel& hex      = cellModel::ref(cellModel::HEX);
+
+    // Index into vertLabels for normal cells
+    label nVertLabels = 0;
+
+    // ===========================================
+    // STAGE 2: Rewrite in VTK form
+    // During this stage, the vertOffset contains the *size* associated with
+    // the per-cell vertLabels entries, and the faceOffset contains the *size*
+    // associated with the per-cell faceLabels.
+
+    const label nInputCells = shapes.size();
+
+    label nIgnored = 0;
+
+    for
+    (
+        label inputi = 0, cellIndex = 0; // cellIndex: the ouput location
+        inputi < nInputCells;
+        ++inputi, ++cellIndex
+    )
+    {
+        const cellShape& shape = shapes[inputi];
+        const cellModel& model = shape.model();
+
+        if (model == tet)
         {
-            // Transform cell sizes (vertOffset) into begin offsets
+            cellTypes[cellIndex] = vtk::cellType::VTK_TETRA;
+            constexpr label nShapePoints = 4; // OR shape.size();
 
-            // vertOffset[0] already contains its size, leave untouched
-            for (label i = 1; i < vertOffset.size(); ++i)
+            if (vertOffset.size())
             {
-                vertOffset[i] += vertOffset[i-1];
+                vertOffset[cellIndex] = nShapePoints;
             }
-
-            // The end face offsets, leaving -1 untouched
-            if (sizing.nFaceLabels())
+            if (prefix)
             {
-                LabelType prev(0);
+                vertLabels[nVertLabels++] = nShapePoints;
+            }
 
-                for (LabelType& off : faceOffset)
-                {
-                    const LabelType sz(off);
-                    if (sz > 0)
-                    {
-                        prev += sz;
-                        off = prev;
-                    }
-                }
+            for (const label cpi : shape)
+            {
+                vertLabels[nVertLabels++] = cpi;
             }
-            break;
         }
-
-        case contentType::INTERNAL1:
+        else if (model == pyr)
         {
-            // Transform cell sizes (vertOffset) into begin offsets
-            {
-                LabelType beg(0);
+            cellTypes[cellIndex] = vtk::cellType::VTK_PYRAMID;
+            constexpr label nShapePoints = 5; // OR shape.size();
 
-                for (LabelType& off : vertOffset)
-                {
-                    const LabelType sz(off);
-                    off = beg;
-                    beg += 1 + sz;  // Additional 1 to skip embedded prefix
-                }
+            if (vertOffset.size())
+            {
+                vertOffset[cellIndex] = nShapePoints;
             }
-
-            // The begin face offsets, leaving -1 untouched
-            if (sizing.nFaceLabels())
+            if (prefix)
             {
-                LabelType beg(0);
+                vertLabels[nVertLabels++] = nShapePoints;
+            }
 
-                for (LabelType& off : faceOffset)
-                {
-                    const LabelType sz(off);
-                    if (sz > 0)
-                    {
-                        off = beg;
-                        beg += sz;
-                    }
-                }
+            for (const label cpi : shape)
+            {
+                vertLabels[nVertLabels++] = cpi;
             }
-            break;
         }
-
-        case contentType::INTERNAL2:
+        else if (model == hex)
         {
-            // Transform cell sizes (vertOffset) into begin/end offsets
-            // input    [n1, n2, n3, ..., 0]
-            // becomes  [0, n1, n1+n2, n1+n2+n3, ..., nTotal]
+            cellTypes[cellIndex] = vtk::cellType::VTK_HEXAHEDRON;
+            constexpr label nShapePoints = 8; // OR shape.size();
 
-            // The last entry of vertOffset was initialized as zero and
-            // never revisited, so the following loop is OK
+            if (vertOffset.size())
             {
-                LabelType total(0);
-
-                for (LabelType& off : vertOffset)
-                {
-                    const LabelType sz(off);
-                    off = total;
-                    total += sz;
-                }
+                vertOffset[cellIndex] = nShapePoints;
+            }
+            if (prefix)
+            {
+                vertLabels[nVertLabels++] = nShapePoints;
             }
 
-            // The begin face offsets, leaving -1 untouched
-            if (sizing.nFaceLabels())
+            for (const label cpi : shape)
             {
-                LabelType beg(0);
+                vertLabels[nVertLabels++] = cpi;
+            }
+        }
+        else if (model == prism)
+        {
+            cellTypes[cellIndex] = vtk::cellType::VTK_WEDGE;
+            constexpr label nShapePoints = 6; // OR shape.size();
 
-                for (LabelType& off : faceOffset)
-                {
-                    const LabelType sz(off);
-                    if (sz > 0)
-                    {
-                        off = beg;
-                        beg += sz;
-                    }
-                }
+            if (vertOffset.size())
+            {
+                vertOffset[cellIndex] = nShapePoints;
             }
-            break;
+            if (prefix)
+            {
+                vertLabels[nVertLabels++] = nShapePoints;
+            }
+
+            // VTK_WEDGE triangles point outwards (swap 1<->2, 4<->5)
+            vertLabels[nVertLabels++] = shape[0];
+            vertLabels[nVertLabels++] = shape[2];
+            vertLabels[nVertLabels++] = shape[1];
+            vertLabels[nVertLabels++] = shape[3];
+            vertLabels[nVertLabels++] = shape[5];
+            vertLabels[nVertLabels++] = shape[4];
+        }
+        else
+        {
+            // Silent here.
+            // - already complained (and skipped) during initial sizing
+            --cellIndex;
+            ++nIgnored;
         }
     }
+
+    // May have been done by caller,
+    // but for additional safety set an identity mapping
+    ListOps::identity(cellMap);
+
+    // ===========================================
+    // Adjust vertOffset for all cells
+    // A second pass is needed for several reasons.
+    // - Additional (decomposed) cells are placed out of sequence
+    // - INTERNAL1 connectivity has size prefixed
+    //
+    // Cell offsets:
+    // - XML format expects end-offsets,
+    // - INTERNAL1 expects begin-offsets
+    // - INTERNAL2 expects begin/end-offsets
+
+    adjustOffsets<LabelType>
+    (
+        vertOffset,
+        faceOffset,
+        output,
+        sizing.nFaceLabels()  // hasFaceStream
+    );
 }