diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 26012e9f710e1c5bb3a8200c0a31874eb81f18f6..7b633c6b3f4d8e149e9a1a045b7e7c9ba21fd608 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -1,3 +1,5 @@
+common/manifoldCellsMeshObject.C
+
 colours/colourTable.C
 colours/colourTables.C
 colours/colourTools.C
diff --git a/src/fileFormats/common/manifoldCellsMeshObject.C b/src/fileFormats/common/manifoldCellsMeshObject.C
new file mode 100644
index 0000000000000000000000000000000000000000..3810334129924f737ef4dbf2f416128cad0f4ecb
--- /dev/null
+++ b/src/fileFormats/common/manifoldCellsMeshObject.C
@@ -0,0 +1,206 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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 "manifoldCellsMeshObject.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeName(manifoldCellsMeshObject);
+}
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Change entries in labelList
+static inline void replaceAll
+(
+    const label oldVal,
+    const label newVal,
+    labelUList& list
+)
+{
+    for (label& val : list)
+    {
+        if (val == oldVal)
+        {
+            val = newVal;
+        }
+    }
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+Foam::refPtr<Foam::cellList> Foam::manifoldCellsMeshObject::filter
+(
+    const polyMesh& mesh,
+    label& nCellsCorrected
+)
+{
+    const auto& oldCells = mesh.cells();
+
+    // Start with copy of existing cell list
+    auto tnewCells = refPtr<cellList>::New(oldCells);
+    auto& newCells = tnewCells.ref();
+
+    DynamicList<label> removed;
+
+    nCellsCorrected = 0;
+    forAll(oldCells, celli)
+    {
+        const auto& oldCFaces = oldCells[celli];
+        auto& newCFaces = newCells[celli];
+
+        removed.clear();
+
+        forAll(oldCFaces, cFacei)
+        {
+            const label facei = oldCFaces[cFacei];
+            const label masteri = newCFaces[cFacei];
+
+            const face& f = mesh.faces()[facei];
+
+            forAll(oldCFaces, cFacej)
+            {
+                const label facej = oldCFaces[cFacej];
+                const label masterj = newCFaces[cFacej];
+
+                if (facej != facei)
+                {
+                    if (face::sameVertices(f, mesh.faces()[facej]))
+                    {
+                        //Pout<< "Same face:" << facei
+                        //    << " master:" << masteri
+                        //    << " verts:" << f << nl
+                        //    << "         :" << facej
+                        //    << " master:" << masterj
+                        //    << " verts:" << mesh.faces()[facej]
+                        //    << endl;
+
+                        if (masteri < masterj)
+                        {
+                            replaceAll(masterj, masteri, newCFaces);
+                            removed.append(masterj);
+                        }
+                        else if (masterj < masteri)
+                        {
+                            replaceAll(masteri, masterj, newCFaces);
+                            removed.append(masteri);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (removed.size())
+        {
+            // Compact out removed faces
+            label newi = 0;
+            for (const label facei : oldCFaces)
+            {
+                if (!removed.found(facei))
+                {
+                    newCFaces[newi++] = facei;
+                }
+            }
+            newCFaces.resize(newi);
+            ++nCellsCorrected;
+        }
+    }
+
+    // Not needed (locally)
+    if (nCellsCorrected == 0)
+    {
+        // Just use the existing mesh
+        tnewCells.cref(mesh.cells());
+    }
+
+    // Number of cells corrected (globally)
+    reduce(nCellsCorrected, sumOp<label>());
+
+    return tnewCells;
+}
+
+
+Foam::refPtr<Foam::cellList> Foam::manifoldCellsMeshObject::filter
+(
+    const polyMesh& mesh
+)
+{
+    label count = 0;
+    return filter(mesh, count);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::manifoldCellsMeshObject::manifoldCellsMeshObject(const polyMesh& mesh)
+:
+    MeshObject<polyMesh, UpdateableMeshObject, manifoldCellsMeshObject>(mesh),
+    cellsPtr_(nullptr),
+    nCorrected_(-1)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::manifoldCellsMeshObject::manifold() const
+{
+    if (nCorrected_ < 0)
+    {
+        cellsPtr_ = filter(mesh(), nCorrected_);
+    }
+
+    return (nCorrected_ > 0);
+}
+
+
+const Foam::cellList& Foam::manifoldCellsMeshObject::cells() const
+{
+    if (nCorrected_ < 0)
+    {
+        cellsPtr_ = filter(mesh(), nCorrected_);
+    }
+
+    return (cellsPtr_ ? cellsPtr_.cref() : mesh().cells());
+}
+
+
+void Foam::manifoldCellsMeshObject::updateMesh(const mapPolyMesh&)
+{
+    cellsPtr_.reset(nullptr);
+    nCorrected_ = -1;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/common/manifoldCellsMeshObject.H b/src/fileFormats/common/manifoldCellsMeshObject.H
new file mode 100644
index 0000000000000000000000000000000000000000..b063576102dfc7f301280c7420f278329e232bbf
--- /dev/null
+++ b/src/fileFormats/common/manifoldCellsMeshObject.H
@@ -0,0 +1,133 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 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::manifoldCellsMeshObject
+
+Description
+    Provides cell-to-faces ('cells()') with duplicate faces removed.
+
+    Useful for postprocessing.
+
+SourceFiles
+    manifoldCellsMeshObject.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_manifoldCellsMeshObject_H
+#define Foam_manifoldCellsMeshObject_H
+
+#include "MeshObject.H"
+#include "polyMesh.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                   Class manifoldCellsMeshObject Declaration
+\*---------------------------------------------------------------------------*/
+
+class manifoldCellsMeshObject
+:
+    public MeshObject<polyMesh, UpdateableMeshObject, manifoldCellsMeshObject>
+{
+    // Private Data
+
+        //- The adjusted cells list
+        mutable refPtr<cellList> cellsPtr_;
+
+        //- The number of globally corrected cells (-1 for uninitialized)
+        mutable label nCorrected_;
+
+
+    // Private Member Functions
+
+        //- Calculate cellList with all duplicate faces removed.
+        //- Is reference to input mesh if no duplicates
+        static refPtr<cellList> filter
+        (
+            const polyMesh& mesh,
+            label& nCellsCorrected
+        );
+
+        //- No copy construct
+        manifoldCellsMeshObject(const manifoldCellsMeshObject&) = delete;
+
+        //- No copy assignment
+        void operator=(const manifoldCellsMeshObject&) = delete;
+
+
+public:
+
+    //- Declare name of the class, no debug
+    ClassNameNoDebug("manifoldCellsMeshObject");
+
+
+    // Constructors
+
+        //- Construct from mesh
+        manifoldCellsMeshObject(const polyMesh& mesh);
+
+
+    //- Destructor
+    virtual ~manifoldCellsMeshObject() = default;
+
+
+    // Member Functions
+
+        //- Calculate cellList with all duplicate faces removed.
+        //- Is reference to input mesh if no duplicates
+        static refPtr<cellList> filter(const polyMesh& mesh);
+
+        //- True if any manifold cells detected (globally)
+        //- Triggers demand-driven filtering if required.
+        bool manifold() const;
+
+        //- Return the (optionally compacted) cell list
+        //- Triggers demand-driven filtering if required.
+        const cellList& cells() const;
+
+        //- Mesh motion
+        virtual bool movePoints()
+        {
+            return false;
+        }
+
+        //- Mesh changes
+        virtual void updateMesh(const mapPolyMesh&);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //