From 5e2d8d6ed2f190b0a29e1fb72427797c127c7421 Mon Sep 17 00:00:00 2001
From: mattijs <mattijs>
Date: Thu, 16 Jul 2020 17:05:33 +0100
Subject: [PATCH] ENH: snappyHexMesh: multi-stage layer addition, automatic
 hole closure

Automatic hole closure:
- introduces 'holeToFace' topoSet source
- used when detecting a 'leak-path'
- creates additional baffles to close the leak

Multi-stage layer addition:
- Can add layers in multiple passes

See issues: #2403, #2404
---
 .../extrude/extrudeMesh/extrudeMesh.C         |   83 +-
 etc/caseDicts/annotated/extrudeMeshDict       |    6 +-
 etc/caseDicts/annotated/topoSetSourcesDict    |   22 +
 .../primitiveMesh/PatchTools/PatchTools.H     |    8 +-
 .../PatchTools/PatchToolsNormals.C            |   22 +-
 .../polyTopoChange/addPatchCellLayer.C        |  768 ++++-
 .../polyTopoChange/addPatchCellLayer.H        |    2 +
 src/mesh/snappyHexMesh/Make/files             |    1 +
 .../meshRefinement/meshRefinement.C           |  657 ++--
 .../meshRefinement/meshRefinement.H           |  155 +-
 .../meshRefinement/meshRefinementBaffles.C    |  659 +++-
 .../meshRefinement/meshRefinementBlock.C      | 1034 +++++++
 .../meshRefinementProblemCells.C              |   61 +-
 .../meshRefinement/meshRefinementRefine.C     |    1 +
 .../refinementSurfaces/refinementSurfaces.C   |   11 +
 .../refinementSurfaces/refinementSurfaces.H   |   11 +
 .../refinementSurfaces/surfaceZonesInfo.C     |   72 +-
 .../layerParameters/layerParameters.C         |   53 +-
 .../layerParameters/layerParameters.H         |   39 +-
 .../refinementParameters.C                    |   42 +
 .../refinementParameters.H                    |    9 +
 .../snappyHexMeshDriver/snappyLayerDriver.C   | 2731 ++++++++++-------
 .../snappyHexMeshDriver/snappyLayerDriver.H   |  106 +-
 .../snappyLayerDriverSinglePass.C             |  500 +++
 .../snappyHexMeshDriver/snappyRefineDriver.C  |  122 +-
 .../snappyHexMeshDriver/snappyRefineDriver.H  |    3 +-
 .../snappyHexMeshDriver/snappySnapDriver.C    |   79 +
 .../snappyHexMeshDriver/snappySnapDriver.H    |   11 +
 src/meshTools/Make/files                      |    1 +
 src/meshTools/polyTopoChange/polyTopoChange.C |   29 +-
 src/meshTools/polyTopoChange/polyTopoChange.H |    4 +
 .../faceSources/holeToFace/holeToFace.C       | 1359 ++++++++
 .../faceSources/holeToFace/holeToFace.H       |  220 ++
 .../extrudeMesh/faceZoneExtrusion/Allrun.pre  |   25 +
 .../mesh/extrudeMesh/faceZoneExtrusion/README |    9 +
 .../constant/transportProperties              |   21 +
 .../faceZoneExtrusion/system/blockMeshDict    |   71 +
 .../faceZoneExtrusion/system/controlDict      |   49 +
 .../faceZoneExtrusion/system/decomposeParDict |   27 +
 .../faceZoneExtrusion/system/extrudeMeshDict  |   30 +
 .../system/extrudeMeshDict.flip               |   30 +
 .../faceZoneExtrusion/system/fvSchemes        |   51 +
 .../faceZoneExtrusion/system/fvSolution       |   52 +
 .../faceZoneExtrusion/system/topoSetDict      |   82 +
 .../faceZoneExtrusion/system/topoSetDict.flip |   50 +
 .../snappyHexMesh/addLayersToFaceZone/Allrun  |    2 +
 .../system/decomposeParDict                   |   22 +-
 .../addLayersToFaceZone/system/fvSolution     |    2 +-
 .../system/snappyHexMeshDict                  |   64 +-
 .../sphere_gapClosure/system/controlDict      |   54 +
 .../system/snappyHexMeshDict                  |  311 ++
 51 files changed, 8218 insertions(+), 1615 deletions(-)
 create mode 100644 src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriverSinglePass.C
 create mode 100644 src/meshTools/topoSet/faceSources/holeToFace/holeToFace.C
 create mode 100644 src/meshTools/topoSet/faceSources/holeToFace/holeToFace.H
 create mode 100755 tutorials/mesh/extrudeMesh/faceZoneExtrusion/Allrun.pre
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/README
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/constant/transportProperties
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/blockMeshDict
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/controlDict
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/decomposeParDict
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict.flip
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSchemes
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSolution
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict
 create mode 100644 tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict.flip
 create mode 100644 tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/controlDict
 create mode 100644 tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/snappyHexMeshDict

diff --git a/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C b/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C
index bdb9ce92703..680eb5815e5 100644
--- a/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C
+++ b/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2015-2020 OpenCFD Ltd.
+    Copyright (C) 2015-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -124,6 +124,38 @@ labelList patchFaces(const polyBoundaryMesh& patches, const wordList& names)
 }
 
 
+void zoneFaces
+(
+    const faceZoneMesh& fzs,
+    const wordList& names,
+    labelList& faceLabels,
+    bitSet& faceFlip
+)
+{
+    label n = 0;
+
+    forAll(names, i)
+    {
+        const auto& pp = fzs[fzs.findZoneID(names[i])];
+        n += pp.size();
+    }
+    faceLabels.setSize(n);
+    faceFlip.setSize(n);
+    n = 0;
+    forAll(names, i)
+    {
+        const auto& pp = fzs[fzs.findZoneID(names[i])];
+        const boolList& ppFlip = pp.flipMap();
+        forAll(pp, i)
+        {
+            faceLabels[n] = pp[i];
+            faceFlip[n] = ppFlip[i];
+            n++;
+        }
+    }
+}
+
+
 void updateFaceLabels(const mapPolyMesh& map, labelList& faceLabels)
 {
     const labelList& reverseMap = map.reverseFaceMap();
@@ -233,7 +265,7 @@ int main(int argc, char *argv[])
     }
     else
     {
-        regionName = polyMesh::defaultRegion;
+        regionName = fvMesh::defaultRegion;
         Info<< "Create mesh for time = "
             << runTimeExtruded.timeName() << nl << endl;
     }
@@ -317,16 +349,34 @@ int main(int argc, char *argv[])
                 sourceCaseDir/("processor" + Foam::name(Pstream::myProcNo()));
         }
         wordList sourcePatches;
-        dict.readEntry("sourcePatches", sourcePatches);
+        wordList sourceFaceZones;
+        if
+        (
+            dict.readIfPresent
+            (
+                "sourcePatches",
+                sourcePatches,
+                keyType::LITERAL
+            )
+        )
+        {
+            if (sourcePatches.size() == 1)
+            {
+                frontPatchName = sourcePatches[0];
+            }
 
-        if (sourcePatches.size() == 1)
+            Info<< "Extruding patches " << sourcePatches
+                << " on mesh " << sourceCasePath << nl
+                << endl;
+        }
+        else
         {
-            frontPatchName = sourcePatches[0];
+            dict.readEntry("sourceFaceZones", sourceFaceZones);
+            Info<< "Extruding faceZones " << sourceFaceZones
+                << " on mesh " << sourceCasePath << nl
+                << endl;
         }
 
-        Info<< "Extruding patches " << sourcePatches
-            << " on mesh " << sourceCasePath << nl
-            << endl;
 
         Time runTime
         (
@@ -344,7 +394,17 @@ int main(int argc, char *argv[])
         // creating separate mesh.
         addPatchCellLayer layerExtrude(mesh, (mode == MESH));
 
-        const labelList meshFaces(patchFaces(patches, sourcePatches));
+        labelList meshFaces;
+        bitSet faceFlips;
+        if (sourceFaceZones.size())
+        {
+            zoneFaces(mesh.faceZones(), sourceFaceZones, meshFaces, faceFlips);
+        }
+        else
+        {
+            meshFaces = patchFaces(patches, sourcePatches);
+            faceFlips.setSize(meshFaces.size());
+        }
 
         if (mode == PATCH && flipNormals)
         {
@@ -411,7 +471,7 @@ int main(int argc, char *argv[])
         // Determine extrudePatch normal
         pointField extrudePatchPointNormals
         (
-            PatchTools::pointNormals(mesh, extrudePatch)
+            PatchTools::pointNormals(mesh, extrudePatch, faceFlips)
         );
 
 
@@ -642,6 +702,7 @@ int main(int argc, char *argv[])
 
             ratio,              // expansion ratio
             extrudePatch,       // patch faces to extrude
+            faceFlips,          // side to extrude (for internal/coupled faces)
 
             edgePatchID,        // if boundary edge: patch for extruded face
             edgeZoneID,         // optional zone for extruded face
@@ -787,7 +848,7 @@ int main(int argc, char *argv[])
             (
                 IOobject
                 (
-                    polyMesh::defaultRegion,
+                    extrudedMesh::defaultRegion,
                     runTimeExtruded.constant(),
                     runTimeExtruded
                 ),
diff --git a/etc/caseDicts/annotated/extrudeMeshDict b/etc/caseDicts/annotated/extrudeMeshDict
index 422f598be09..486d8d6298d 100644
--- a/etc/caseDicts/annotated/extrudeMeshDict
+++ b/etc/caseDicts/annotated/extrudeMeshDict
@@ -24,8 +24,10 @@ constructFrom patch;
 //constructFrom surface;
 
 // If construct from patch/mesh:
-sourceCase    "<case>";
-sourcePatches (movingWall);
+sourceCase      "<case>";
+// and one of sourcePatches or sourceFaceZones (but not both):
+sourceFaceZones (someFacesZone);
+sourcePatches   (movingWall);
 
 // If construct from patch: patch to use for back (can be same as sourcePatch)
 exposedPatchName movingWall;
diff --git a/etc/caseDicts/annotated/topoSetSourcesDict b/etc/caseDicts/annotated/topoSetSourcesDict
index ce5cc7ac4b2..c1ad42711ac 100644
--- a/etc/caseDicts/annotated/topoSetSourcesDict
+++ b/etc/caseDicts/annotated/topoSetSourcesDict
@@ -384,6 +384,28 @@ faceSet_doc
     }
 
 
+    //- Faces to close connection between points in different 'zones'
+    {
+        source  holeToFace;
+        points
+        (
+            ((0.2 0.2 -10) (1.3 0.4 -0.1))  // points for zone 0
+            ((10 10 10))                    // points for zone 1
+        );
+
+        // optional blocked faces (all uncoupled boundary faces are always
+        // blocked)
+        faceSet blockedFaces;
+
+        // optional subset of cells to operate in (default is all cells)
+        cellSet candidateCells;
+
+        // optional erosion of resulting set. This does some local optimisations
+        // to minimise the set of faces
+        erode true;
+    }
+
+
     //- All faces of faceZone
     {
         source  zoneToFace;
diff --git a/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchTools.H b/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchTools.H
index 3579c952ae3..a50b0fdf326 100644
--- a/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchTools.H
+++ b/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchTools.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2020 OpenCFD Ltd.
+    Copyright (C) 2020,2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -199,7 +199,8 @@ public:
     static tmp<pointField> pointNormals
     (
         const polyMesh&,
-        const PrimitivePatch<FaceList, PointField>&
+        const PrimitivePatch<FaceList, PointField>&,
+        const bitSet& flipMap = bitSet::null()
     );
 
 
@@ -211,7 +212,8 @@ public:
         const polyMesh&,
         const PrimitivePatch<FaceList, PointField>&,
         const labelList& patchEdges,
-        const labelList& coupledEdges
+        const labelList& coupledEdges,
+        const bitSet& flipMap = bitSet::null()
     );
 
 
diff --git a/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchToolsNormals.C b/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchToolsNormals.C
index 7d14c99d7bf..f606cbc50d8 100644
--- a/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchToolsNormals.C
+++ b/src/OpenFOAM/meshes/primitiveMesh/PatchTools/PatchToolsNormals.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -38,7 +38,8 @@ Foam::tmp<Foam::pointField>
 Foam::PatchTools::pointNormals
 (
     const polyMesh& mesh,
-    const PrimitivePatch<FaceList, PointField>& p
+    const PrimitivePatch<FaceList, PointField>& p,
+    const bitSet& pFlip
 )
 {
     const globalMeshData& globalData = mesh.globalData();
@@ -72,7 +73,9 @@ Foam::PatchTools::pointNormals
                 pNormals.setSize(pFaces.size());
                 forAll(pFaces, i)
                 {
-                    pNormals[i] = p.faceNormals()[pFaces[i]];
+                    const label facei = pFaces[i];
+                    const vector& n = p.faceNormals()[facei];
+                    pNormals[i] = ((pFlip.empty() || !pFlip[facei]) ? n : -n);
                 }
             }
         }
@@ -165,7 +168,7 @@ Foam::PatchTools::pointNormals
             const vector& n = faceNormals[facei];
             forAll(f, fp)
             {
-                extrudeN[f[fp]] += n;
+                extrudeN[f[fp]] += ((pFlip.empty() || !pFlip[facei]) ? n : -n);
             }
         }
         extrudeN /= mag(extrudeN)+VSMALL;
@@ -196,7 +199,8 @@ Foam::PatchTools::edgeNormals
     const polyMesh& mesh,
     const PrimitivePatch<FaceList, PointField>& p,
     const labelList& patchEdges,
-    const labelList& coupledEdges
+    const labelList& coupledEdges,
+    const bitSet& pFlip
 )
 {
     // 1. Start off with local normals
@@ -213,7 +217,13 @@ Foam::PatchTools::edgeNormals
             const labelList& eFaces = edgeFaces[edgei];
             for (const label facei : eFaces)
             {
-                edgeNormals[edgei] += faceNormals[facei];
+                const vector& n = faceNormals[facei];
+                edgeNormals[edgei] +=
+                (
+                    (pFlip.empty() || !pFlip[facei])
+                  ? n
+                  : -n
+                );
             }
         }
         edgeNormals /= mag(edgeNormals)+VSMALL;
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C
index f2eda1b767d..b0efd6df4b9 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C
@@ -45,6 +45,40 @@ License
 namespace Foam
 {
     defineTypeNameAndDebug(addPatchCellLayer, 0);
+
+    // Reduction class to get minimum value over face.
+    class minEqOpFace
+    {
+    public:
+
+        void operator()(face& x, const face& y) const
+        {
+            if (x.size())
+            {
+                if (y.size())
+                {
+                    label j = 0;
+                    forAll(x, i)
+                    {
+                        x[i] = min(x[i], y[j]);
+
+                        j = y.rcIndex(j);
+                    }
+                }
+            }
+            else if (y.size())
+            {
+                x.setSize(y.size());
+                label j = 0;
+                forAll(x, i)
+                {
+                    x[i] = y[j];
+                    j = y.rcIndex(j);
+                }
+            }
+        }
+    };
+
 }
 
 
@@ -300,6 +334,8 @@ Foam::label Foam::addPatchCellLayer::addSideFace
 
 
         //Pout<< "Added boundary face:" << newFace
+        //    << " atfc:" << newFace.centre(meshMod.points())
+        //    << " n:" << newFace.unitNormal(meshMod.points())
         //    << " own:" << addedCells[ownFacei][layerOwn]
         //    << " patch:" << newPatchID
         //    << endl;
@@ -405,6 +441,8 @@ Foam::label Foam::addPatchCellLayer::addSideFace
         );
 
         //Pout<< "Added internal face:" << newFace
+        //    << " atfc:" << newFace.centre(meshMod.points())
+        //    << " n:" << newFace.unitNormal(meshMod.points())
         //    << " own:" << addedCells[ownFacei][layerOwn]
         //    << " nei:" << addedCells[nbrFacei][layerNbr]
         //    << endl;
@@ -920,36 +958,37 @@ void Foam::addPatchCellLayer::globalEdgeInfo
 
                                     meshEdgeToFace[edgei] =
                                         globalFaces.toGlobal(facei);
+                                }
+
+                                // Override any patch info. Note that
+                                // meshEdgeToFace might be an internal face.
+                                if (meshEdgeToPatch[edgei] == -1)
+                                {
+                                    meshEdgeToPatch[edgei] = pp.index();
+                                }
 
-                                    // Override any patch info
-                                    if (meshEdgeToPatch[edgei] == -1)
+                                // Override any zone info
+                                if (meshEdgeToZone[edgei] == -1)
+                                {
+                                    meshEdgeToZone[edgei] =
+                                        faceToZone[facei];
+                                    const edge& meshE = mesh.edges()[edgei];
+                                    const int d = edge::compare(e, meshE);
+                                    if (d == 1)
                                     {
-                                        meshEdgeToPatch[edgei] = pp.index();
+                                        meshEdgeToFlip[edgei] =
+                                            faceToFlip[facei];
                                     }
-
-                                    // Override any zone info
-                                    if (meshEdgeToZone[edgei] == -1)
+                                    else if (d == -1)
+                                    {
+                                        meshEdgeToFlip[edgei] =
+                                           !faceToFlip[facei];
+                                    }
+                                    else
                                     {
-                                        meshEdgeToZone[edgei] =
-                                            faceToZone[facei];
-                                        const edge& meshE = mesh.edges()[edgei];
-                                        const int d = edge::compare(e, meshE);
-                                        if (d == 1)
-                                        {
-                                            meshEdgeToFlip[edgei] =
-                                                faceToFlip[facei];
-                                        }
-                                        else if (d == -1)
-                                        {
-                                            meshEdgeToFlip[edgei] =
-                                               !faceToFlip[facei];
-                                        }
-                                        else
-                                        {
-                                            FatalErrorInFunction
-                                                << "big problem"
-                                                << exit(FatalError);
-                                        }
+                                        FatalErrorInFunction
+                                            << "big problem"
+                                            << exit(FatalError);
                                     }
                                 }
                             }
@@ -962,6 +1001,7 @@ void Foam::addPatchCellLayer::globalEdgeInfo
         }
     }
 
+
     // Synchronise across coupled edges. Max patch/face/faceZone wins
     syncTools::syncEdgeList
     (
@@ -1420,6 +1460,7 @@ void Foam::addPatchCellLayer::setRefinement
     const labelListList& globalEdgeFaces,
     const scalarField& expansionRatio,
     const indirectPrimitivePatch& pp,
+    const bitSet& ppFlip,
 
     const labelList& edgePatchID,
     const labelList& edgeZoneID,
@@ -1446,6 +1487,7 @@ void Foam::addPatchCellLayer::setRefinement
         pp.nPoints() != firstLayerDisp.size()
      || pp.nPoints() != nPointLayers.size()
      || pp.size() != nFaceLayers.size()
+     || pp.size() != ppFlip.size()
     )
     {
         FatalErrorInFunction
@@ -1455,9 +1497,89 @@ void Foam::addPatchCellLayer::setRefinement
             << "  displacement:" << firstLayerDisp.size()
             << "  nPointLayers:" << nPointLayers.size() << nl
             << " patch.nFaces:" << pp.size()
+            << " flip map:" << ppFlip.size()
             << "  nFaceLayers:" << nFaceLayers.size()
             << abort(FatalError);
     }
+    if (!addToMesh_)
+    {
+        // flip map should be false
+        if (ppFlip.count())
+        {
+            FatalErrorInFunction
+                << "In generating stand-alone mesh the flip map should be empty"
+                << ". Instead it is " << ppFlip.count()
+                << abort(FatalError);
+        }
+    }
+    else
+    {
+        // Maybe check for adding to neighbour of boundary faces? How about
+        // coupled faces where the faceZone flipMap is negated
+
+        // For all boundary faces:
+        //  -1 : not extruded
+        //   0 : extruded from owner outwards (flip = false)
+        //   1 : extrude from neighbour outwards
+        labelList stateAndFlip(mesh_.nBoundaryFaces(), 0);
+        forAll(pp.addressing(), patchFacei)
+        {
+            if (nFaceLayers[patchFacei] > 0)
+            {
+                const label meshFacei = pp.addressing()[patchFacei];
+                const label bFacei = meshFacei-mesh_.nInternalFaces();
+                if (bFacei >= 0)
+                {
+                    stateAndFlip[bFacei] = label(ppFlip[patchFacei]);
+                }
+            }
+        }
+        // Make sure uncoupled patches do not trigger the error below
+        for (const auto& patch : mesh_.boundaryMesh())
+        {
+            if (!patch.coupled())
+            {
+                forAll(patch, i)
+                {
+                    label& state = stateAndFlip[patch.offset()+i];
+                    state = (state == 0 ? 1 : 0);
+                }
+            }
+        }
+        syncTools::swapBoundaryFaceList(mesh_, stateAndFlip);
+
+        forAll(pp.addressing(), patchFacei)
+        {
+            if (nFaceLayers[patchFacei] > 0)
+            {
+                const label meshFacei = pp.addressing()[patchFacei];
+                const label bFacei = meshFacei-mesh_.nInternalFaces();
+                if (bFacei >= 0)
+                {
+                    if (stateAndFlip[bFacei] == -1)
+                    {
+                        FatalErrorInFunction
+                            << "At extruded face:" << meshFacei
+                            << " at:" << mesh_.faceCentres()[meshFacei]
+                            << " locally have nLayers:"
+                            << nFaceLayers[patchFacei]
+                            << " but remotely have none" << exit(FatalError);
+                    }
+                    else if (stateAndFlip[bFacei] == label(ppFlip[patchFacei]))
+                    {
+                        FatalErrorInFunction
+                            << "At extruded face:" << meshFacei
+                            << " at:" << mesh_.faceCentres()[meshFacei]
+                            << " locally have flip:" << ppFlip[patchFacei]
+                            << " which is not the opposite of coupled version "
+                            << stateAndFlip[bFacei]
+                            << exit(FatalError);
+                    }
+                }
+            }
+        }
+    }
+
 
     forAll(nPointLayers, i)
     {
@@ -1503,6 +1625,19 @@ void Foam::addPatchCellLayer::setRefinement
 
     const labelList& meshPoints = pp.meshPoints();
 
+
+    // Determine which points are on which side of the extrusion
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    bitSet isBlockedFace(mesh_.nFaces());
+    forAll(nFaceLayers, patchFacei)
+    {
+        if (nFaceLayers[patchFacei] > 0)
+        {
+            isBlockedFace.set(pp.addressing()[patchFacei]);
+        }
+    }
+
     // Some storage for edge-face-addressing.
     DynamicList<label> ef;
 
@@ -1636,49 +1771,51 @@ void Foam::addPatchCellLayer::setRefinement
                         << abort(FatalError);
                 }
 
-                label myFacei = pp.addressing()[eFaces[0]];
-
-                label meshEdgei = meshEdges[edgei];
-
-                // Mesh faces using edge
-                const labelList& meshFaces = mesh_.edgeFaces(meshEdgei, ef);
-
-                // Check that there is only one patchface using edge.
-                const polyBoundaryMesh& patches = mesh_.boundaryMesh();
-
-                label bFacei = -1;
-
-                forAll(meshFaces, i)
-                {
-                    label facei = meshFaces[i];
-
-                    if (facei != myFacei)
-                    {
-                        if (!mesh_.isInternalFace(facei))
-                        {
-                            if (bFacei == -1)
-                            {
-                                bFacei = facei;
-                            }
-                            else
-                            {
-                                FatalErrorInFunction
-                                    << "boundary-edge-to-be-extruded:"
-                                    << pp.points()[meshPoints[e[0]]]
-                                    << pp.points()[meshPoints[e[1]]]
-                                    << " has more than two boundary faces"
-                                    << " using it:"
-                                    << bFacei << " fc:"
-                                    << mesh_.faceCentres()[bFacei]
-                                    << " patch:" << patches.whichPatch(bFacei)
-                                    << " and " << facei << " fc:"
-                                    << mesh_.faceCentres()[facei]
-                                    << " patch:" << patches.whichPatch(facei)
-                                    << abort(FatalError);
-                            }
-                        }
-                    }
-                }
+                //label myFacei = pp.addressing()[eFaces[0]];
+                //
+                //label meshEdgei = meshEdges[edgei];
+                //
+                //// Mesh faces using edge
+                //const labelList& meshFaces = mesh_.edgeFaces(meshEdgei, ef);
+                //
+                //// Check that there is only one patchface using edge.
+                //const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+                //
+                //label bFacei = -1;
+                //
+                //forAll(meshFaces, i)
+                //{
+                //    label facei = meshFaces[i];
+                //
+                //    if (facei != myFacei)
+                //    {
+                //        if (!mesh_.isInternalFace(facei))
+                //        {
+                //            if (bFacei == -1)
+                //            {
+                //                bFacei = facei;
+                //            }
+                //            else
+                //            {
+                //                //FatalErrorInFunction
+                //                WarningInFunction
+                //                    << "boundary-edge-to-be-extruded:"
+                //                    << pp.points()[meshPoints[e[0]]]
+                //                    << pp.points()[meshPoints[e[1]]]
+                //                    << " has more than one boundary faces"
+                //                    << " using it:"
+                //                    << bFacei << " fc:"
+                //                    << mesh_.faceCentres()[bFacei]
+                //                    << " patch:" << patches.whichPatch(bFacei)
+                //                    << " and " << facei << " fc:"
+                //                    << mesh_.faceCentres()[facei]
+                //                    << " patch:" << patches.whichPatch(facei)
+                //                    << endl;
+                //                    //abort(FatalError);
+                //            }
+                //        }
+                //    }
+                //}
             }
         }
     }
@@ -1723,6 +1860,198 @@ void Foam::addPatchCellLayer::setRefinement
     }
 
 
+    // Store per face whether it uses the duplicated point or the original one
+    // Also mark any affected cells. We could transport the duplicated point
+    // itself but since it is a processor-local index only we only transport
+    // a boolean.
+
+    // Per face, per index in face either labelMax or a valid index. Note:
+    // most faces are not affected in which case the face will be zero size
+    // and only have a nullptr and a size.
+    faceList baseFaces(mesh_.nFaces());
+    bitSet isAffectedCell(mesh_.nCells());
+    {
+        const faceList& localFaces = pp.localFaces();
+        forAll(localFaces, patchFacei)
+        {
+            const face& f = localFaces[patchFacei];
+            forAll(f, fp)
+            {
+                const label patchPointi = f[fp];
+                if (nPointLayers[patchPointi] > 0)
+                {
+                    const label meshFacei = pp.addressing()[patchFacei];
+                    face& baseF = baseFaces[meshFacei];
+                    // Initialise to labelMax if not yet sized
+                    baseF.setSize(f.size(), labelMax);
+                    baseF[fp] = pp.meshPoints()[patchPointi];
+
+                    if (ppFlip[patchFacei])
+                    {
+                        // Neighbour stays. Affected points on the owner side.
+                        const label celli = mesh_.faceOwner()[meshFacei];
+                        isAffectedCell.set(celli);
+                    }
+                    else if (mesh_.isInternalFace(meshFacei))
+                    {
+                        // Owner unaffected. Unaffected points on neighbour side
+                        const label celli = mesh_.faceNeighbour()[meshFacei];
+                        isAffectedCell.set(celli);
+                    }
+                }
+            }
+        }
+    }
+
+    // Transport affected side across faces. Could do across edges: say we have
+    // a loose cell edge-(but not face-)connected to face-to-be-extruded do
+    // we want it to move with the extrusion or stay connected to the original?
+    // For now just keep it connected to the original.
+    {
+        // Work space
+        Map<label> minPointValue(128);
+        faceList oldBoundaryFaces(mesh_.nBoundaryFaces());
+
+        while (true)
+        {
+            bitSet newIsAffectedCell(mesh_.nCells());
+
+            label nChanged = 0;
+            for (const label celli : isAffectedCell)
+            {
+                const cell& cFaces = mesh_.cells()[celli];
+
+                // 1. Determine marked base points. Inside a single cell all
+                //    faces use the same 'instance' of a point.
+                minPointValue.clear();
+                for (const label facei : cFaces)
+                {
+                    const face& baseF = baseFaces[facei];
+                    const face& f = mesh_.faces()[facei];
+
+                    if (baseF.size())
+                    {
+                        forAll(f, fp)
+                        {
+                            if (baseF[fp] != labelMax)
+                            {
+                                // Could check here for inconsistent patchPoint
+                                // e.g. cell using both sides of a
+                                // face-to-be-extruded. Is not possible!
+                                minPointValue.insert(f[fp], baseF[fp]);
+                            }
+                        }
+                    }
+                }
+
+                //Pout<< "For cell:" << celli
+                //    << " at:" << mesh_.cellCentres()[celli]
+                //    << " have minPointValue:" << minPointValue
+                //    << endl;
+
+                // 2. Transport marked points on all cell points
+                for (const label facei : cFaces)
+                {
+                    const face& f = mesh_.faces()[facei];
+                    face& baseF = baseFaces[facei];
+
+                    const label oldNChanged = nChanged;
+                    forAll(f, fp)
+                    {
+                        const auto fnd = minPointValue.find(f[fp]);
+                        if (fnd.found())
+                        {
+                            baseF.setSize(f.size(), labelMax);
+                            if (baseF[fp] == labelMax)
+                            {
+                                baseF[fp] = fnd();
+                                nChanged++;
+
+                                //Pout<< "For cell:" << celli
+                                //    << " at:" << mesh_.cellCentres()[celli]
+                                //    << " on face:" << facei
+                                //    << " points:"
+                                //    << UIndirectList<point>(mesh_.points(), f)
+                                //    << " now have baseFace:" << baseF
+                                //    << endl;
+                            }
+                        }
+                    }
+
+                    if (!isBlockedFace(facei) && nChanged > oldNChanged)
+                    {
+                        // Mark neighbouring cells
+                        const label own = mesh_.faceOwner()[facei];
+                        if (!isAffectedCell[own])
+                        {
+                            newIsAffectedCell.set(own);
+                        }
+                        if (mesh_.isInternalFace(facei))
+                        {
+                            const label nei = mesh_.faceNeighbour()[facei];
+                            if (!isAffectedCell[nei])
+                            {
+                                newIsAffectedCell.set(nei);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (debug)
+            {
+                Pout<< "isAffectedCell:" << isAffectedCell.count() << endl;
+                Pout<< "newIsAffectedCell:" << newIsAffectedCell.count()
+                    << endl;
+                Pout<< "nChanged:" << nChanged << endl;
+            }
+
+            if (returnReduce(nChanged, sumOp<label>()) == 0)
+            {
+                break;
+            }
+
+
+            // Transport minimum across coupled faces
+            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+            SubList<face> l
+            (
+                baseFaces,
+                mesh_.nBoundaryFaces(),
+                mesh_.nInternalFaces()
+            );
+            oldBoundaryFaces = l;
+            syncTools::syncBoundaryFaceList
+            (
+                mesh_,
+                l,
+                minEqOpFace(),
+                Foam::dummyTransform()  // dummy transformation
+            );
+
+            forAll(l, bFacei)
+            {
+                // Note: avoid special handling of comparing zero-sized faces
+                //       (see face::operator==). Review.
+                const labelUList& baseVts = l[bFacei];
+                const labelUList& oldVts = oldBoundaryFaces[bFacei];
+                if (baseVts != oldVts)
+                {
+                    const label facei = mesh_.nInternalFaces()+bFacei;
+                    const label own = mesh_.faceOwner()[facei];
+                    if (!isAffectedCell[own])
+                    {
+                        newIsAffectedCell.set(own);
+                    }
+                }
+            }
+
+            isAffectedCell = newIsAffectedCell;
+        }
+    }
+
+
     //
     // Create new points
     //
@@ -1734,18 +2063,21 @@ void Foam::addPatchCellLayer::setRefinement
         copiedPatchPoints.setSize(firstLayerDisp.size());
         forAll(firstLayerDisp, patchPointi)
         {
-            label meshPointi = meshPoints[patchPointi];
-            label zoneI = mesh_.pointZones().whichZone(meshPointi);
-            copiedPatchPoints[patchPointi] = meshMod.setAction
-            (
-                polyAddPoint
+            if (addedPoints_[patchPointi].size())
+            {
+                label meshPointi = meshPoints[patchPointi];
+                label zoneI = mesh_.pointZones().whichZone(meshPointi);
+                copiedPatchPoints[patchPointi] = meshMod.setAction
                 (
-                    mesh_.points()[meshPointi],         // point
-                    -1,         // master point
-                    zoneI,      // zone for point
-                    true        // supports a cell
-                )
-            );
+                    polyAddPoint
+                    (
+                        mesh_.points()[meshPointi],         // point
+                        -1,         // master point
+                        zoneI,      // zone for point
+                        true        // supports a cell
+                    )
+                );
+            }
         }
     }
 
@@ -1755,9 +2087,8 @@ void Foam::addPatchCellLayer::setRefinement
     {
         if (addedPoints_[patchPointi].size())
         {
-            label meshPointi = meshPoints[patchPointi];
-
-            label zoneI = mesh_.pointZones().whichZone(meshPointi);
+            const label meshPointi = meshPoints[patchPointi];
+            const label zoneI = mesh_.pointZones().whichZone(meshPointi);
 
             point pt = mesh_.points()[meshPointi];
 
@@ -1767,7 +2098,7 @@ void Foam::addPatchCellLayer::setRefinement
             {
                 pt += disp;
 
-                label addedVertI = meshMod.setAction
+                const label addedVertI = meshMod.setAction
                 (
                     polyAddPoint
                     (
@@ -1778,6 +2109,12 @@ void Foam::addPatchCellLayer::setRefinement
                     )
                 );
 
+
+                //Pout<< "Adding point:" << addedVertI << " at:" << pt
+                //    << " from point:" << meshPointi
+                //    << " at:" << mesh_.points()[meshPointi]
+                //    << endl;
+
                 addedPoints_[patchPointi][i] = addedVertI;
 
                 disp *= expansionRatio[patchPointi];
@@ -1796,31 +2133,56 @@ void Foam::addPatchCellLayer::setRefinement
     {
         if (nFaceLayers[patchFacei] > 0)
         {
-            addedCells[patchFacei].setSize(nFaceLayers[patchFacei]);
-
-            label meshFacei = pp.addressing()[patchFacei];
+            const label meshFacei = pp.addressing()[patchFacei];
 
-            label ownZoneI = mesh_.cellZones().whichZone
-            (
-                mesh_.faceOwner()[meshFacei]
-            );
+            label extrudeCelli = -2;
+            label extrudeZonei;
+            if (!addToMesh_)
+            {
+                extrudeCelli = -1;
+                const label ownCelli = mesh_.faceOwner()[meshFacei];
+                extrudeZonei = mesh_.cellZones().whichZone(ownCelli);
+            }
+            else if (!ppFlip[patchFacei])
+            {
+                // Normal: extrude from owner face
+                extrudeCelli = mesh_.faceOwner()[meshFacei];
+                extrudeZonei = mesh_.cellZones().whichZone(extrudeCelli);
+            }
+            else if (mesh_.isInternalFace(meshFacei))
+            {
+                // Extrude from neighbour face (if internal). Might be
+                // that it is a coupled face and the other side is
+                // extruded
+                extrudeCelli = mesh_.faceNeighbour()[meshFacei];
+                extrudeZonei = mesh_.cellZones().whichZone(extrudeCelli);
+            }
 
-            for (label i = 0; i < nFaceLayers[patchFacei]; i++)
+            if (extrudeCelli != -2)
             {
-                // Note: add from cell (owner of patch face) or from face?
-                // for now add from cell so we can map easily.
-                addedCells[patchFacei][i] = meshMod.setAction
-                (
-                    polyAddCell
+                addedCells[patchFacei].setSize(nFaceLayers[patchFacei]);
+
+                for (label i = 0; i < nFaceLayers[patchFacei]; i++)
+                {
+                    // Note: add from cell (owner of patch face) or from face?
+                    // for now add from cell so we can map easily.
+                    addedCells[patchFacei][i] = meshMod.setAction
                     (
-                        -1,             // master point
-                        -1,             // master edge
-                        -1,             // master face
-                        (addToMesh_ ? mesh_.faceOwner()[meshFacei] : -1),
-                                        //master
-                        ownZoneI        // zone for cell
-                    )
-                );
+                        polyAddCell
+                        (
+                            -1,             // master point
+                            -1,             // master edge
+                            -1,             // master face
+                            extrudeCelli,   // master cell
+                            extrudeZonei    // zone for cell
+                        )
+                    );
+
+                    //Pout<< "Added cell:" << addedCells[patchFacei][i]
+                    //    << " from master:" << extrudeCelli
+                    //    << " at:" << mesh_.cellCentres()[extrudeCelli]
+                    //    << endl;
+                }
             }
         }
     }
@@ -1870,20 +2232,50 @@ void Foam::addPatchCellLayer::setRefinement
                         newFace[fp] = addedPoints_[f[fp]][i+offset];
                     }
                 }
-
+                //Pout<< "   newFace:" << newFace << endl;
+                //Pout<< "   coords:"
+                //    << UIndirectList<point>(meshMod.points(), newFace)
+                //    << " normal:" << newFace.unitNormal(meshMod.points())
+                //    << endl;
 
                 // Get new neighbour
+                label own = addedCells[patchFacei][i];
                 label nei;
                 label patchi;
                 label zoneI = -1;
                 bool flip = false;
-
+                bool fluxFlip = false;
 
                 if (i == addedCells[patchFacei].size()-1)
                 {
-                    // Top layer so is patch face.
-                    nei = -1;
+                    // Top layer so is either patch face or connects to
+                    // the other cell
                     patchi = patchID[patchFacei];
+                    if (patchi == -1)
+                    {
+                        // Internal face
+                        nei =
+                        (
+                           !ppFlip[patchFacei]
+                          ? mesh_.faceNeighbour()[meshFacei]
+                          : mesh_.faceOwner()[meshFacei]
+                        );
+
+                        if (ppFlip[patchFacei])
+                        {
+                            newFace = newFace.reverseFace();
+                        }
+                        //Pout<< "** adding top (internal) face:"
+                        //    << " at:" << mesh_.faceCentres()[meshFacei]
+                        //   <<  " own:" << own << " nei:" << nei
+                        //    << " patchi:" << patchi
+                        //    << " newFace:" << newFace
+                        //    << endl;
+                    }
+                    else
+                    {
+                        nei = -1;
+                    }
                     zoneI = mesh_.faceZones().whichZone(meshFacei);
                     if (zoneI != -1)
                     {
@@ -1898,29 +2290,55 @@ void Foam::addPatchCellLayer::setRefinement
                     patchi = -1;
                 }
 
+                if (nei != -1 && nei < own)
+                {
+                    // Wrongly oriented internal face
+                    newFace = newFace.reverseFace();
+                    std::swap(own, nei);
+                    flip = !flip;
+                    fluxFlip = true;
+
+                    //Pout<< "Flipped newFace:"
+                    //    << newFace.unitNormal(meshMod.points())
+                    //    << " own:" << own
+                    //    << " nei:" << nei
+                    //    << endl;
+                }
+
+
 
                 layerFaces_[patchFacei][i+1] = meshMod.setAction
                 (
                     polyAddFace
                     (
                         newFace,                    // face
-                        addedCells[patchFacei][i],  // owner
+                        own,                        // owner
                         nei,                        // neighbour
                         -1,                         // master point
                         -1,                         // master edge
                         (addToMesh_ ? meshFacei : -1), // master face
-                        false,                      // flux flip
+                        fluxFlip,                   // flux flip
                         patchi,                     // patch for face
                         zoneI,                      // zone for face
                         flip                        // face zone flip
                     )
                 );
+
+                //Pout<< "added layer face:" << layerFaces_[patchFacei][i+1]
+                //    << " verts:" << newFace
+                //    << " newFc:" << newFace.centre(meshMod.points())
+                //    << " originalFc:" << mesh_.faceCentres()[meshFacei]
+                //    << nl
+                //    << "     n:" << newFace.unitNormal(meshMod.points())
+                //    << " own:" << own << " nei:" << nei
+                //    << " patchi:" << patchi
+                //    << endl;
             }
         }
     }
 
     //
-    // Modify old patch faces to be on the inside
+    // Modify owner faces to have addedCells as neighbour
     //
 
     if (addToMesh_)
@@ -1932,22 +2350,39 @@ void Foam::addPatchCellLayer::setRefinement
                 label meshFacei = pp.addressing()[patchFacei];
 
                 layerFaces_[patchFacei][0] = meshFacei;
+                const face& f = pp[patchFacei];
+
+                const label own =
+                (
+                    !ppFlip[patchFacei]
+                  ? mesh_.faceOwner()[meshFacei]
+                  : mesh_.faceNeighbour()[meshFacei]
+                );
+                const label nei = addedCells[patchFacei][0];
 
                 meshMod.setAction
                 (
                     polyModifyFace
                     (
-                        pp[patchFacei],                 // modified face
+                        (ppFlip[patchFacei] ? f.reverseFace() : f),// verts
                         meshFacei,                      // label of face
-                        mesh_.faceOwner()[meshFacei],   // owner
-                        addedCells[patchFacei][0],      // neighbour
-                        false,                          // face flip
+                        own,                            // owner
+                        nei,                            // neighbour
+                        ppFlip[patchFacei],             // face flip
                         -1,                             // patch for face
                         true, //false,                  // remove from zone
                         -1, //zoneI,                    // zone for face
                         false                           // face flip in zone
                     )
                 );
+
+                //Pout<< "Modified bottom face " << meshFacei
+                //    << " at:" << mesh_.faceCentres()[meshFacei]
+                //    << " new own:" << own << " new nei:" << nei
+                //    << " verts:" << meshMod.faces()[meshFacei]
+                //    << " n:"
+                //    << meshMod.faces()[meshFacei].unitNormal(meshMod.points())
+                //    << endl;
             }
         }
     }
@@ -1957,7 +2392,7 @@ void Foam::addPatchCellLayer::setRefinement
         // in the exposed patch ID.
         forAll(pp, patchFacei)
         {
-            if (nFaceLayers[patchFacei] > 0)
+            if (addedCells[patchFacei].size())
             {
                 label meshFacei = pp.addressing()[patchFacei];
                 label zoneI = mesh_.faceZones().whichZone(meshFacei);
@@ -2283,6 +2718,13 @@ void Foam::addPatchCellLayer::setRefinement
 
                         newFace.setSize(newFp);
 
+                        // Walked edges as if owner face was extruded. Reverse
+                        // for neighbour face extrusion.
+                        if (ppFlip[patchFacei])
+                        {
+                            newFace = newFace.reverseFace();
+                        }
+
                         if (debug)
                         {
                             labelHashSet verts(2*newFace.size());
@@ -2372,6 +2814,98 @@ void Foam::addPatchCellLayer::setRefinement
             }
         }
     }
+
+
+    // Adjust side faces if they're on the side where points were duplicated
+    // (i.e. adding to internal faces). Like duplicatePoints::setRefinement.
+    if (addToMesh_)
+    {
+        face newFace;
+
+        forAll(baseFaces, facei)
+        {
+            const face& f = mesh_.faces()[facei];
+            const face& baseF = baseFaces[facei];
+
+            if (isBlockedFace(facei) || baseF.empty())
+            {
+                // Either part of patch or no duplicated points on face
+                continue;
+            }
+
+            // Start off from original face
+            newFace = f;
+            forAll(f, fp)
+            {
+                const label meshPointi = f[fp];
+                if (baseF[fp] != labelMax)
+                {
+                    // Duplicated point
+                    const label patchPointi = pp.meshPointMap()[meshPointi];
+                    const label addedPointi = addedPoints_[patchPointi].last();
+
+                    //Pout<< "    For point:" << meshPointi
+                    //    << " at:" << mesh_.points()[meshPointi]
+                    //    << " at:" << pp.localPoints()[patchPointi]
+                    //    << " using addedpoint:" << addedPointi
+                    //    << " at:" << meshMod.points()[addedPointi]
+                    //    << endl;
+
+                    newFace[fp] = addedPointi;
+                }
+            }
+
+            //Pout<< "for face:" << facei << nl
+            //    << "    old:" << f
+            //    << " n:" << newFace.unitNormal(meshMod.points())
+            //    << " coords:" << UIndirectList<point>(mesh_.points(), f)
+            //    << nl
+            //    << "    new:" << newFace
+            //    << " n:" << newFace.unitNormal(meshMod.points())
+            //    << " coords:"
+            //    << UIndirectList<point>(meshMod.points(), newFace)
+            //    << endl;
+
+            // Get current zone info
+            label zoneID = mesh_.faceZones().whichZone(facei);
+            bool zoneFlip = false;
+            if (zoneID >= 0)
+            {
+                const faceZone& fZone = mesh_.faceZones()[zoneID];
+                zoneFlip = fZone.flipMap()[fZone.whichFace(facei)];
+            }
+
+
+            if (mesh_.isInternalFace(facei))
+            {
+                meshMod.modifyFace
+                (
+                    newFace,                    // modified face
+                    facei,                      // label of modified face
+                    mesh_.faceOwner()[facei],   // owner
+                    mesh_.faceNeighbour()[facei],   // neighbour
+                    false,                      // face flip
+                    -1,                         // patch for face
+                    zoneID,                     // zone for face
+                    zoneFlip                    // face flip in zone
+                );
+            }
+            else
+            {
+                meshMod.modifyFace
+                (
+                    newFace,                    // modified face
+                    facei,                      // label of modified face
+                    mesh_.faceOwner()[facei],   // owner
+                    -1,                         // neighbour
+                    false,                      // face flip
+                    patches.whichPatch(facei),  // patch for face
+                    zoneID,                     // zone for face
+                    zoneFlip                    // face flip in zone
+                );
+            }
+        }
+    }
 }
 
 
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H
index fac5288dad4..b0d12af7ed6 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H
@@ -399,6 +399,7 @@ public:
                 const labelListList& globalEdgeFaces,
                 const scalarField& expansionRatio,
                 const indirectPrimitivePatch& pp,
+                const bitSet& flip,
 
                 const labelList& sidePatchID,
                 const labelList& sideZoneID,
@@ -431,6 +432,7 @@ public:
                     globalEdgeFaces,
                     scalarField(pp.nPoints(), 1.0),     // expansion ration
                     pp,
+                    bitSet(pp.size()),                  // flip
 
                     sidePatchID,
                     labelList(pp.nEdges(), -1),         // zoneID
diff --git a/src/mesh/snappyHexMesh/Make/files b/src/mesh/snappyHexMesh/Make/files
index d29863fbc00..28be73f7c19 100644
--- a/src/mesh/snappyHexMesh/Make/files
+++ b/src/mesh/snappyHexMesh/Make/files
@@ -1,4 +1,5 @@
 snappyHexMeshDriver/snappyLayerDriver.C
+snappyHexMeshDriver/snappyLayerDriverSinglePass.C
 snappyHexMeshDriver/snappySnapDriver.C
 snappyHexMeshDriver/snappySnapDriverFeature.C
 snappyHexMeshDriver/snappyRefineDriver.C
diff --git a/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C b/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C
index 7064665f442..d9e7af0d7ef 100644
--- a/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C
+++ b/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.C
@@ -61,6 +61,7 @@ License
 #include "faceSet.H"
 #include "topoDistanceData.H"
 #include "FaceCellWave.H"
+#include "PackedBoolList.H"
 
 // Leak path
 #include "shortestPathSet.H"
@@ -436,9 +437,107 @@ void Foam::meshRefinement::updateIntersections(const labelList& changedFaces)
 }
 
 
-Foam::labelList Foam::meshRefinement::nearestPatch
+void Foam::meshRefinement::nearestFace
 (
-    const labelList& adaptPatchIDs
+    const labelUList& startFaces,
+    const bitSet& isBlockedFace,
+    
+    autoPtr<mapDistribute>& mapPtr,
+    labelList& faceToStart,
+    const label nIter
+) const
+{
+    // From startFaces walk out (but not through isBlockedFace). Returns
+    // faceToStart which is the index into startFaces (or rather distributed
+    // version of it). E.g.
+    // pointField startFc(mesh.faceCentres(), startFaces);
+    // mapPtr().distribute(startFc);
+    // forAll(faceToStart, facei)
+    // {
+    //    const label sloti = faceToStart[facei];
+    //    if (sloti != -1)
+    //    {
+    //        Pout<< "face:" << mesh.faceCentres()[facei]
+    //            << " nearest:" << startFc[sloti]
+    //            << endl;
+    //    }
+    // }
+
+
+    // Consecutive global numbering for start elements
+    const globalIndex globalStart(startFaces.size());
+
+    // Field on cells and faces.
+    List<topoDistanceData<label>> cellData(mesh_.nCells());
+    List<topoDistanceData<label>> faceData(mesh_.nFaces());
+
+    // Mark blocked faces to there not visited
+    for (const label facei : isBlockedFace)
+    {
+        faceData[facei] = topoDistanceData<label>(0, -1);
+    }
+
+    List<topoDistanceData<label>> startData(startFaces.size());
+    forAll(startFaces, i)
+    {
+        const label facei = startFaces[i];
+        if (isBlockedFace[facei])
+        {
+            FatalErrorInFunction << "Start face:" << facei
+                << " at:" << mesh_.faceCentres()[facei]
+                << " is also blocked" << exit(FatalError);
+        }
+        startData[i] = topoDistanceData<label>(0, globalStart.toGlobal(i));
+    }
+
+
+    // Propagate information inwards
+    FaceCellWave<topoDistanceData<label>> deltaCalc
+    (
+        mesh_,
+        startFaces,
+        startData,
+        faceData,
+        cellData,
+        0
+    );
+    deltaCalc.iterate(nIter);
+
+    // And extract
+
+    faceToStart.setSize(mesh_.nFaces());
+    faceToStart = -1;
+    bool haveWarned = false;
+    forAll(faceData, facei)
+    {
+        if (!faceData[facei].valid(deltaCalc.data()))
+        {
+            if (!haveWarned)
+            {
+                WarningInFunction
+                    << "Did not visit some faces, e.g. face " << facei
+                    << " at " << mesh_.faceCentres()[facei]
+                    << endl;
+                haveWarned = true;
+            }
+        }
+        else
+        {
+            faceToStart[facei] = faceData[facei].data();
+        }
+    }
+
+    // Compact
+    List<Map<label>> compactMap;
+    mapPtr.reset(new mapDistribute(globalStart, faceToStart, compactMap));
+}
+
+
+void Foam::meshRefinement::nearestPatch
+(
+    const labelList& adaptPatchIDs,
+    labelList& nearestPatch,
+    labelList& nearestZone
 ) const
 {
     // Determine nearest patch for all mesh faces. Used when removing cells
@@ -446,11 +545,21 @@ Foam::labelList Foam::meshRefinement::nearestPatch
 
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
-    labelList nearestAdaptPatch;
+    nearestZone.setSize(mesh_.nFaces(), -1);
 
     if (adaptPatchIDs.size())
     {
-        nearestAdaptPatch.setSize(mesh_.nFaces(), adaptPatchIDs[0]);
+        nearestPatch.setSize(mesh_.nFaces(), adaptPatchIDs[0]);
+
+
+        // Get per-face the zone or -1
+        labelList faceToZone(mesh_.nFaces(), -1);
+        {
+            for (const faceZone& fz : mesh_.faceZones())
+            {
+                UIndirectList<label>(faceToZone, fz) = fz.index();
+            }
+        }
 
 
         // Count number of faces in adaptPatchIDs
@@ -462,12 +571,12 @@ Foam::labelList Foam::meshRefinement::nearestPatch
         }
 
         // Field on cells and faces.
-        List<topoDistanceData<label>> cellData(mesh_.nCells());
-        List<topoDistanceData<label>> faceData(mesh_.nFaces());
+        List<topoDistanceData<labelPair>> cellData(mesh_.nCells());
+        List<topoDistanceData<labelPair>> faceData(mesh_.nFaces());
 
         // Start of changes
         labelList patchFaces(nFaces);
-        List<topoDistanceData<label>> patchData(nFaces);
+        List<topoDistanceData<labelPair>> patchData(nFaces);
         nFaces = 0;
         forAll(adaptPatchIDs, i)
         {
@@ -477,13 +586,21 @@ Foam::labelList Foam::meshRefinement::nearestPatch
             forAll(pp, i)
             {
                 patchFaces[nFaces] = pp.start()+i;
-                patchData[nFaces] = topoDistanceData<label>(0, patchi);
+                patchData[nFaces] = topoDistanceData<labelPair>
+                (
+                    0,
+                    labelPair
+                    (
+                        patchi,
+                        faceToZone[pp.start()+i]
+                    )
+                );
                 nFaces++;
             }
         }
 
         // Propagate information inwards
-        FaceCellWave<topoDistanceData<label>> deltaCalc
+        FaceCellWave<topoDistanceData<labelPair>> deltaCalc
         (
             mesh_,
             patchFaces,
@@ -513,31 +630,45 @@ Foam::labelList Foam::meshRefinement::nearestPatch
             }
             else
             {
-                nearestAdaptPatch[facei] = faceData[facei].data();
+                const labelPair& data = faceData[facei].data();
+                nearestPatch[facei] = data.first();
+                nearestZone[facei] = data.second();
             }
         }
     }
     else
     {
         // Use patch 0
-        nearestAdaptPatch.setSize(mesh_.nFaces(), 0);
+        nearestPatch.setSize(mesh_.nFaces(), 0);
     }
+}
+
 
+Foam::labelList Foam::meshRefinement::nearestPatch
+(
+    const labelList& adaptPatchIDs
+) const
+{
+    labelList nearestAdaptPatch;
+    labelList nearestAdaptZone;
+    nearestPatch(adaptPatchIDs, nearestAdaptPatch, nearestAdaptZone);
     return nearestAdaptPatch;
 }
 
 
-Foam::labelList Foam::meshRefinement::nearestIntersection
+void Foam::meshRefinement::nearestIntersection
 (
     const labelList& surfacesToTest,
-    const label defaultRegion
+    const labelList& testFaces,
+
+    labelList& surface1,
+    List<pointIndexHit>& hit1,
+    labelList& region1,
+    labelList& surface2,
+    List<pointIndexHit>& hit2,
+    labelList& region2
 ) const
 {
-    // Determine nearest intersection for all mesh faces. Used when removing
-    // cells to give some reasonable patch to exposed faces. Use this
-    // function instead of nearestPatch if you don't have patches yet.
-
-
     // Swap neighbouring cell centres and cell level
     labelList neiLevel(mesh_.nBoundaryFaces());
     pointField neiCc(mesh_.nBoundaryFaces());
@@ -545,9 +676,6 @@ Foam::labelList Foam::meshRefinement::nearestIntersection
 
 
     // Collect segments
-    // ~~~~~~~~~~~~~~~~
-
-    const labelList testFaces(intersectedFaces());
 
     pointField start(testFaces.size());
     pointField end(testFaces.size());
@@ -564,17 +692,48 @@ Foam::labelList Foam::meshRefinement::nearestIntersection
     );
 
     // Do tests in one go
+
+    surfaces_.findNearestIntersection
+    (
+        surfacesToTest,
+        start,
+        end,
+
+        surface1,
+        hit1,
+        region1,
+        surface2,
+        hit2,
+        region2
+    );
+}
+
+
+Foam::labelList Foam::meshRefinement::nearestIntersection
+(
+    const labelList& surfacesToTest,
+    const label defaultRegion
+) const
+{
+    // Determine nearest intersection for all mesh faces. Used when removing
+    // cells to give some reasonable patch to exposed faces. Use this
+    // function instead of nearestPatch if you don't have patches yet.
+
+
+    // All faces with any intersection
+    const labelList testFaces(intersectedFaces());
+
+    // Find intersection (if any)
     labelList surface1;
     List<pointIndexHit> hit1;
     labelList region1;
     labelList surface2;
     List<pointIndexHit> hit2;
     labelList region2;
-    surfaces_.findNearestIntersection
+    nearestIntersection
     (
         surfacesToTest,
-        start,
-        end,
+        testFaces,
 
         surface1,
         hit1,
@@ -584,6 +743,7 @@ Foam::labelList Foam::meshRefinement::nearestIntersection
         region2
     );
 
+
     labelList nearestRegion(mesh_.nFaces(), defaultRegion);
 
     // Field on cells and faces.
@@ -591,9 +751,9 @@ Foam::labelList Foam::meshRefinement::nearestIntersection
     List<topoDistanceData<label>> faceData(mesh_.nFaces());
 
     // Start walking from all intersected faces
-    DynamicList<label> patchFaces(start.size());
-    DynamicList<topoDistanceData<label>> patchData(start.size());
-    forAll(start, i)
+    DynamicList<label> patchFaces(testFaces.size());
+    DynamicList<topoDistanceData<label>> patchData(testFaces.size());
+    forAll(testFaces, i)
     {
         label facei = testFaces[i];
         if (surface1[i] != -1)
@@ -2490,6 +2650,33 @@ bool Foam::meshRefinement::getFaceZoneInfo
 }
 
 
+Foam::label Foam::meshRefinement::addPointZone(const word& name)
+{
+    pointZoneMesh& pointZones = mesh_.pointZones();
+
+    label zoneI = pointZones.findZoneID(name);
+
+    if (zoneI == -1)
+    {
+        zoneI = pointZones.size();
+        pointZones.clearAddressing();
+        pointZones.setSize(zoneI+1);
+        pointZones.set
+        (
+            zoneI,
+            new pointZone
+            (
+                name,           // name
+                labelList(0),   // addressing
+                zoneI,          // index
+                pointZones      // pointZoneMesh
+            )
+        );
+    }
+    return zoneI;
+}
+
+
 void Foam::meshRefinement::selectSeparatedCoupledFaces(boolList& selected) const
 {
     for (const polyPatch& pp : mesh_.boundaryMesh())
@@ -2505,6 +2692,83 @@ void Foam::meshRefinement::selectSeparatedCoupledFaces(boolList& selected) const
 }
 
 
+Foam::labelList Foam::meshRefinement::countEdgeFaces
+(
+    const uindirectPrimitivePatch& pp
+) const
+{
+    // Count number of faces per edge. Parallel consistent.
+
+    const labelListList& edgeFaces = pp.edgeFaces();
+    labelList nEdgeFaces(edgeFaces.size());
+    forAll(edgeFaces, edgei)
+    {
+        nEdgeFaces[edgei] = edgeFaces[edgei].size();
+    }
+
+    // Sync across processor patches
+    if (Pstream::parRun())
+    {
+        const globalMeshData& globalData = mesh_.globalData();
+        const mapDistribute& map = globalData.globalEdgeSlavesMap();
+        const indirectPrimitivePatch& cpp = globalData.coupledPatch();
+
+        // Match pp edges to coupled edges
+        labelList patchEdges;
+        labelList coupledEdges;
+        PackedBoolList sameEdgeOrientation;
+        PatchTools::matchEdges
+        (
+            pp,
+            cpp,
+            patchEdges,
+            coupledEdges,
+            sameEdgeOrientation
+        );
+
+        // Convert patch-edge data into cpp-edge data
+        labelList coupledNEdgeFaces(map.constructSize(), Zero);
+        UIndirectList<label>(coupledNEdgeFaces, coupledEdges) =
+            UIndirectList<label>(nEdgeFaces, patchEdges);
+
+        // Synchronise
+        globalData.syncData
+        (
+            coupledNEdgeFaces,
+            globalData.globalEdgeSlaves(),
+            globalData.globalEdgeTransformedSlaves(),
+            map,
+            plusEqOp<label>()
+        );
+
+        // Convert back from cpp-edge to patch-edge
+        UIndirectList<label>(nEdgeFaces, patchEdges) =
+            UIndirectList<label>(coupledNEdgeFaces, coupledEdges);
+    }
+    return nEdgeFaces;
+}
+
+
+Foam::label Foam::meshRefinement::findCell
+(
+    const polyMesh& mesh,
+    const vector& perturbVec,
+    const point& p
+)
+{
+    // Force calculation of base points (needs to be synchronised)
+    (void)mesh.tetBasePtIs();
+
+    label celli = mesh.findCell(p, findCellMode);
+    if (returnReduce(celli, maxOp<label>()) == -1)
+    {
+        // See if we can perturb a bit
+        celli = mesh.findCell(p+perturbVec, findCellMode);
+    }
+    return celli;
+}
+
+
 Foam::label Foam::meshRefinement::findRegion
 (
     const polyMesh& mesh,
@@ -2539,6 +2803,176 @@ Foam::label Foam::meshRefinement::findRegion
 }
 
 
+Foam::fileName Foam::meshRefinement::writeLeakPath
+(
+    const polyMesh& mesh,
+    const pointField& locationsInMesh,
+    const pointField& locationsOutsideMesh,
+    const writer<scalar>& leakPathFormatter,
+    const boolList& blockedFace
+)
+{
+    const polyBoundaryMesh& pbm = mesh.boundaryMesh();
+
+    fileName outputDir;
+    if (Pstream::master())
+    {
+        outputDir =
+            mesh.time().globalPath()
+          / functionObject::outputPrefix
+          / mesh.pointsInstance();
+        outputDir.clean();
+        mkDir(outputDir);
+    }
+
+
+    // Write the leak path
+
+    meshSearch searchEngine(mesh);
+    shortestPathSet leakPath
+    (
+        "leakPath",
+        mesh,
+        searchEngine,
+        coordSet::coordFormatNames[coordSet::coordFormat::DISTANCE],
+        false,  //true,
+        50,     // tbd. Number of iterations
+        pbm.groupPatchIDs()["wall"],
+        locationsInMesh,
+        locationsOutsideMesh,
+        blockedFace
+    );
+
+    // Split leak path according to segment. Note: segment index
+    // is global (= index in locationsInsideMesh)
+    List<pointList> segmentPoints;
+    List<scalarList> segmentDist;
+    {
+        label nSegments = 0;
+        if (leakPath.segments().size())
+        {
+            nSegments = max(leakPath.segments())+1;
+        }
+        reduce(nSegments, maxOp<label>());
+
+        labelList nElemsPerSegment(nSegments, Zero);
+        for (label segmenti : leakPath.segments())
+        {
+            nElemsPerSegment[segmenti]++;
+        }
+        segmentPoints.setSize(nElemsPerSegment.size());
+        segmentDist.setSize(nElemsPerSegment.size());
+        forAll(nElemsPerSegment, i)
+        {
+            segmentPoints[i].setSize(nElemsPerSegment[i]);
+            segmentDist[i].setSize(nElemsPerSegment[i]);
+        }
+        nElemsPerSegment = 0;
+
+        forAll(leakPath, elemi)
+        {
+            label segmenti = leakPath.segments()[elemi];
+            pointList& points = segmentPoints[segmenti];
+            scalarList& dist = segmentDist[segmenti];
+            label& n = nElemsPerSegment[segmenti];
+
+            points[n] = leakPath[elemi];
+            dist[n] = leakPath.curveDist()[elemi];
+            n++;
+        }
+    }
+
+    PtrList<coordSet> allLeakPaths(segmentPoints.size());
+    forAll(allLeakPaths, segmenti)
+    {
+        // Collect data from all processors
+        List<pointList> gatheredPts(Pstream::nProcs());
+        gatheredPts[Pstream::myProcNo()] =
+            std::move(segmentPoints[segmenti]);
+        Pstream::gatherList(gatheredPts);
+
+        List<scalarList> gatheredDist(Pstream::nProcs());
+        gatheredDist[Pstream::myProcNo()] =
+            std::move(segmentDist[segmenti]);
+        Pstream::gatherList(gatheredDist);
+
+        // Combine processor lists into one big list.
+        pointList allPts
+        (
+            ListListOps::combine<pointList>
+            (
+                gatheredPts, accessOp<pointList>()
+            )
+        );
+        scalarList allDist
+        (
+            ListListOps::combine<scalarList>
+            (
+                gatheredDist, accessOp<scalarList>()
+            )
+        );
+
+        // Sort according to curveDist
+        labelList indexSet(Foam::sortedOrder(allDist));
+
+        allLeakPaths.set
+        (
+            segmenti,
+            new coordSet
+            (
+                leakPath.name(),
+                leakPath.axis(),
+                pointList(allPts, indexSet),
+                //scalarList(allDist, indexSet)
+                scalarList(allPts.size(), scalar(segmenti))
+            )
+        );
+    }
+
+    fileName fName;
+    if (Pstream::master())
+    {
+        List<List<scalarField>> allLeakData(1);
+        List<scalarField>& varData = allLeakData[0];
+        varData.setSize(allLeakPaths.size());
+        forAll(allLeakPaths, segmenti)
+        {
+            varData[segmenti] = allLeakPaths[segmenti].curveDist();
+        }
+
+        const wordList valueSetNames(1, "leakPath");
+
+        fName =
+            outputDir
+           /leakPathFormatter.getFileName
+            (
+                allLeakPaths[0],
+                valueSetNames
+            );
+
+        // Note scope to force writing to finish before
+        // FatalError exit
+        OFstream ofs(fName);
+        if (ofs.opened())
+        {
+            leakPathFormatter.write
+            (
+                true,                   // write tracks
+                List<scalarField>(),    // times
+                allLeakPaths,
+                valueSetNames,
+                allLeakData,
+                ofs
+            );
+        }
+    }
+
+    Pstream::scatter(fName);
+
+    return fName;
+}
+
+
 // Modify cellRegion to be consistent with locationsInMesh.
 // - all regions not in locationsInMesh are set to -1
 // - check that all regions inside locationsOutsideMesh are set to -1
@@ -2549,7 +2983,7 @@ Foam::label Foam::meshRefinement::findRegions
     const pointField& locationsInMesh,
     const pointField& locationsOutsideMesh,
     const bool exitIfLeakPath,
-    const writer<scalar>& leakPathFormatter,
+    const refPtr<writer<scalar>>& leakPathFormatter,
     const label nRegions,
     labelList& cellRegion,
     const boolList& blockedFace
@@ -2604,165 +3038,22 @@ Foam::label Foam::meshRefinement::findRegions
             label index = insideRegions.find(regioni);
             if (index != -1)
             {
-                const polyBoundaryMesh& pbm = mesh.boundaryMesh();
-
-                fileName outputDir;
-                if (Pstream::master())
-                {
-                    outputDir =
-                    (
-                        mesh.time().globalPath()
-                      / functionObject::outputPrefix
-                      / mesh.pointsInstance()
-                    );
-                    outputDir.clean();  // Remove unneeded ".."
-                    mkDir(outputDir);
-                }
-
-
-                // Write the leak path
-
-                meshSearch searchEngine(mesh);
-                shortestPathSet leakPath
-                (
-                    "leakPath",
-                    mesh,
-                    searchEngine,
-                    coordSet::coordFormatNames[coordSet::coordFormat::DISTANCE],
-                    false,  //true,
-                    50,     // tbd. Number of iterations
-                    pbm.groupPatchIDs()["wall"],
-                    locationsInMesh,
-                    locationsOutsideMesh,
-                    blockedFace
-                );
-
-                // Split leak path according to segment. Note: segment index
-                // is global (= index in locationsInsideMesh)
-                List<pointList> segmentPoints;
-                List<scalarList> segmentDist;
+                if (leakPathFormatter.valid())
                 {
-                    label nSegments = 0;
-                    if (leakPath.segments().size())
-                    {
-                        nSegments = max(leakPath.segments())+1;
-                    }
-                    reduce(nSegments, maxOp<label>());
-
-                    labelList nElemsPerSegment(nSegments, Zero);
-                    for (label segmenti : leakPath.segments())
-                    {
-                        nElemsPerSegment[segmenti]++;
-                    }
-                    segmentPoints.setSize(nElemsPerSegment.size());
-                    segmentDist.setSize(nElemsPerSegment.size());
-                    forAll(nElemsPerSegment, i)
-                    {
-                        segmentPoints[i].setSize(nElemsPerSegment[i]);
-                        segmentDist[i].setSize(nElemsPerSegment[i]);
-                    }
-                    nElemsPerSegment = 0;
-
-                    forAll(leakPath, elemi)
-                    {
-                        label segmenti = leakPath.segments()[elemi];
-                        pointList& points = segmentPoints[segmenti];
-                        scalarList& dist = segmentDist[segmenti];
-                        label& n = nElemsPerSegment[segmenti];
-
-                        points[n] = leakPath[elemi];
-                        dist[n] = leakPath.curveDist()[elemi];
-                        n++;
-                    }
-                }
-
-                PtrList<coordSet> allLeakPaths(segmentPoints.size());
-                forAll(allLeakPaths, segmenti)
-                {
-                    // Collect data from all processors
-                    List<pointList> gatheredPts(Pstream::nProcs());
-                    gatheredPts[Pstream::myProcNo()] =
-                        std::move(segmentPoints[segmenti]);
-                    Pstream::gatherList(gatheredPts);
-
-                    List<scalarList> gatheredDist(Pstream::nProcs());
-                    gatheredDist[Pstream::myProcNo()] =
-                        std::move(segmentDist[segmenti]);
-                    Pstream::gatherList(gatheredDist);
-
-                    // Combine processor lists into one big list.
-                    pointList allPts
-                    (
-                        ListListOps::combine<pointList>
-                        (
-                            gatheredPts, accessOp<pointList>()
-                        )
-                    );
-                    scalarList allDist
-                    (
-                        ListListOps::combine<scalarList>
-                        (
-                            gatheredDist, accessOp<scalarList>()
-                        )
-                    );
-
-                    // Sort according to curveDist
-                    labelList indexSet(Foam::sortedOrder(allDist));
-
-                    allLeakPaths.set
+                    const fileName fName
                     (
-                        segmenti,
-                        new coordSet
+                        writeLeakPath
                         (
-                            leakPath.name(),
-                            leakPath.axis(),
-                            pointList(allPts, indexSet),
-                            //scalarList(allDist, indexSet)
-                            scalarList(allPts.size(), scalar(segmenti))
+                            mesh,
+                            locationsInMesh,
+                            locationsOutsideMesh,
+                            leakPathFormatter,
+                            blockedFace
                         )
                     );
+                    Info<< "Dumped leak path to " << fName << endl;
                 }
 
-                fileName fName;
-                if (Pstream::master())
-                {
-                    List<List<scalarField>> allLeakData(1);
-                    List<scalarField>& varData = allLeakData[0];
-                    varData.setSize(allLeakPaths.size());
-                    forAll(allLeakPaths, segmenti)
-                    {
-                        varData[segmenti] = allLeakPaths[segmenti].curveDist();
-                    }
-
-                    const wordList valueSetNames(1, "leakPath");
-
-                    fName =
-                        outputDir
-                       /leakPathFormatter.getFileName
-                        (
-                            allLeakPaths[0],
-                            valueSetNames
-                        );
-
-                    // Note scope to force writing to finish before
-                    // FatalError exit
-                    OFstream ofs(fName);
-                    if (ofs.opened())
-                    {
-                        leakPathFormatter.write
-                        (
-                            true,                // write tracks
-                            List<scalarField>(), // times
-                            allLeakPaths,
-                            valueSetNames,
-                            allLeakData,
-                            ofs
-                        );
-                    }
-                }
-
-                Pstream::scatter(fName);
-
                 if (exitIfLeakPath)
                 {
                     FatalErrorInFunction
@@ -2770,7 +3061,6 @@ Foam::label Foam::meshRefinement::findRegions
                         << " is inside same mesh region " << regioni
                         << " as one of the locations outside mesh "
                         << locationsOutsideMesh
-                        << nl << "    Dumped leak path to " << fName
                         << exit(FatalError);
                 }
                 else
@@ -2779,8 +3069,7 @@ Foam::label Foam::meshRefinement::findRegions
                         << "Location in mesh " << locationsInMesh[index]
                         << " is inside same mesh region " << regioni
                         << " as one of the locations outside mesh "
-                        << locationsOutsideMesh
-                        << nl << "Dumped leak path to " << fName << endl;
+                        << locationsOutsideMesh << endl;
                 }
             }
         }
@@ -2814,7 +3103,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMeshRegions
     const pointField& locationsInMesh,
     const pointField& locationsOutsideMesh,
     const bool exitIfLeakPath,
-    const writer<scalar>& leakPathFormatter
+    const refPtr<writer<scalar>>& leakPathFormatter
 )
 {
     // Force calculation of face decomposition (used in findCell)
diff --git a/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.H b/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.H
index 66fbf372fce..db8a65ab171 100644
--- a/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.H
+++ b/src/mesh/snappyHexMesh/meshRefinement/meshRefinement.H
@@ -52,6 +52,7 @@ SourceFiles
 #include "autoPtr.H"
 #include "labelPairHashes.H"
 #include "indirectPrimitivePatch.H"
+#include "uindirectPrimitivePatch.H"
 #include "pointFieldsFwd.H"
 #include "Tuple2.H"
 #include "pointIndexHit.H"
@@ -69,6 +70,7 @@ namespace Foam
 // Forward Declarations
 class fvMesh;
 class mapDistributePolyMesh;
+class mapDistribute;
 class decompositionMethod;
 class refinementSurfaces;
 class refinementFeatures;
@@ -584,6 +586,8 @@ private:
                 const labelList& globalToMasterPatch,
                 const pointField& locationsInMesh,
                 const wordList& regionsInMesh,
+                const pointField& locationsOutsideMesh,
+                const refPtr<writer<scalar>>& leakPathFormatter,
 
                 const labelList& neiLevel,
                 const pointField& neiCc,
@@ -612,6 +616,17 @@ private:
                 polyTopoChange& meshMod
             ) const;
 
+            //- Write leak path
+            static fileName writeLeakPath
+            (
+                const polyMesh& mesh,
+                const pointField& locationsInMesh,
+                const pointField& locationsOutsideMesh,
+                const writer<scalar>& leakPathFormatter,
+                const boolList& blockedFace
+            );
+
+
         // Problem cell handling
 
             //- Helper function to mark face as being on 'boundary'. Used by
@@ -658,12 +673,37 @@ private:
             //- Returns list with for every internal face -1 or the patch
             //  they should be baffled into. If removeEdgeConnectedCells is set
             //  removes cells based on perpendicularAngle.
-            labelList markFacesOnProblemCells
+            void markFacesOnProblemCells
             (
                 const dictionary& motionDict,
                 const bool removeEdgeConnectedCells,
                 const scalarField& perpendicularAngle,
-                const labelList& globalToMasterPatch
+                const labelList& globalToMasterPatch,
+
+                labelList& facePatch,
+                labelList& faceZone
+            ) const;
+
+            //- Calculates for every face the nearest 'start' face. Any
+            //  unreached face does not get set (faceToStart[facei] = -1)
+            void nearestFace
+            (
+                const labelUList& startFaces,
+                const bitSet& isBlockedFace,
+
+                autoPtr<mapDistribute>& mapPtr,
+                labelList& faceToStart,
+                const label nIter = labelMax
+            ) const;
+
+            //- Calculates for every face the label of the nearest
+            //  patch its zone. Any unreached face (disconnected mesh?) becomes
+            //  adaptPatchIDs[0]
+            void nearestPatch
+            (
+                const labelList& adaptPatchIDs,
+                labelList& nearestPatch,
+                labelList& nearestZone
             ) const;
 
             //- Returns list with for every face the label of the nearest
@@ -682,12 +722,15 @@ private:
 
             //- Returns list with for every internal face -1 or the patch
             //  they should be baffled into.
-            labelList markFacesOnProblemCellsGeometric
+            void markFacesOnProblemCellsGeometric
             (
                 const snapParameters& snapParams,
                 const dictionary& motionDict,
                 const labelList& globalToMasterPatch,
-                const labelList& globalToSlavePatch
+                const labelList& globalToSlavePatch,
+
+                labelList& facePatch,
+                labelList& faceZone
             ) const;
 
 
@@ -802,6 +845,8 @@ private:
                 const label backgroundZoneID,
                 const pointField& locationsInMesh,
                 const wordList& zonesInMesh,
+                const pointField& locationsOutsideMesh,
+                const refPtr<writer<scalar>>& leakPathFormatter,
 
                 labelList& cellToZone,
                 labelList& unnamedRegion1,
@@ -1156,6 +1201,47 @@ public:
                     const label growIter
                 );
 
+            // Blocking leaks (by blocking cells)
+
+                //- Faces currently on boundary or intersected by surface
+                void selectIntersectedFaces
+                (
+                    const labelList& surfaces,
+                    boolList& isBlockedFace
+                ) const;
+
+                //- Return list of cells to block by walking from the seedCells
+                //  until reaching a leak face
+                labelList detectLeakCells
+                (
+                    const boolList& isBlockedFace,
+                    const labelList& leakFaces,
+                    const labelList& seedCells
+                ) const;
+
+                //- Remove minimum amount of cells to break any leak from
+                //  inside to outside
+                autoPtr<mapPolyMesh> removeLeakCells
+                (
+                    const labelList& globalToMasterPatch,
+                    const labelList& globalToSlavePatch,
+                    const pointField& locationsInMesh,
+                    const pointField& locationsOutsideMesh,
+                    const labelList& selectedSurfaces
+                );
+
+                //- Baffle faces to break any leak from inside to outside
+                autoPtr<mapPolyMesh> blockLeakFaces
+                (
+                    const labelList& globalToMasterPatch,
+                    const labelList& globalToSlavePatch,
+                    const pointField& locationsInMesh,
+                    const wordList& zonesInMesh,
+                    const pointField& locationsOutsideMesh,
+                    const labelList& selectedSurfaces
+                );
+
+
             //- Refine some cells
             autoPtr<mapPolyMesh> refine(const labelList& cellsToRefine);
 
@@ -1217,7 +1303,7 @@ public:
                 const pointField& locationsInMesh,
                 const wordList& regionsInMesh,
                 const pointField& locationsOutsideMesh,
-                const writer<scalar>& leakPathFormatter
+                const refPtr<writer<scalar>>& leakPathFormatter
             );
 
             //- Merge free-standing baffles
@@ -1233,8 +1319,7 @@ public:
                 const labelList& globalToMasterPatch,
                 const labelList& globalToSlavePatch,
                 const pointField& locationsInMesh,
-                const pointField& locationsOutsideMesh,
-                const writer<scalar>& leakPathFormatter
+                const pointField& locationsOutsideMesh
             );
 
             //- Split off (with optional buffer layers) unreachable areas
@@ -1260,7 +1345,8 @@ public:
                 const labelList& globalToMasterPatch,
                 const labelList& globalToSlavePatch,
                 const pointField& locationsInMesh,
-                const wordList& regionsInMesh
+                const wordList& regionsInMesh,
+                const pointField& locationsOutsideMesh
             );
 
             //- Find boundary points that connect to more than one cell
@@ -1349,6 +1435,8 @@ public:
                 const label nErodeCellZones,
                 const pointField& locationsInMesh,
                 const wordList& regionsInMesh,
+                const pointField& locationsOutsideMesh,
+                const refPtr<writer<scalar>>& leakPathFormatter,
                 wordPairHashTable& zonesToFaceZone
             );
 
@@ -1394,12 +1482,50 @@ public:
                 surfaceZonesInfo::faceZoneType& fzType
             ) const;
 
+            //- Add pointZone if does not exist. Return index of zone
+            label addPointZone(const word& name);
+
+            //- Count number of faces per patch edge. Parallel consistent.
+            labelList countEdgeFaces(const uindirectPrimitivePatch& pp) const;
+
             //- Select coupled faces that are not collocated
             void selectSeparatedCoupledFaces(boolList&) const;
 
             //- Find any intersection of surface. Store in surfaceIndex_.
             void updateIntersections(const labelList& changedFaces);
 
+            //- Calculate nearest intersection for selected mesh faces
+            void nearestIntersection
+            (
+                const labelList& surfacesToTest,
+                const labelList& testFaces,
+
+                labelList& surface1,
+                List<pointIndexHit>& hit1,
+                labelList& region1,
+                labelList& surface2,
+                List<pointIndexHit>& hit2,
+                labelList& region2
+            ) const;
+
+            //- Remove cells. Put exposedFaces into exposedPatchIDs.
+            autoPtr<mapPolyMesh> doRemoveCells
+            (
+                const labelList& cellsToRemove,
+                const labelList& exposedFaces,
+                const labelList& exposedPatchIDs,
+                removeCells& cellRemover
+            );
+
+            //- Find cell point is in. Uses optional perturbation to re-test.
+            //  Returns -1 on processors that do not have the cell.
+            static label findCell
+            (
+                const polyMesh&,
+                const vector& perturbVec,
+                const point& p
+            );
+
             //- Find region point is in. Uses optional perturbation to re-test.
             static label findRegion
             (
@@ -1418,7 +1544,7 @@ public:
                 const pointField& locationsInMesh,
                 const pointField& locationsOutsideMesh,
                 const bool exitIfLeakPath,
-                const writer<scalar>& leakPathFormatter,
+                const refPtr<writer<scalar>>& leakPathFormatter,
                 const label nRegions,
                 labelList& cellRegion,
                 const boolList& blockedFace
@@ -1433,16 +1559,7 @@ public:
                 const pointField& locationsInMesh,
                 const pointField& locationsOutsideMesh,
                 const bool exitIfLeakPath,
-                const writer<scalar>& leakPathFormatter
-            );
-
-            //- Remove cells. Put exposedFaces into exposedPatchIDs.
-            autoPtr<mapPolyMesh> doRemoveCells
-            (
-                const labelList& cellsToRemove,
-                const labelList& exposedFaces,
-                const labelList& exposedPatchIDs,
-                removeCells& cellRemover
+                const refPtr<writer<scalar>>& leakPathFormatter
             );
 
             //- Split faces into two
diff --git a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBaffles.C b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBaffles.C
index 8638d0386d6..26f891b2289 100644
--- a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBaffles.C
+++ b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBaffles.C
@@ -54,6 +54,7 @@ License
 #include "shellSurfaces.H"
 #include "zeroGradientFvPatchFields.H"
 #include "volFields.H"
+#include "holeToFace.H"
 
 #include "FaceCellWave.H"
 #include "wallPoints.H"
@@ -290,7 +291,8 @@ void Foam::meshRefinement::getBafflePatches
     const labelList& globalToMasterPatch,
     const pointField& locationsInMesh,
     const wordList& zonesInMesh,
-
+    const pointField& locationsOutsideMesh,
+    const refPtr<writer<scalar>>& leakPathFormatter,
     const labelList& neiLevel,
     const pointField& neiCc,
 
@@ -307,6 +309,8 @@ void Foam::meshRefinement::getBafflePatches
 
     // 1. Determine intersections with unnamed surfaces and cell zones
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    // Notice that this also does hole-closure so the unnamed* is not just
+    // the surface intersections.
 
     labelList cellToZone;
     labelList unnamedRegion1;
@@ -321,6 +325,8 @@ void Foam::meshRefinement::getBafflePatches
             -2,                 // zone to put unreached cells into
             locationsInMesh,
             zonesInMesh,
+            locationsOutsideMesh,
+            leakPathFormatter,
 
             cellToZone,
             unnamedRegion1,
@@ -2836,6 +2842,8 @@ void Foam::meshRefinement::zonify
     const label backgroundZoneID,
     const pointField& locationsInMesh,
     const wordList& zonesInMesh,
+    const pointField& locationsOutsideMesh,
+    const refPtr<writer<scalar>>& leakPathFormatter,
 
     labelList& cellToZone,
     labelList& unnamedRegion1,
@@ -2856,6 +2864,17 @@ void Foam::meshRefinement::zonify
 
     const PtrList<surfaceZonesInfo>& surfZones = surfaces_.surfZones();
 
+    // Collect inside and outside into single list
+    const List<pointField> allLocations
+    (
+        refinementParameters::zonePoints
+        (
+            locationsInMesh,
+            zonesInMesh,
+            locationsOutsideMesh
+        )
+    );
+
     // Swap neighbouring cell centres and cell level
     labelList neiLevel(mesh_.nBoundaryFaces());
     pointField neiCc(mesh_.nBoundaryFaces());
@@ -2899,7 +2918,105 @@ void Foam::meshRefinement::zonify
         unnamedRegion2
     );
 
+    // Extend with hole closing faces (only if locationsOutsideMesh)
+    labelList unnamedFaces;
+    labelList unnamedClosureFaces;
+    labelList unnamedToClosure;
+    autoPtr<mapDistribute> unnamedMapPtr;
+    if (locationsOutsideMesh.size())
+    {
+        unnamedFaces = ListOps::findIndices
+        (
+            unnamedRegion1,
+            [](const label x){return x != -1;}
+        );
+
+        const globalIndex globalUnnamedFaces(unnamedFaces.size());
 
+        unnamedMapPtr = holeToFace::calcClosure
+        (
+            mesh_,
+            allLocations,
+            unnamedFaces,
+            globalUnnamedFaces,
+            true,                   // allow erosion
+
+            unnamedClosureFaces,
+            unnamedToClosure
+        );
+
+        if (debug)
+        {
+            Pout<< "meshRefinement::zonify : found wall closure faces:"
+                << unnamedClosureFaces.size()
+                << "  map:" << unnamedMapPtr.valid() << endl;
+        }
+
+
+        // Add to unnamedRegion1, unnamedRegion2
+        if (unnamedMapPtr.valid())
+        {
+            Info<< "Detected and closed leak path from "
+                << locationsInMesh << " to " << locationsOutsideMesh
+                << endl;
+
+            // Dump leak path
+            if (leakPathFormatter.valid())
+            {
+                boolList blockedFace(mesh_.nFaces(), false);
+                UIndirectList<bool>(blockedFace, unnamedFaces) = true;
+                const fileName fName
+                (
+                    writeLeakPath
+                    (
+                        mesh_,
+                        locationsInMesh,
+                        locationsOutsideMesh,
+                        leakPathFormatter(),
+                        blockedFace
+                    )
+                );
+                Info<< "Dumped leak path to " << fName << endl;
+            }
+
+            labelList packedRegion1
+            (
+                UIndirectList<label>(unnamedRegion1, unnamedFaces)
+            );
+            unnamedMapPtr->distribute(packedRegion1);
+            labelList packedRegion2
+            (
+                UIndirectList<label>(unnamedRegion2, unnamedFaces)
+            );
+            unnamedMapPtr->distribute(packedRegion2);
+            forAll(unnamedClosureFaces, i)
+            {
+                const label sloti = unnamedToClosure[i];
+
+                if (sloti != -1)
+                {
+                    const label facei = unnamedClosureFaces[i];
+                    const label region1 = unnamedRegion1[facei];
+                    const label slotRegion1 = packedRegion1[sloti];
+                    const label region2 = unnamedRegion2[facei];
+                    const label slotRegion2 = packedRegion2[sloti];
+
+                    if (slotRegion1 != region1 || slotRegion2 != region2)
+                    {
+                        unnamedRegion1[facei] = slotRegion1;
+                        unnamedRegion2[facei] = slotRegion2;
+                    }
+                }
+            }
+        }
+    }
+
+
+    // Extend with hole closing faces (only if locationsOutsideMesh)
+    labelList namedFaces;
+    labelList namedClosureFaces;
+    labelList namedToClosure;
+    autoPtr<mapDistribute> namedMapPtr;
     if (namedSurfaces.size())
     {
         getIntersections
@@ -2910,9 +3027,185 @@ void Foam::meshRefinement::zonify
             namedSurfaceRegion,
             posOrientation
         );
+
+        if (locationsOutsideMesh.size())
+        {
+            namedFaces = ListOps::findIndices
+            (
+                namedSurfaceRegion,
+                [](const label x){return x != -1;}
+            );
+
+            const globalIndex globalNamedFaces(namedFaces.size());
+
+            namedMapPtr = holeToFace::calcClosure
+            (
+                mesh_,
+                allLocations,
+                namedFaces,
+                globalNamedFaces,
+                true,                   // allow erosion
+
+                namedClosureFaces,
+                namedToClosure
+            );
+
+            if (debug)
+            {
+                Pout<< "meshRefinement::zonify : found faceZone closure faces:"
+                    << namedClosureFaces.size()
+                    << "  map:" << namedMapPtr.valid() << endl;
+            }
+
+            // Add to namedSurfaceRegion, posOrientation
+            if (namedMapPtr.valid())
+            {
+                Info<< "Detected and closed leak path from "
+                    << locationsInMesh << " to " << locationsOutsideMesh
+                    << endl;
+
+                // Dump leak path
+                if (leakPathFormatter.valid())
+                {
+                    boolList blockedFace(mesh_.nFaces(), false);
+                    UIndirectList<bool>(blockedFace, unnamedFaces) = true;
+                    UIndirectList<bool>(blockedFace, namedFaces) = true;
+                    const fileName fName
+                    (
+                        writeLeakPath
+                        (
+                            mesh_,
+                            locationsInMesh,
+                            locationsOutsideMesh,
+                            leakPathFormatter(),
+                            blockedFace
+                        )
+                    );
+                    Info<< "Dumped leak path to " << fName << endl;
+                }
+
+                labelList packedSurfaceRegion
+                (
+                    UIndirectList<label>(namedSurfaceRegion, namedFaces)
+                );
+                namedMapPtr->distribute(packedSurfaceRegion);
+                boolList packedOrientation(posOrientation.size());
+                forAll(namedFaces, i)
+                {
+                    const label facei = namedFaces[i];
+                    packedOrientation[i] = posOrientation[facei];
+                }
+                namedMapPtr->distribute(packedOrientation);
+                forAll(namedClosureFaces, i)
+                {
+                    const label sloti = namedToClosure[i];
+                    if (sloti != -1)
+                    {
+                        const label facei = namedClosureFaces[i];
+                        const label regioni = namedSurfaceRegion[facei];
+                        const label slotRegioni = packedSurfaceRegion[sloti];
+                        const bool orient = posOrientation[facei];
+                        const bool slotOrient = packedOrientation[sloti];
+
+                        if (slotRegioni != regioni || slotOrient != orient)
+                        {
+                            namedSurfaceRegion[facei] = slotRegioni;
+                            posOrientation[facei] = slotOrient;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    // 1b. Add any hole closure faces to frozenPoints pointZone
+    {
+        bitSet isClosureFace(mesh_.nFaces());
+        isClosureFace.set(unnamedClosureFaces);
+        isClosureFace.set(namedClosureFaces);
+        const labelList closureFaces(isClosureFace.sortedToc());
+
+        const uindirectPrimitivePatch pp
+        (
+            UIndirectList<face>(mesh_.faces(), closureFaces),
+            mesh_.points()
+        );
+
+        // Count number of faces per edge
+        const labelList nEdgeFaces(countEdgeFaces(pp));
+
+        // Freeze all internal points
+        bitSet isFrozenPoint(mesh_.nPoints());
+        forAll(nEdgeFaces, edgei)
+        {
+            if (nEdgeFaces[edgei] != 1)
+            {
+                const edge& e = pp.edges()[edgei];
+                isFrozenPoint.set(pp.meshPoints()[e[0]]);
+                isFrozenPoint.set(pp.meshPoints()[e[1]]);
+            }
+        }
+
+        // Lookup/add pointZone and include its points
+        pointZoneMesh& pointZones =
+            const_cast<pointZoneMesh&>(mesh_.pointZones());
+        const label zonei =
+            const_cast<meshRefinement&>(*this).addPointZone("frozenPoints");
+        const bitSet oldSet(mesh_.nPoints(), pointZones[zonei]);
+        isFrozenPoint.set(oldSet);
+
+        syncTools::syncPointList
+        (
+            mesh_,
+            isFrozenPoint,
+            orEqOp<unsigned int>(),
+            0u
+        );
+
+        // Override addressing
+        pointZones.clearAddressing();
+        pointZones[zonei] = isFrozenPoint.sortedToc();
+
+        if (debug)
+        {
+            const pointZone& pz = pointZones[zonei];
+            mkDir(mesh_.time().timePath());
+            OBJstream str(mesh_.time().timePath()/pz.name()+".obj");
+            Pout<< "Writing " << pz.size() << " frozen points to "
+                << str.name() << endl;
+            for (const label pointi : pz)
+            {
+                str.write(mesh_.points()[pointi]);
+            }
+        }
+
+        if (debug && returnReduce(unnamedClosureFaces.size(), sumOp<label>()))
+        {
+            mkDir(mesh_.time().timePath());
+            OBJstream str(mesh_.time().timePath()/"unnamedClosureFaces.obj");
+            Pout<< "Writing " << unnamedClosureFaces.size()
+                << " unnamedClosureFaces to " << str.name() << endl;
+            for (const label facei : unnamedClosureFaces)
+            {
+                str.write(mesh_.faces()[facei], mesh_.points(), false);
+            }
+        }
+        if (debug && returnReduce(namedClosureFaces.size(), sumOp<label>()))
+        {
+            mkDir(mesh_.time().timePath());
+            OBJstream str(mesh_.time().timePath()/"namedClosureFaces.obj");
+            Pout<< "Writing " << namedClosureFaces.size()
+                << " namedClosureFaces to " << str.name() << endl;
+            for (const label facei : namedClosureFaces)
+            {
+                str.write(mesh_.faces()[facei], mesh_.points(), false);
+            }
+        }
     }
 
 
+
     // 2. Walk from locationsInMesh. Hard set cellZones.
     //    Note: walk through faceZones! (these might get overridden later)
 
@@ -3369,24 +3662,31 @@ void Foam::meshRefinement::handleSnapProblems
         << endl;
 
     labelList facePatch;
+    labelList faceZone;
     if (useTopologicalSnapDetection)
     {
-        facePatch = markFacesOnProblemCells
+        markFacesOnProblemCells
         (
             motionDict,
             removeEdgeConnectedCells,
             perpendicularAngle,
-            globalToMasterPatch
+            globalToMasterPatch,
+
+            facePatch,
+            faceZone
         );
     }
     else
     {
-        facePatch = markFacesOnProblemCellsGeometric
+        markFacesOnProblemCellsGeometric
         (
             snapParams,
             motionDict,
             globalToMasterPatch,
-            globalToSlavePatch
+            globalToSlavePatch,
+
+            facePatch,
+            faceZone
         );
     }
     Info<< "Analyzed problem cells in = "
@@ -3416,6 +3716,47 @@ void Foam::meshRefinement::handleSnapProblems
         ++runTime;
     }
 
+
+    // Add faces-to-baffle to faceZone. For now do this outside of topoChanges
+    {
+        const faceZoneMesh& fzs = mesh_.faceZones();
+        List<DynamicList<label>> zoneToFaces(fzs.size());
+        List<DynamicList<bool>> zoneToFlip(fzs.size());
+
+        // Start off with original contents
+        forAll(fzs, zonei)
+        {
+            zoneToFaces[zonei].append(fzs[zonei]);
+            zoneToFlip[zonei].append(fzs[zonei].flipMap());
+        }
+
+        // Add any to-be-patched face
+        forAll(facePatch, facei)
+        {
+            if (facePatch[facei] != -1)
+            {
+                label zonei = faceZone[facei];
+                if (zonei != -1)
+                {
+                    zoneToFaces[zonei].append(facei);
+                    zoneToFlip[zonei].append(false);
+                }
+            }
+        }
+
+        forAll(zoneToFaces, zonei)
+        {
+            surfaceZonesInfo::addFaceZone
+            (
+                fzs[zonei].name(),
+                zoneToFaces[zonei],
+                zoneToFlip[zonei],
+                mesh_
+            );
+        }
+    }
+
+
     // Create baffles with same owner and neighbour for now.
     createBaffles(facePatch, facePatch);
 
@@ -4168,7 +4509,7 @@ void Foam::meshRefinement::baffleAndSplitMesh
     const pointField& locationsInMesh,
     const wordList& zonesInMesh,
     const pointField& locationsOutsideMesh,
-    const writer<scalar>& leakPathFormatter
+    const refPtr<writer<scalar>>& leakPathFormatter
 )
 {
     // Introduce baffles
@@ -4194,6 +4535,8 @@ void Foam::meshRefinement::baffleAndSplitMesh
 
         locationsInMesh,
         zonesInMesh,
+        locationsOutsideMesh,
+        refPtr<writer<scalar>>(nullptr),
 
         neiLevel,
         neiCc,
@@ -4266,6 +4609,8 @@ void Foam::meshRefinement::baffleAndSplitMesh
 
                 locationsInMesh,
                 zonesInMesh,
+                locationsOutsideMesh,
+                refPtr<writer<scalar>>(nullptr),
 
                 neiLevel,
                 neiCc,
@@ -4347,8 +4692,7 @@ void Foam::meshRefinement::mergeFreeStandingBaffles
     const labelList& globalToMasterPatch,
     const labelList& globalToSlavePatch,
     const pointField& locationsInMesh,
-    const pointField& locationsOutsideMesh,
-    const writer<scalar>& leakPathFormatter
+    const pointField& locationsOutsideMesh
 )
 {
     // Merge baffles
@@ -4416,7 +4760,7 @@ void Foam::meshRefinement::mergeFreeStandingBaffles
             locationsInMesh,
             locationsOutsideMesh,
             true,   // Exit if any connection between inside and outside
-            leakPathFormatter
+            refPtr<writer<scalar>>(nullptr) //leakPathFormatter
         );
 
 
@@ -4461,6 +4805,8 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
 
         locationsInMesh,
         zonesInMesh,
+        locationsOutsideMesh,
+        leakPathFormatter,
 
         neiLevel,
         neiCc,
@@ -4529,6 +4875,122 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
     const labelList& faceOwner = mesh_.faceOwner();
     const labelList& faceNeighbour = mesh_.faceNeighbour();
 
+
+    // Checks
+    for (label facei = 0; facei < mesh_.nInternalFaces(); facei++)
+    {
+        if (ownPatch[facei] == -1 && neiPatch[facei] != -1)
+        {
+            FatalErrorInFunction << "Problem in face:" << facei
+                << " at:" << mesh_.faceCentres()[facei]
+                << " ownPatch:" << ownPatch[facei]
+                << " neiPatch:" << neiPatch[facei]
+                << exit(FatalError);
+        }
+        else
+        {
+            // Check if cellRegion indeed limited by patch
+            const label ownRegion = cellRegion[faceOwner[facei]];
+            const label neiRegion = cellRegion[faceNeighbour[facei]];
+            if (ownRegion != neiRegion)
+            {
+                if (ownPatch[facei] == -1)
+                {
+                    FatalErrorInFunction << "Problem in face:" << facei
+                        << " at:" << mesh_.faceCentres()[facei]
+                        << " ownPatch:" << ownPatch[facei]
+                        << " ownRegion:" << ownRegion
+                        << " neiPatch:" << neiPatch[facei]
+                        << " neiRegion:" << neiRegion
+                        << exit(FatalError);
+                }
+            }
+        }
+    }
+
+    // Determine on original data the nearest face. This is used as a fall-back
+    // to set the patch if the nBufferLayers walking didn't work.
+    labelList nearestOwnPatch;
+    if (nBufferLayers)
+    {
+        DynamicList<label> startFaces;
+        forAll(ownPatch, facei)
+        {
+            if (ownPatch[facei] != -1)
+            {
+                startFaces.append(facei);
+            }
+        }
+
+        // Per face the index to the start face.
+        labelList faceToStart;
+        autoPtr<mapDistribute> mapPtr;
+        nearestFace
+        (
+            startFaces,
+            bitSet(mesh_.nFaces()), // no blocked faces
+            mapPtr,
+            faceToStart,
+            nBufferLayers+4     // bit more than nBufferLayers since
+                                // walking face-cell-face
+        );
+
+        // Use map to push ownPatch to all reached faces
+        labelList startOwnPatch(ownPatch, startFaces);
+        mapPtr().distribute(startOwnPatch);
+
+        nearestOwnPatch.setSize(mesh_.nFaces());
+        nearestOwnPatch = -1;
+        forAll(faceToStart, facei)
+        {
+            const label sloti = faceToStart[facei];
+            if (sloti != -1)
+            {
+                nearestOwnPatch[facei] = startOwnPatch[sloti];
+            }
+        }
+    }
+
+    // Leak closure:
+    // ~~~~~~~~~~~~~
+    // We do not want to add buffer layers on the frozen points/faces
+    // since these are the exact faces needed to close a hole (to an
+    // locationOutsideMesh). Adding even
+    // a single layer of cells would mean that in further manipulation there
+    // is now no path to the locationOutsideMesh so the layer closure does
+    // not get triggered and we keep the added 1 layer of cells on the
+    // closure faces.
+    bitSet isFrozenPoint(mesh_.nPoints());
+    bitSet isFrozenFace(mesh_.nFaces());
+
+    if (nBufferLayers)
+    {
+        const labelListList& pointFaces = mesh_.pointFaces();
+        const pointZoneMesh& pzs = mesh_.pointZones();
+        const label pointZonei = pzs.findZoneID("frozenPoints");
+        if (pointZonei != -1)
+        {
+            const pointZone& pz = pzs[pointZonei];
+            isFrozenPoint.set(pz);
+            for (const label pointi : pz)
+            {
+                isFrozenFace.set(pointFaces[pointi]);
+            }
+        }
+
+        const faceZoneMesh& fzs = mesh_.faceZones();
+        const label faceZonei = fzs.findZoneID("frozenFaces");
+        if (faceZonei != -1)
+        {
+            const faceZone& fz = fzs[faceZonei];
+            isFrozenFace.set(fz);
+            for (const label facei : fz)
+            {
+                isFrozenPoint.set(mesh_.faces()[facei]);
+            }
+        }
+    }
+
     // Patch for exposed faces for lack of anything sensible.
     label defaultPatch = 0;
     if (globalToMasterPatch.size())
@@ -4542,33 +5004,43 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
 
         labelList pointBaffle(mesh_.nPoints(), -1);
 
-        forAll(faceNeighbour, faceI)
+        forAll(faceNeighbour, facei)
         {
-            const face& f = mesh_.faces()[faceI];
+            if (!isFrozenFace[facei])
+            {
+                const face& f = mesh_.faces()[facei];
 
-            const label ownRegion = cellRegion[faceOwner[faceI]];
-            const label neiRegion = cellRegion[faceNeighbour[faceI]];
+                const label ownRegion = cellRegion[faceOwner[facei]];
+                const label neiRegion = cellRegion[faceNeighbour[facei]];
 
-            if (ownRegion == -1 && neiRegion != -1)
-            {
-                // Note max(..) since possibly regionSplit might have split
-                // off extra unreachable parts of mesh. Note: or can this only
-                // happen for boundary faces?
-                forAll(f, fp)
+                if (ownRegion == -1 && neiRegion != -1)
                 {
-                    pointBaffle[f[fp]] = max(defaultPatch, ownPatch[faceI]);
-                }
-            }
-            else if (ownRegion != -1 && neiRegion == -1)
-            {
-                label newPatchI = neiPatch[faceI];
-                if (newPatchI == -1)
-                {
-                    newPatchI = max(defaultPatch, ownPatch[faceI]);
+                    // Note max(..) since possibly regionSplit might have split
+                    // off extra unreachable parts of mesh. Note: or can this
+                    // only happen for boundary faces?
+                    forAll(f, fp)
+                    {
+                        if (!isFrozenPoint[f[fp]])
+                        {
+                            pointBaffle[f[fp]] =
+                                max(defaultPatch, ownPatch[facei]);
+                        }
+                    }
                 }
-                forAll(f, fp)
+                else if (ownRegion != -1 && neiRegion == -1)
                 {
-                    pointBaffle[f[fp]] = newPatchI;
+                    label newPatchi = neiPatch[facei];
+                    if (newPatchi == -1)
+                    {
+                        newPatchi = max(defaultPatch, ownPatch[facei]);
+                    }
+                    forAll(f, fp)
+                    {
+                        if (!isFrozenPoint[f[fp]])
+                        {
+                            pointBaffle[f[fp]] = newPatchi;
+                        }
+                    }
                 }
             }
         }
@@ -4577,21 +5049,29 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
         syncTools::swapBoundaryCellList(mesh_, cellRegion, neiCellRegion);
         for
         (
-            label faceI = mesh_.nInternalFaces();
-            faceI < mesh_.nFaces();
-            faceI++
+            label facei = mesh_.nInternalFaces();
+            facei < mesh_.nFaces();
+            facei++
         )
         {
-            const face& f = mesh_.faces()[faceI];
+            if (!isFrozenFace[facei])
+            {
+                const face& f = mesh_.faces()[facei];
 
-            const label ownRegion = cellRegion[faceOwner[faceI]];
-            const label neiRegion = neiCellRegion[faceI-mesh_.nInternalFaces()];
+                const label ownRegion = cellRegion[faceOwner[facei]];
+                const label neiRegion =
+                    neiCellRegion[facei-mesh_.nInternalFaces()];
 
-            if (ownRegion == -1 && neiRegion != -1)
-            {
-                forAll(f, fp)
+                if (ownRegion == -1 && neiRegion != -1)
                 {
-                    pointBaffle[f[fp]] = max(defaultPatch, ownPatch[faceI]);
+                    forAll(f, fp)
+                    {
+                        if (!isFrozenPoint[f[fp]])
+                        {
+                            pointBaffle[f[fp]] =
+                                max(defaultPatch, ownPatch[facei]);
+                        }
+                    }
                 }
             }
         }
@@ -4610,19 +5090,19 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
 
         const labelListList& pointFaces = mesh_.pointFaces();
 
-        forAll(pointFaces, pointI)
+        forAll(pointFaces, pointi)
         {
-            if (pointBaffle[pointI] != -1)
+            if (pointBaffle[pointi] != -1)
             {
-                const labelList& pFaces = pointFaces[pointI];
+                const labelList& pFaces = pointFaces[pointi];
 
-                forAll(pFaces, pFaceI)
+                forAll(pFaces, pFacei)
                 {
-                    label faceI = pFaces[pFaceI];
+                    const label facei = pFaces[pFacei];
 
-                    if (ownPatch[faceI] == -1)
+                    if (!isFrozenFace[facei] && ownPatch[facei] == -1)
                     {
-                        ownPatch[faceI] = pointBaffle[pointI];
+                        ownPatch[facei] = pointBaffle[pointi];
                     }
                 }
             }
@@ -4634,11 +5114,11 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
 
         labelList newOwnPatch(ownPatch);
 
-        forAll(ownPatch, faceI)
+        forAll(ownPatch, facei)
         {
-            if (ownPatch[faceI] != -1)
+            if (!isFrozenFace[facei] && ownPatch[facei] != -1)
             {
-                label own = faceOwner[faceI];
+                const label own = faceOwner[facei];
 
                 if (cellRegion[own] == -1)
                 {
@@ -4647,15 +5127,16 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
                     const cell& ownFaces = mesh_.cells()[own];
                     forAll(ownFaces, j)
                     {
-                        if (ownPatch[ownFaces[j]] == -1)
+                        const label ownFacei = ownFaces[j];
+                        if (!isFrozenFace[ownFacei] && ownPatch[ownFacei] == -1)
                         {
-                            newOwnPatch[ownFaces[j]] = ownPatch[faceI];
+                            newOwnPatch[ownFacei] = ownPatch[facei];
                         }
                     }
                 }
-                if (mesh_.isInternalFace(faceI))
+                if (mesh_.isInternalFace(facei))
                 {
-                    label nei = faceNeighbour[faceI];
+                    const label nei = faceNeighbour[facei];
 
                     if (cellRegion[nei] == -1)
                     {
@@ -4664,9 +5145,11 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
                         const cell& neiFaces = mesh_.cells()[nei];
                         forAll(neiFaces, j)
                         {
-                            if (ownPatch[neiFaces[j]] == -1)
+                            const label neiFacei = neiFaces[j];
+                            const bool isFrozen = isFrozenFace[neiFacei];
+                            if (!isFrozen && ownPatch[neiFacei] == -1)
                             {
-                                newOwnPatch[neiFaces[j]] = ownPatch[faceI];
+                                newOwnPatch[neiFacei] = ownPatch[facei];
                             }
                         }
                     }
@@ -4680,17 +5163,16 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
     }
 
 
-
     // Subset
     // ~~~~~~
 
     // Get cells to remove
     DynamicList<label> cellsToRemove(mesh_.nCells());
-    forAll(cellRegion, cellI)
+    forAll(cellRegion, celli)
     {
-        if (cellRegion[cellI] == -1)
+        if (cellRegion[celli] == -1)
         {
-            cellsToRemove.append(cellI);
+            cellsToRemove.append(celli);
         }
     }
     cellsToRemove.shrink();
@@ -4706,29 +5188,51 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
     removeCells cellRemover(mesh_);
 
     // Pick up patches for exposed faces
-    labelList exposedFaces(cellRemover.getExposedFaces(cellsToRemove));
+    const labelList exposedFaces(cellRemover.getExposedFaces(cellsToRemove));
     labelList exposedPatches(exposedFaces.size());
 
+    label nUnpatched = 0;
+
     forAll(exposedFaces, i)
     {
-        label faceI = exposedFaces[i];
+        label facei = exposedFaces[i];
 
-        if (ownPatch[faceI] != -1)
+        if (ownPatch[facei] != -1)
         {
-            exposedPatches[i] = ownPatch[faceI];
+            exposedPatches[i] = ownPatch[facei];
         }
         else
         {
-            WarningInFunction
-                << "For exposed face " << faceI
-                << " fc:" << mesh_.faceCentres()[faceI]
-                << " found no patch." << endl
-                << "    Taking patch " << defaultPatch
-                << " instead." << endl;
-            exposedPatches[i] = defaultPatch;
+            const label fallbackPatch =
+            (
+                nearestOwnPatch.size()
+              ? nearestOwnPatch[facei]
+              : defaultPatch
+            );
+            if (nUnpatched == 0)
+            {
+                WarningInFunction
+                    << "For exposed face " << facei
+                    << " fc:" << mesh_.faceCentres()[facei]
+                    << " found no patch." << endl
+                    << "    Taking patch " << fallbackPatch
+                    << " instead. Suppressing future warnings" << endl;
+            }
+            nUnpatched++;
+
+            exposedPatches[i] = fallbackPatch;
         }
     }
 
+    reduce(nUnpatched, sumOp<label>());
+    if (nUnpatched > 0)
+    {
+        Info<< "Detected " << nUnpatched << " faces out of "
+            << returnReduce(exposedFaces.size(), sumOp<label>())
+            << " for which the default patch " << defaultPatch
+            << " will be used" << endl;
+    }
+
     return doRemoveCells
     (
         cellsToRemove,
@@ -4746,7 +5250,8 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::removeLimitShells
     const labelList& globalToMasterPatch,
     const labelList& globalToSlavePatch,
     const pointField& locationsInMesh,
-    const wordList& zonesInMesh
+    const wordList& zonesInMesh,
+    const pointField& locationsOutsideMesh
 )
 {
     // Determine patches to put intersections into
@@ -4766,6 +5271,8 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::removeLimitShells
 
         locationsInMesh,
         zonesInMesh,
+        locationsOutsideMesh,
+        refPtr<writer<scalar>>(nullptr),
 
         neiLevel,
         neiCc,
@@ -4788,7 +5295,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::removeLimitShells
     {
         if (levelShell[celli] != -1)
         {
-            // Mark cell region so it gets deletec
+            // Mark cell region so it gets deleted
             cellRegion[celli] = -1;
         }
     }
@@ -5066,6 +5573,8 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
     const label nErodeCellZones,
     const pointField& locationsInMesh,
     const wordList& zonesInMesh,
+    const pointField& locationsOutsideMesh,
+    const refPtr<writer<scalar>>& leakPathFormatter,
     wordPairHashTable& zonesToFaceZone
 )
 {
@@ -5145,6 +5654,8 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
             -1,             // Set all cells with cellToZone -2 to -1
             locationsInMesh,
             zonesInMesh,
+            locationsOutsideMesh,
+            leakPathFormatter,
 
             cellToZone,
             unnamedRegion1,
@@ -5383,6 +5894,8 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
             if (debug)
             {
                 OBJstream str(mesh_.time().path()/"freeStanding.obj");
+                Pout<< "meshRefinement::zonify : dumping faceZone faces to "
+                    << str.name() << endl;
                 str.write(patch.localFaces(), patch.localPoints(), false);
             }
 
diff --git a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBlock.C b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBlock.C
index 4f23065d021..f17d933a832 100644
--- a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBlock.C
+++ b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementBlock.C
@@ -32,13 +32,27 @@ License
 #include "removeCells.H"
 #include "unitConversion.H"
 #include "bitSet.H"
+#include "volFields.H"
 
+// Leak path
+#include "shortestPathSet.H"
+#include "meshSearch.H"
+#include "topoDistanceData.H"
 #include "FaceCellWave.H"
+#include "removeCells.H"
+#include "regionSplit.H"
+
 #include "volFields.H"
 #include "wallPoints.H"
 #include "searchableSurfaces.H"
 #include "distributedTriSurfaceMesh.H"
 
+#include "holeToFace.H"
+#include "refinementParameters.H"
+#include "uindirectPrimitivePatch.H"
+#include "OBJstream.H"
+#include "PatchTools.H"
+
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 //Foam::label Foam::meshRefinement::markFakeGapRefinement
@@ -1128,4 +1142,1024 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::removeGapCells
 }
 
 
+void Foam::meshRefinement::selectIntersectedFaces
+(
+    const labelList& selectedSurfaces,
+    boolList& isBlockedFace
+) const
+{
+    // Like meshRefinement::selectSeparatedCoupledFaces. tbd: convert to bitSet
+
+    // Check that no connection between inside and outside points
+    isBlockedFace.setSize(mesh_.nFaces(), false);
+
+    // Block off separated couples.
+    selectSeparatedCoupledFaces(isBlockedFace);
+
+    // Block off intersections with selected surfaces
+
+    // Mark per face (for efficiency)
+    boolList isSelectedSurf(surfaces_.surfaces().size(), false);
+    UIndirectList<bool>(isSelectedSurf, selectedSurfaces) = true;
+
+    forAll(surfaceIndex_, facei)
+    {
+        const label surfi = surfaceIndex_[facei];
+        if (surfi != -1 && isSelectedSurf[surfi])
+        {
+            isBlockedFace[facei] = true;
+        }
+    }
+}
+
+
+//Foam::labelList Foam::meshRefinement::detectLeakCells
+//(
+//    const boolList& isBlockedFace,
+//    const labelList& leakFaces,
+//    const labelList& seedCells
+//) const
+//{
+//    int dummyTrackData = 0;
+//    List<topoDistanceData<label>> allFaceInfo(mesh_.nFaces());
+//    List<topoDistanceData<label>> allCellInfo(mesh_.nCells());
+//
+//    // Block faces
+//    forAll(isBlockedFace, facei)
+//    {
+//        if (isBlockedFace[facei])
+//        {
+//            allFaceInfo[facei] = topoDistanceData<label>(labelMax, 123);
+//        }
+//    }
+//    for (const label facei : leakFaces)
+//    {
+//        allFaceInfo[facei] = topoDistanceData<label>(labelMax, 123);
+//    }
+//
+//    // Walk from inside cell
+//    DynamicList<topoDistanceData<label>> faceDist;
+//    DynamicList<label> cFaces1;
+//    for (const label celli : seedCells)
+//    {
+//        if (celli != -1)
+//        {
+//            const labelList& cFaces = mesh_.cells()[celli];
+//            faceDist.reserve(cFaces.size());
+//            cFaces1.reserve(cFaces.size());
+//
+//            for (const label facei : cFaces)
+//            {
+//                if (!allFaceInfo[facei].valid(dummyTrackData))
+//                {
+//                    cFaces1.append(facei);
+//                    faceDist.append(topoDistanceData<label>(0, 123));
+//                }
+//            }
+//        }
+//    }
+//
+//    // Walk through face-cell wave till all cells are reached
+//    FaceCellWave<topoDistanceData<label>> wallDistCalc
+//    (
+//        mesh_,
+//        cFaces1,
+//        faceDist,
+//        allFaceInfo,
+//        allCellInfo,
+//        mesh_.globalData().nTotalCells()+1   // max iterations
+//    );
+//
+//    label nRemove = 0;
+//    for (const label facei : leakFaces)
+//    {
+//        const label own = mesh_.faceOwner()[facei];
+//        if (!allCellInfo[own].valid(dummyTrackData))
+//        {
+//            nRemove++;
+//        }
+//        if (mesh_.isInternalFace(facei))
+//        {
+//            const label nei = mesh_.faceNeighbour()[facei];
+//            if (!allCellInfo[nei].valid(dummyTrackData))
+//            {
+//                nRemove++;
+//            }
+//        }
+//    }
+//
+//    labelList cellsToRemove(nRemove);
+//    nRemove = 0;
+//    for (const label facei : leakFaces)
+//    {
+//        const label own = mesh_.faceOwner()[facei];
+//        if (!allCellInfo[own].valid(dummyTrackData))
+//        {
+//            cellsToRemove[nRemove++] = own;
+//        }
+//        if (mesh_.isInternalFace(facei))
+//        {
+//            const label nei = mesh_.faceNeighbour()[facei];
+//            if (!allCellInfo[nei].valid(dummyTrackData))
+//            {
+//                cellsToRemove[nRemove++] = nei;
+//            }
+//        }
+//    }
+//
+//    if (debug)
+//    {
+//        volScalarField fld
+//        (
+//            IOobject
+//            (
+//                "cellsToKeep",
+//                mesh_.time().timeName(),
+//                mesh_,
+//                IOobject::NO_READ,
+//                IOobject::NO_WRITE
+//            ),
+//            mesh_,
+//            dimensionedScalar(dimless, Zero)
+//        );
+//        forAll(allCellInfo, celli)
+//        {
+//            if (allCellInfo[celli].valid(dummyTrackData))
+//            {
+//                fld[celli] = allCellInfo[celli].distance();
+//            }
+//        }
+//        forAll(fld.boundaryField(), patchi)
+//        {
+//            const polyPatch& pp = mesh_.boundaryMesh()[patchi];
+//            SubList<topoDistanceData<label>> p(pp.patchSlice(allFaceInfo));
+//            scalarField pfld
+//            (
+//                fld.boundaryField()[patchi].size(),
+//                Zero
+//            );
+//            forAll(pfld, i)
+//            {
+//                if (p[i].valid(dummyTrackData))
+//                {
+//                    pfld[i] = 1.0*p[i].distance();
+//                }
+//            }
+//            fld.boundaryFieldRef()[patchi] == pfld;
+//        }
+//        //Note: do not swap cell values so do not do
+//        //fld.correctBoundaryConditions();
+//        Pout<< "Writing distance field for initial cells "
+//            << seedCells << " to " << fld.objectPath() << endl;
+//        fld.write();
+//    }
+//
+//    return cellsToRemove;
+//}
+//
+//
+//Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::removeLeakCells
+//(
+//    const labelList& globalToMasterPatch,
+//    const labelList& globalToSlavePatch,
+//    const pointField& locationsInMesh,
+//    const wordList& zonesInMesh,
+//    const pointField& locationsOutsideMesh,
+//    const labelList& selectedSurfaces
+//)
+//{
+//    boolList isBlockedFace;
+//    selectIntersectedFaces(selectedSurfaces, isBlockedFace);
+//
+//    // Determine cell regions
+//    const regionSplit cellRegion(mesh_, isBlockedFace);
+//
+//    // Detect locationsInMesh regions
+//    labelList insideCells(locationsInMesh.size(), -1);
+//    labelList insideRegions(locationsInMesh.size(), -1);
+//    forAll(locationsInMesh, i)
+//    {
+//        insideCells[i] = findCell
+//        (
+//            mesh_,
+//            mergeDistance_*vector::one,   //perturbVec,
+//            locationsInMesh[i]
+//        );
+//        if (insideCells[i] != -1)
+//        {
+//            insideRegions[i] = cellRegion[insideCells[i]];
+//        }
+//        reduce(insideRegions[i], maxOp<label>());
+//
+//        if (insideRegions[i] == -1)
+//        {
+//            // See if we can perturb a bit
+//            insideCells[i] = findCell
+//            (
+//                mesh_,
+//                mergeDistance_*vector::one,   //perturbVec,
+//                locationsInMesh[i]+mergeDistance_*vector::one
+//            );
+//            if (insideCells[i] != -1)
+//            {
+//                insideRegions[i] = cellRegion[insideCells[i]];
+//            }
+//            reduce(insideRegions[i], maxOp<label>());
+//
+//            if (insideRegions[i] == -1)
+//            {
+//                FatalErrorInFunction
+//                    << "Cannot find locationInMesh " << locationsInMesh[i]
+//                    << " on any processor" << exit(FatalError);
+//            }
+//        }
+//    }
+//
+//
+//    // Check that all the locations outside the
+//    // mesh do not conflict with those inside
+//
+//    bool haveLeak = false;
+//    forAll(locationsOutsideMesh, i)
+//    {
+//        // Find the region containing the point
+//        label regioni = findRegion
+//        (
+//            mesh_,
+//            cellRegion,
+//            mergeDistance_*vector::one,   //perturbVec,
+//            locationsOutsideMesh[i]
+//        );
+//
+//        if (regioni != -1)
+//        {
+//            // Check for locationsOutsideMesh overlapping with inside ones
+//            if (insideRegions.find(regioni) != -1)
+//            {
+//                haveLeak = true;
+//                WarningInFunction
+//                    << "Outside location " << locationsOutsideMesh[i]
+//                    << " in region " << regioni
+//                    << " is connected to one of the inside points "
+//                    << locationsInMesh << endl;
+//            }
+//        }
+//    }
+//
+//
+//    autoPtr<mapPolyMesh> mapPtr;
+//    if (returnReduce(haveLeak, orOp<bool>()))
+//    {
+//        // Use shortestPathSet to provide a minimum set of faces needed
+//        // to close hole. Tbd: maybe directly use wave?
+//        meshSearch searchEngine(mesh_);
+//        shortestPathSet leakPath
+//        (
+//            "leakPath",
+//            mesh_,
+//            searchEngine,
+//            coordSet::coordFormatNames[coordSet::coordFormat::DISTANCE],
+//            true,
+//            50,    // tbd. Number of iterations. This is the maximum
+//                    // number of faces in the leak hole
+//
+//            //pbm.groupPatchIDs()["wall"],  // patch to grow from
+//            meshedPatches(),   // patch to grow from
+//
+//            locationsInMesh,
+//            locationsOutsideMesh,
+//            isBlockedFace
+//        );
+//
+//
+//        // Use leak path to find minimum set of cells to delete
+//        const labelList cellsToRemove
+//        (
+//            detectLeakCells
+//            (
+//                isBlockedFace,
+//                leakPath.leakFaces(),
+//                insideCells
+//            )
+//        );
+//
+//        // Re-do intersections to find nearest unnamed surface
+//        const label defaultRegion
+//        (
+//            surfaces().globalRegion
+//            (
+//                selectedSurfaces[0],
+//                0
+//            )
+//        );
+//
+//        const labelList nearestRegion
+//        (
+//            nearestIntersection
+//            (
+//                selectedSurfaces,
+//                defaultRegion
+//            )
+//        );
+//
+//
+//        // Remove cells
+//        removeCells cellRemover(mesh_);
+//        const labelList exposedFaces
+//        (
+//            cellRemover.getExposedFaces(cellsToRemove)
+//        );
+//
+//        labelList exposedPatches(exposedFaces.size());
+//        forAll(exposedFaces, i)
+//        {
+//            label facei = exposedFaces[i];
+//            exposedPatches[i] = globalToMasterPatch[nearestRegion[facei]];
+//        }
+//
+//        mapPtr = doRemoveCells
+//        (
+//            cellsToRemove,
+//            exposedFaces,
+//            exposedPatches,
+//            cellRemover
+//        );
+//
+//
+//        // Put the exposed faces into a special faceZone
+//        {
+//            // Add to "frozenFaces" zone
+//            faceZoneMesh& faceZones = mesh_.faceZones();
+//
+//            // Get current frozen faces (if any)
+//            bitSet isFrozenFace(mesh_.nFaces());
+//            label zonei = faceZones.findZoneID("frozenFaces");
+//            if (zonei != -1)
+//            {
+//                const bitSet oldSet(mesh_.nFaces(), faceZones[zonei]);
+//                isFrozenFace.set(oldSet);
+//            }
+//
+//            // Add newly exposed faces (if not yet in any faceZone!)
+//            const labelList exposed
+//            (
+//                renumber
+//                (
+//                    mapPtr().reverseFaceMap(),
+//                    exposedFaces
+//                )
+//            );
+//            bitSet isZonedFace(mesh_.nFaces(), faceZones.zoneMap().toc());
+//            for (const label facei : exposed)
+//            {
+//                if (!isZonedFace[facei])
+//                {
+//                    isFrozenFace.set(facei);
+//                }
+//            }
+//
+//            syncTools::syncFaceList
+//            (
+//                mesh_,
+//                isFrozenFace,
+//                orEqOp<unsigned int>(),
+//                0u
+//            );
+//
+//            // Add faceZone if non existing
+//            faceZones.clearAddressing();
+//            if (zonei == -1)
+//            {
+//                zonei = faceZones.size();
+//                faceZones.setSize(zonei+1);
+//                faceZones.set
+//                (
+//                    zonei,
+//                    new faceZone
+//                    (
+//                        "frozenFaces",              // name
+//                        labelList(0),               // addressing
+//                        boolList(0),                // flip
+//                        zonei,                      // index
+//                        faceZones                   // faceZoneMesh
+//                    )
+//                );
+//            }
+//
+//            // Update faceZone with new contents
+//            labelList frozenFaces(isFrozenFace.toc());
+//            boolList frozenFlip(frozenFaces.size(), false);
+//
+//            faceZones[zonei].resetAddressing
+//            (
+//                std::move(frozenFaces),
+//                std::move(frozenFlip)
+//            );
+//        }
+//
+//
+//        //// Put the exposed points into a special pointZone
+//        //if (false)
+//        //{
+//        //    const labelList meshFaceIDs
+//        //    (
+//        //        renumber
+//        //        (
+//        //            mapPtr().reverseFaceMap(),
+//        //            exposedFaces
+//        //        )
+//        //    );
+//        //    const uindirectPrimitivePatch pp
+//        //    (
+//        //        UIndirectList<face>(mesh_.faces(), meshFaceIDs),
+//        //        mesh_.points()
+//        //    );
+//        //
+//        //    // Count number of faces per edge
+//        //    const labelListList& edgeFaces = pp.edgeFaces();
+//        //    labelList nEdgeFaces(edgeFaces.size());
+//        //    forAll(edgeFaces, edgei)
+//        //    {
+//        //        nEdgeFaces[edgei] = edgeFaces[edgei].size();
+//        //    }
+//        //
+//        //    // Sync across processor patches
+//        //    if (Pstream::parRun())
+//        //    {
+//        //        const globalMeshData& globalData = mesh_.globalData();
+//        //        const mapDistribute& map = globalData.globalEdgeSlavesMap();
+//        //        const indirectPrimitivePatch& cpp =
+//        //            globalData.coupledPatch();
+//        //
+//        //        // Match pp edges to coupled edges
+//        //        labelList patchEdges;
+//        //        labelList coupledEdges;
+//        //        PackedBoolList sameEdgeOrientation;
+//        //        PatchTools::matchEdges
+//        //        (
+//        //            pp,
+//        //            cpp,
+//        //            patchEdges,
+//        //            coupledEdges,
+//        //            sameEdgeOrientation
+//        //        );
+//        //
+//        //
+//        //        // Convert patch-edge data into cpp-edge data
+//        //        labelList coupledNEdgeFaces(map.constructSize(), Zero);
+//        //        UIndirectList<label>(coupledNEdgeFaces, coupledEdges) =
+//        //            UIndirectList<label>(nEdgeFaces, patchEdges);
+//        //
+//        //        // Synchronise
+//        //        globalData.syncData
+//        //        (
+//        //            coupledNEdgeFaces,
+//        //            globalData.globalEdgeSlaves(),
+//        //            globalData.globalEdgeTransformedSlaves(),
+//        //            map,
+//        //            plusEqOp<label>()
+//        //        );
+//        //
+//        //        // Convert back from cpp-edge to patch-edge
+//        //        UIndirectList<label>(nEdgeFaces, patchEdges) =
+//        //            UIndirectList<label>(coupledNEdgeFaces, coupledEdges);
+//        //    }
+//        //
+//        //    // Freeze all internal points
+//        //    bitSet isFrozenPoint(mesh_.nPoints());
+//        //    forAll(nEdgeFaces, edgei)
+//        //    {
+//        //        if (nEdgeFaces[edgei] != 1)
+//        //        {
+//        //            const edge& e = pp.edges()[edgei];
+//        //            isFrozenPoint.set(pp.meshPoints()[e[0]]);
+//        //            isFrozenPoint.set(pp.meshPoints()[e[1]]);
+//        //        }
+//        //    }
+//        //    // Add to "frozenPoints" zone
+//        //    pointZoneMesh& pointZones = mesh_.pointZones();
+//        //    pointZones.clearAddressing();
+//        //    label zonei = pointZones.findZoneID("frozenPoints");
+//        //    if (zonei != -1)
+//        //    {
+//        //        const bitSet oldSet(mesh_.nPoints(), pointZones[zonei]);
+//        //        // Add to isFrozenPoint
+//        //        isFrozenPoint.set(oldSet);
+//        //    }
+//        //
+//        //    syncTools::syncPointList
+//        //    (
+//        //        mesh_,
+//        //        isFrozenPoint,
+//        //        orEqOp<unsigned int>(),
+//        //        0u
+//        //    );
+//        //
+//        //    if (zonei == -1)
+//        //    {
+//        //        zonei = pointZones.size();
+//        //        pointZones.setSize(zonei+1);
+//        //        pointZones.set
+//        //        (
+//        //            zonei,
+//        //            new pointZone
+//        //            (
+//        //                "frozenPoints",             // name
+//        //                isFrozenPoint.sortedToc(),  // addressing
+//        //                zonei,                      // index
+//        //                pointZones                  // pointZoneMesh
+//        //            )
+//        //        );
+//        //    }
+//        //}
+//
+//
+//        if (debug&meshRefinement::MESH)
+//        {
+//            const_cast<Time&>(mesh_.time())++;
+//
+//            Pout<< "Writing current mesh to time "
+//                << timeName() << endl;
+//            write
+//            (
+//                meshRefinement::debugType(debug),
+//                meshRefinement::writeType
+//                (
+//                    meshRefinement::writeLevel()
+//                  | meshRefinement::WRITEMESH
+//                ),
+//                mesh_.time().path()/timeName()
+//            );
+//            Pout<< "Dumped mesh in = "
+//                << mesh_.time().cpuTimeIncrement() << " s\n" << nl << endl;
+//        }
+//    }
+//    return mapPtr;
+//}
+
+
+//Foam::autoPtr<Foam::mapDistribute> Foam::meshRefinement::nearestFace
+//(
+//    const globalIndex& globalSeedFaces,
+//    const labelList& seedFaces,
+//    const labelList& closureFaces
+//) const
+//{
+//    // Takes a set of faces for which we have information (seedFaces,
+//    // globalSeedFaces - these are e.g. intersected faces) and walks out
+//    // across nonSeedFace. Returns for
+//    // every nonSeedFace the nearest seed face (in global indexing).
+//    // Used e.g. in hole closing. Assumes that seedFaces, closureFaces
+//    // are a small subset of the master
+//    // so are not large - it uses edge addressing on the closureFaces
+//
+//
+//    if (seedFaces.size() != globalSeedFaces.localSize())
+//    {
+//        FatalErrorInFunction << "problem : seedFaces:" << seedFaces.size()
+//            << " globalSeedFaces:" << globalSeedFaces.localSize()
+//            << exit(FatalError);
+//    }
+//
+//    //// Mark mesh points that are used by any closureFaces. This is for
+//    //// initial filtering
+//    //bitSet isNonSeedPoint(mesh.nPoints());
+//    //for (const label facei : closureFaces)
+//    //{
+//    //    isNonSeedPoint.set(mesh_.faces()[facei]);
+//    //}
+//    //syncTools::syncPointList
+//    //(
+//    //    mesh_,
+//    //    isNonSeedPoint,
+//    //    orEqOp<unsigned int>(),
+//    //    0u
+//    //);
+//
+//    // Make patch
+//    const uindirectPrimitivePatch pp
+//    (
+//        IndirectList<face>(mesh_.faces(), closureFaces),
+//        mesh_.points()
+//    );
+//    const edgeList& edges = pp.edges();
+//    const labelList& mp = pp.meshPoints();
+//    const label nBndEdges = pp.nEdges() - pp.nInternalEdges();
+//
+//    // For all faces in seedFaces mark the edge with a face. No special
+//    // handling for multiple faces sharing the edge - first one wins
+//    EdgeMap<label> edgeMap(pp.nEdges());
+//    for (const label facei : seedFaces)
+//    {
+//        const label globalFacei = globalSeedFaces.toGlobal(facei);
+//        const face& f = mesh_.faces()[facei];
+//        forAll(f, fp)
+//        {
+//            label nextFp = f.fcIndex(fp);
+//            edgeMap.insert(edge(f[fp], f[nextFp]), globalFacei);
+//        }
+//    }
+//    syncTools::syncEdgeMap(mesh_, edgeMap, maxEqOp<label>());
+//
+//
+//
+//    // Seed
+//    DynamicList<label> initialEdges(2*nBndEdges);
+//    DynamicList<edgeTopoDistanceData<label, uindirectPrimitivePatch>>
+//        initialEdgesInfo(2*nBndEdges);
+//    forAll(edges, edgei)
+//    {
+//        const edge& e = edges[edgei];
+//        const edge meshE = edge(mp[e[0]], mp[e[1]]);
+//
+//        EdgeMap<label>::const_iterator iter = edgeMap.find(meshE);
+//        if (iter.found())
+//        {
+//            initialEdges.append(edgei);
+//            initialEdgesInfo.append
+//            (
+//                edgeTopoDistanceData<label, uindirectPrimitivePatch>
+//                (
+//                    0,          // distance
+//                    iter()      // globalFacei
+//                )
+//            );
+//        }
+//    }
+//
+//    // Data on all edges and faces
+//    List<edgeTopoDistanceData<label, uindirectPrimitivePatch>> allEdgeInfo
+//    (
+//        pp.nEdges()
+//    );
+//    List<edgeTopoDistanceData<label, uindirectPrimitivePatch>> allFaceInfo
+//    (
+//        pp.size()
+//    );
+//
+//    // Walk
+//    PatchEdgeFaceWave
+//    <
+//        uindirectPrimitivePatch,
+//        edgeTopoDistanceData<label, uindirectPrimitivePatch>
+//    > calc
+//    (
+//        mesh_,
+//        pp,
+//        initialEdges,
+//        initialEdgesInfo,
+//        allEdgeInfo,
+//        allFaceInfo,
+//        returnReduce(pp.nEdges(), sumOp<label>())
+//    );
+//
+//
+//    // Per non-seed face the seed face
+//    labelList closureToSeed(pp.size(), -1);
+//    forAll(allFaceInfo, facei)
+//    {
+//        if (allFaceInfo[facei].valid(calc.data()))
+//        {
+//            closureToSeed[facei] = allFaceInfo[facei].data();
+//        }
+//    }
+//
+//    List<Map<label>> compactMap;
+//    return autoPtr<mapDistribute>::New
+//    (
+//        globalSeedFaces,
+//        closureToSeed,
+//        compactMap
+//    );
+//}
+
+
+Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::blockLeakFaces
+(
+    const labelList& globalToMasterPatch,
+    const labelList& globalToSlavePatch,
+    const pointField& locationsInMesh,
+    const wordList& zonesInMesh,
+    const pointField& locationsOutsideMesh,
+    const labelList& selectedSurfaces
+)
+{
+    // Problem: this is based on cached intersection information. This might
+    // not include the surface we actually want to use. In which case the
+    // surface would not be seen as intersected!
+    boolList isBlockedFace;
+    selectIntersectedFaces(selectedSurfaces, isBlockedFace);
+
+    // Determine cell regions
+    const regionSplit cellRegion(mesh_, isBlockedFace);
+
+    // Detect locationsInMesh regions
+    labelList insideCells(locationsInMesh.size(), -1);
+    labelList insideRegions(locationsInMesh.size(), -1);
+    forAll(locationsInMesh, i)
+    {
+        insideCells[i] = findCell
+        (
+            mesh_,
+            mergeDistance_*vector::one,   //perturbVec,
+            locationsInMesh[i]
+        );
+        if (insideCells[i] != -1)
+        {
+            insideRegions[i] = cellRegion[insideCells[i]];
+        }
+        reduce(insideRegions[i], maxOp<label>());
+
+        if (insideRegions[i] == -1)
+        {
+            // See if we can perturb a bit
+            insideCells[i] = findCell
+            (
+                mesh_,
+                mergeDistance_*vector::one,   //perturbVec,
+                locationsInMesh[i]+mergeDistance_*vector::one
+            );
+            if (insideCells[i] != -1)
+            {
+                insideRegions[i] = cellRegion[insideCells[i]];
+            }
+            reduce(insideRegions[i], maxOp<label>());
+
+            if (insideRegions[i] == -1)
+            {
+                FatalErrorInFunction
+                    << "Cannot find locationInMesh " << locationsInMesh[i]
+                    << " on any processor" << exit(FatalError);
+            }
+        }
+    }
+
+
+    // Check that all the locations outside the
+    // mesh do not conflict with those inside
+
+    bool haveLeak = false;
+    forAll(locationsOutsideMesh, i)
+    {
+        // Find the region containing the point
+        label regioni = findRegion
+        (
+            mesh_,
+            cellRegion,
+            mergeDistance_*vector::one,   //perturbVec,
+            locationsOutsideMesh[i]
+        );
+
+        if (regioni != -1)
+        {
+            // Check for locationsOutsideMesh overlapping with inside ones
+            if (insideRegions.find(regioni) != -1)
+            {
+                haveLeak = true;
+                WarningInFunction
+                    << "Outside location " << locationsOutsideMesh[i]
+                    << " in region " << regioni
+                    << " is connected to one of the inside points "
+                    << locationsInMesh << endl;
+            }
+        }
+    }
+
+
+    autoPtr<mapPolyMesh> mapPtr;
+    if (returnReduce(haveLeak, orOp<bool>()))
+    {
+        // Use holeToFace to provide a minimum set of faces needed
+        // to close hole.
+
+        const List<pointField> allLocations
+        (
+            refinementParameters::zonePoints
+            (
+                locationsInMesh,
+                zonesInMesh,
+                locationsOutsideMesh
+            )
+        );
+
+        const labelList blockingFaces(findIndices(isBlockedFace, true));
+
+        labelList closureFaces;
+        labelList closureToBlocked;
+        autoPtr<mapDistribute> closureMapPtr;
+        {
+            const globalIndex globalBlockingFaces(blockingFaces.size());
+
+            closureMapPtr = holeToFace::calcClosure
+            (
+                mesh_,
+                allLocations,
+                blockingFaces,
+                globalBlockingFaces,
+                true,                   // allow erosion
+
+                closureFaces,
+                closureToBlocked
+            );
+
+            if (debug)
+            {
+                Pout<< "meshRefinement::blockLeakFaces :"
+                    << " found closure faces:" << closureFaces.size()
+                    << "  map:" << closureMapPtr.valid() << endl;
+            }
+
+            if (!closureMapPtr.valid())
+            {
+                FatalErrorInFunction
+                    << "have leak but did not find any closure faces"
+                    << exit(FatalError);
+            }
+        }
+
+        // Baffle faces
+        labelList ownPatch(mesh_.nFaces(), -1);
+        labelList neiPatch(mesh_.nFaces(), -1);
+
+        // Keep (global) boundary faces in their patch
+        {
+            const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+            for (label patchi = 0; patchi < patches.nNonProcessor(); ++patchi)
+            {
+                const polyPatch& pp = patches[patchi];
+
+                forAll(pp, i)
+                {
+                    ownPatch[pp.start()+i] = patchi;
+                    neiPatch[pp.start()+i] = patchi;
+                }
+            }
+        }
+
+        const faceZoneMesh& fzs = mesh_.faceZones();
+
+        // Mark zone per face
+        labelList faceToZone(mesh_.nFaces(), -1);
+        boolList faceToFlip(mesh_.nFaces(), false);
+        forAll(fzs, zonei)
+        {
+            const labelList& addressing = fzs[zonei];
+            const boolList& flipMap = fzs[zonei].flipMap();
+
+            forAll(addressing, i)
+            {
+                faceToZone[addressing[i]] = zonei;
+                faceToFlip[addressing[i]] = flipMap[i];
+            }
+        }
+
+
+        // Fetch patch and zone info from blockingFaces
+        labelList packedOwnPatch(labelUIndList(ownPatch, blockingFaces));
+        closureMapPtr->distribute(packedOwnPatch);
+        labelList packedNeiPatch(labelUIndList(neiPatch, blockingFaces));
+        closureMapPtr->distribute(packedNeiPatch);
+        labelList packedZone(labelUIndList(faceToZone, blockingFaces));
+        closureMapPtr->distribute(packedZone);
+        boolList packedFlip(UIndirectList<bool>(faceToFlip, blockingFaces));
+        closureMapPtr->distribute(packedFlip);
+        forAll(closureFaces, i)
+        {
+            const label facei = closureFaces[i];
+            const label sloti = closureToBlocked[i];
+
+            if (sloti != -1)
+            {
+                // TBD. how to orient own/nei patch?
+                ownPatch[facei] = packedOwnPatch[sloti];
+                neiPatch[facei] = packedNeiPatch[sloti];
+                faceToZone[facei] = packedZone[sloti];
+                faceToFlip[facei] = packedFlip[sloti];
+            }
+        }
+
+
+        // Add faces to faceZone. For now do this outside of createBaffles
+        // below
+        {
+            List<DynamicList<label>> zoneToFaces(fzs.size());
+            List<DynamicList<bool>> zoneToFlip(fzs.size());
+
+            // Add any to-be-patched face
+            forAll(faceToZone, facei)
+            {
+                const label zonei = faceToZone[facei];
+                if (zonei != -1)
+                {
+                    zoneToFaces[zonei].append(facei);
+                    zoneToFlip[zonei].append(faceToFlip[facei]);
+                }
+            }
+
+            forAll(zoneToFaces, zonei)
+            {
+                surfaceZonesInfo::addFaceZone
+                (
+                    fzs[zonei].name(),
+                    zoneToFaces[zonei],
+                    zoneToFlip[zonei],
+                    mesh_
+                );
+            }
+        }
+
+        // Put the points of closureFaces into a special pointZone
+        {
+            const uindirectPrimitivePatch pp
+            (
+                UIndirectList<face>(mesh_.faces(), closureFaces),
+                mesh_.points()
+            );
+
+            // Count number of faces per edge
+            const labelList nEdgeFaces(countEdgeFaces(pp));
+
+            // Freeze all internal points
+            bitSet isFrozenPoint(mesh_.nPoints());
+            forAll(nEdgeFaces, edgei)
+            {
+                if (nEdgeFaces[edgei] != 1)
+                {
+                    const edge& e = pp.edges()[edgei];
+                    isFrozenPoint.set(pp.meshPoints()[e[0]]);
+                    isFrozenPoint.set(pp.meshPoints()[e[1]]);
+                }
+            }
+
+            // Lookup/add pointZone and include its points
+            pointZoneMesh& pointZones =
+                const_cast<pointZoneMesh&>(mesh_.pointZones());
+            const label zonei = addPointZone("frozenPoints");
+            const bitSet oldSet(mesh_.nPoints(), pointZones[zonei]);
+            isFrozenPoint.set(oldSet);
+
+            syncTools::syncPointList
+            (
+                mesh_,
+                isFrozenPoint,
+                orEqOp<unsigned int>(),
+                0u
+            );
+
+            // Override addressing
+            pointZones.clearAddressing();
+            pointZones[zonei] = isFrozenPoint.sortedToc();
+        }
+
+
+
+        // Create baffles for faces
+        mapPtr = createBaffles(ownPatch, neiPatch);
+
+        //// Put the exposed faces into a special faceZone
+        //{
+        //    // Add newly exposed faces (if not yet in any faceZone!)
+        //    const labelList exposed
+        //    (
+        //        renumber
+        //        (
+        //            mapPtr().reverseFaceMap(),
+        //            blockingFaces
+        //        )
+        //    );
+        //
+        //    surfaceZonesInfo::addFaceZone
+        //    (
+        //        "frozenFaces",
+        //        exposed,
+        //        boolList(exposed.size(), false),
+        //        mesh_
+        //    );
+        //}
+
+
+        if (debug&meshRefinement::MESH)
+        {
+            const_cast<Time&>(mesh_.time())++;
+
+            Pout<< "Writing current mesh to time "
+                << timeName() << endl;
+            write
+            (
+                meshRefinement::debugType(debug),
+                meshRefinement::writeType
+                (
+                    meshRefinement::writeLevel()
+                  | meshRefinement::WRITEMESH
+                ),
+                mesh_.time().path()/timeName()
+            );
+            Pout<< "Dumped mesh in = "
+                << mesh_.time().cpuTimeIncrement() << " s\n" << nl << endl;
+        }
+    }
+    return mapPtr;
+}
+
+
 // ************************************************************************* //
diff --git a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementProblemCells.C b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementProblemCells.C
index 58d964a33e6..fa541ba8a6e 100644
--- a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementProblemCells.C
+++ b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementProblemCells.C
@@ -380,12 +380,15 @@ bool Foam::meshRefinement::isCollapsedCell
 // are not baffled at this stage)
 // Used to remove cells by baffling all their faces and have the
 // splitMeshRegions chuck away non used regions.
-Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
+void Foam::meshRefinement::markFacesOnProblemCells
 (
     const dictionary& motionDict,
     const bool removeEdgeConnectedCells,
     const scalarField& perpendicularAngle,
-    const labelList& globalToPatch
+    const labelList& globalToPatch,
+
+    labelList& facePatch,
+    labelList& faceZone
 ) const
 {
     const labelList& cellLevel = meshCutter_.cellLevel();
@@ -401,7 +404,7 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
 
     // Fill boundary data. All elements on meshed patches get marked.
     // Get the labels of added patches.
-    labelList adaptPatchIDs(meshedPatches());
+    const labelList adaptPatchIDs(meshedPatches());
 
     forAll(adaptPatchIDs, i)
     {
@@ -424,13 +427,18 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
     }
 
 
-    // Per face the nearest adaptPatch
-    const labelList nearestAdaptPatch(nearestPatch(adaptPatchIDs));
+    // Per mesh face the nearest adaptPatch and its faceZone (if any)
+    labelList nearestAdaptPatch;
+    labelList nearestAdaptZone;
+    nearestPatch(adaptPatchIDs, nearestAdaptPatch, nearestAdaptZone);
 
 
     // Per internal face (boundary faces not used) the patch that the
     // baffle should get (or -1)
-    labelList facePatch(mesh_.nFaces(), -1);
+    facePatch.setSize(mesh_.nFaces());
+    facePatch = -1;
+    faceZone.setSize(mesh_.nFaces());
+    faceZone = -1;
 
     // Swap neighbouring cell centres and cell level
     labelList neiLevel(mesh_.nBoundaryFaces());
@@ -474,6 +482,7 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
                 if (facePatch[facei] == -1 && mesh_.isInternalFace(facei))
                 {
                     facePatch[facei] = nearestAdaptPatch[facei];
+                    faceZone[facei] = nearestAdaptZone[facei];
                     nBaffleFaces++;
 
                     // Mark face as a 'boundary'
@@ -714,6 +723,7 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
                         )
                         {
                             facePatch[facei] = nearestAdaptPatch[facei];
+                            faceZone[facei] = nearestAdaptZone[facei];
                             nBaffleFaces++;
 
                             // Mark face as a 'boundary'
@@ -795,6 +805,7 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
                             )
                             {
                                 facePatch[facei] = nearestAdaptPatch[facei];
+                                faceZone[facei] = nearestAdaptZone[facei];
                                 nBaffleFaces++;
 
                                 // Mark face as a 'boundary'
@@ -882,6 +893,7 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
                 else
                 {
                     facePatch[facei] = nearestAdaptPatch[facei];
+                    faceZone[facei] = nearestAdaptZone[facei];
                     nBaffleFaces++;
 
                     // Do NOT update boundary data since this would grow blocked
@@ -937,6 +949,7 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
                         else
                         {
                             facePatch[facei] = nearestAdaptPatch[facei];
+                            faceZone[facei] = nearestAdaptZone[facei];
                             if (isMasterFace.test(facei))
                             {
                                 ++nBaffleFaces;
@@ -964,6 +977,12 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
             facePatch,
             maxEqOp<label>()
         );
+        syncTools::syncFaceList
+        (
+            mesh_,
+            faceZone,
+            maxEqOp<label>()
+        );
     }
 
     Info<< "markFacesOnProblemCells : marked "
@@ -978,26 +997,27 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
             << " internal faces from getting converted into baffles."
             << endl;
     }
-
-    return facePatch;
 }
 
 
 // Mark faces to be baffled to prevent snapping problems. Does
 // test to find nearest surface and checks which faces would get squashed.
-Foam::labelList Foam::meshRefinement::markFacesOnProblemCellsGeometric
+void Foam::meshRefinement::markFacesOnProblemCellsGeometric
 (
     const snapParameters& snapParams,
     const dictionary& motionDict,
     const labelList& globalToMasterPatch,
-    const labelList& globalToSlavePatch
+    const labelList& globalToSlavePatch,
+
+    labelList& facePatch,
+    labelList& faceZone
 ) const
 {
     pointField oldPoints(mesh_.points());
 
     // Repeat (most of) snappySnapDriver::doSnap
     {
-        labelList adaptPatchIDs(meshedPatches());
+        const labelList adaptPatchIDs(meshedPatches());
 
         // Construct addressing engine.
         autoPtr<indirectPrimitivePatch> ppPtr
@@ -1106,11 +1126,17 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCellsGeometric
 
 
     // Per face the nearest adaptPatch
-    const labelList nearestAdaptPatch(nearestPatch(meshedPatches()));
+    //const labelList nearestAdaptPatch(nearestPatch(meshedPatches()));
+    labelList nearestAdaptPatch;
+    labelList nearestAdaptZone;
+    nearestPatch(meshedPatches(), nearestAdaptPatch, nearestAdaptZone);
 
     // Per face (internal or coupled!) the patch that the
     // baffle should get (or -1).
-    labelList facePatch(mesh_.nFaces(), -1);
+    facePatch.setSize(mesh_.nFaces());
+    facePatch = -1;
+    faceZone.setSize(mesh_.nFaces());
+    faceZone = -1;
     // Count of baffled faces
     label nBaffleFaces = 0;
 
@@ -1218,6 +1244,7 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCellsGeometric
             if (patchi == -1 || mesh_.boundaryMesh()[patchi].coupled())
             {
                 facePatch[facei] = nearestAdaptPatch[facei];
+                faceZone[facei] = nearestAdaptZone[facei];
                 nBaffleFaces++;
 
                 //Pout<< "    " << facei
@@ -1244,8 +1271,12 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCellsGeometric
         facePatch,
         maxEqOp<label>()
     );
-
-    return facePatch;
+    syncTools::syncFaceList
+    (
+        mesh_,
+        faceZone,
+        maxEqOp<label>()
+    );
 }
 
 
diff --git a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementRefine.C b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementRefine.C
index 601f7ca6a2e..e8d9e308362 100644
--- a/src/mesh/snappyHexMesh/meshRefinement/meshRefinementRefine.C
+++ b/src/mesh/snappyHexMesh/meshRefinement/meshRefinementRefine.C
@@ -2310,6 +2310,7 @@ Foam::labelList Foam::meshRefinement::refineCandidates
         // Limit refinement
         // ~~~~~~~~~~~~~~~~
 
+        if (limitShells_.shells().size())
         {
             label nUnmarked = unmarkInternalRefinement(refineCell, nRefine);
             if (nUnmarked > 0)
diff --git a/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.C b/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.C
index c82f64f89a7..c98a71adab7 100644
--- a/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.C
+++ b/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.C
@@ -207,6 +207,7 @@ Foam::refinementSurfaces::refinementSurfaces
     PtrList<dictionary> globalPatchInfo(surfI);
 
     labelList globalBlockLevel(surfI, labelMax);
+    labelList globalLeakLevel(surfI, labelMax);
 
     // Per surface, per region data
     List<Map<label>> regionMinLevel(surfI);
@@ -218,6 +219,7 @@ Foam::refinementSurfaces::refinementSurfaces
     List<Map<scalar>> regionAngle(surfI);
     List<Map<autoPtr<dictionary>>> regionPatchInfo(surfI);
     List<Map<label>> regionBlockLevel(surfI);
+    List<Map<label>> regionLeakLevel(surfI);
 
     wordHashSet unmatchedKeys(surfacesDict.toc());
 
@@ -330,6 +332,7 @@ Foam::refinementSurfaces::refinementSurfaces
             }
             dict.readIfPresent("perpendicularAngle", globalAngle[surfI]);
             dict.readIfPresent("blockLevel", globalBlockLevel[surfI]);
+            dict.readIfPresent("leakLevel", globalLeakLevel[surfI]);
 
 
             if (dict.found("regions"))
@@ -455,6 +458,10 @@ Foam::refinementSurfaces::refinementSurfaces
                         {
                             regionBlockLevel[surfI].insert(regionI, l);
                         }
+                        if (regionDict.readIfPresent<label>("leakLevel", l))
+                        {
+                            regionLeakLevel[surfI].insert(regionI, l);
+                        }
                     }
                 }
             }
@@ -503,6 +510,8 @@ Foam::refinementSurfaces::refinementSurfaces
     patchInfo_.setSize(nRegions);
     blockLevel_.setSize(nRegions);
     blockLevel_ = labelMax;
+    leakLevel_.setSize(nRegions);
+    leakLevel_ = labelMax;
 
 
     forAll(globalMinLevel, surfI)
@@ -532,6 +541,7 @@ Foam::refinementSurfaces::refinementSurfaces
                 );
             }
             blockLevel_[globalRegionI] = globalBlockLevel[surfI];
+            leakLevel_[globalRegionI] = globalLeakLevel[surfI];
         }
 
         // Overwrite with region specific information
@@ -572,6 +582,7 @@ Foam::refinementSurfaces::refinementSurfaces
             const label globalRegionI = regionOffset_[surfI] + iter.key();
 
             blockLevel_[globalRegionI] = iter.val();
+            leakLevel_[globalRegionI] = iter.val();
         }
     }
 }
diff --git a/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.H b/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.H
index c96ad7e7115..16d8582c091 100644
--- a/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.H
+++ b/src/mesh/snappyHexMesh/refinementSurfaces/refinementSurfaces.H
@@ -96,6 +96,10 @@ class refinementSurfaces
         //  needs to apply
         labelList blockLevel_;
 
+        //- From global region number to cell level at which leakage detection
+        //  needs to apply
+        labelList leakLevel_;
+
         //- From global region number to small-gap level specification
         List<FixedList<label, 3>> extendedGapLevel_;
 
@@ -228,6 +232,13 @@ public:
                 return blockLevel_;
             }
 
+            //- From global region number to cell level at which leakage
+            //- detection is applied. labelMax if not set.
+            const labelList& leakLevel() const
+            {
+                return leakLevel_;
+            }
+
             //- From global region number to specification of gap and its
             //  refinement: 3 labels specifying
             //  - minimum wanted number of cells in the gap
diff --git a/src/mesh/snappyHexMesh/refinementSurfaces/surfaceZonesInfo.C b/src/mesh/snappyHexMesh/refinementSurfaces/surfaceZonesInfo.C
index d4efaf867ea..8903cfaa2db 100644
--- a/src/mesh/snappyHexMesh/refinementSurfaces/surfaceZonesInfo.C
+++ b/src/mesh/snappyHexMesh/refinementSurfaces/surfaceZonesInfo.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2013-2015 OpenFOAM Foundation
-    Copyright (C) 2015 OpenCFD Ltd.
+    Copyright (C) 2015-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -522,6 +522,7 @@ Foam::label Foam::surfaceZonesInfo::addFaceZone
     {
         zoneI = faceZones.size();
         faceZones.setSize(zoneI+1);
+
         faceZones.set
         (
             zoneI,
@@ -535,6 +536,75 @@ Foam::label Foam::surfaceZonesInfo::addFaceZone
             )
         );
     }
+//    else
+//    {
+//        // Already have faceZone. Add to addressing (if necessary)
+//
+//        faceZone& fz = faceZones[zoneI];
+//
+//        DebugVar(fz.size());
+//        DebugVar(fz.addressing().size());
+//
+//        if (fz.size() != addressing.size())
+//        {
+//            faceZones.clearAddressing();
+//            fz.resetAddressing(addressing, flipMap);
+//        }
+//        else
+//        {
+//            const labelList& oldAddressing = fz;
+//            const boolList& oldFlipMap = fz.flipMap();
+//
+//            bitSet isZoneFace(mesh.nFaces(), oldAddressing);
+//            bitSet isZoneFlip(mesh.nFaces());
+//            forAll(oldAddressing, i)
+//            {
+//                const label facei = oldAddressing[i];
+//                isZoneFlip[facei] = oldFlipMap[i];
+//            }
+//
+//            const bitSet newZoneFace(mesh.nFaces(), addressing);
+//            bitSet newZoneFlip(mesh.nFaces());
+//            forAll(addressing, i)
+//            {
+//                if (flipMap[i])
+//                {
+//                    newZoneFlip.set(addressing[i]);
+//                }
+//            }
+//
+//            bool isChanged = false;
+//            forAll(isZoneFace, facei)
+//            {
+//                if
+//                (
+//                    isZoneFace[facei] != newZoneFace[facei]
+//                 || isZoneFlip[facei] != newZoneFlip[facei]
+//                )
+//                {
+//                    isZoneFace[facei] = newZoneFace[facei];
+//                    isZoneFlip[facei] = newZoneFlip[facei];
+//                    isChanged = true;
+//                }
+//            }
+//
+//            if (isChanged)
+//            {
+//                labelList newAddressing(isZoneFace.sortedToc());
+//                boolList newFlip(newAddressing.size(), false);
+//                forAll(newAddressing, i)
+//                {
+//                    newFlip[i] = isZoneFlip[newAddressing[i]];
+//                }
+//                faceZones.clearAddressing();
+//                fz.resetAddressing
+//                (
+//                    std::move(newAddressing),
+//                    std::move(newFlip)
+//                );
+//            }
+//        }
+//    }
     return zoneI;
 }
 
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.C b/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.C
index 62b50bb0c5b..891ca1f0391 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.C
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.C
@@ -393,7 +393,8 @@ Foam::layerParameters::layerParameters
             "meshShrinker",
             medialAxisMeshMover::typeName
         )
-    )
+    ),
+    nOuterIter_(dict.getOrDefault<scalar>("nOuterIter", 1))
 {
     // Detect layer specification mode
 
@@ -693,6 +694,56 @@ Foam::layerParameters::layerParameters
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+Foam::scalar Foam::layerParameters::layerThickness
+(
+    const label nLayers,
+    const scalar layerThickness,    // overall layer thickness
+    const scalar expansionRatio,
+
+    const label layerStart,         // start in nLayers
+    const label layerSize           // size of slice of nLayers
+)
+{
+    if (layerSize == 0 || nLayers == 0)
+    {
+        return 0.0;
+    }
+    else if (layerSize > nLayers || layerStart >= nLayers)
+    {
+        FatalErrorInFunction    << "Illegal input for slice of layer:"
+            << " overall nLayers:" << nLayers
+            << " slice nLayers:" << layerSize
+            << " slice start:" << layerStart
+            << exit(FatalError);
+        return 0.0;
+    }
+    else if (mag(expansionRatio-1) < SMALL)
+    {
+        return layerThickness*layerSize/nLayers;
+    }
+    else
+    {
+        const scalar firstLayerThickness =
+            finalLayerThicknessRatio(nLayers, expansionRatio)
+          * layerThickness
+          / pow(expansionRatio, nLayers-1);
+
+        // Calculate thickness of single layer at layerStart
+        const scalar startThickness =
+            firstLayerThickness
+           *pow(expansionRatio, layerStart);
+
+        // See below for formula
+        const scalar thickness =
+            startThickness
+           *(1.0 - pow(expansionRatio, layerSize))
+           /(1.0 - expansionRatio);
+
+        return thickness;
+    }
+}
+
+
 Foam::scalar Foam::layerParameters::layerThickness
 (
     const thicknessModelType layerSpec,
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.H b/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.H
index 3c8778a6de9..88e4e1444d7 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.H
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/layerParameters/layerParameters.H
@@ -121,26 +121,28 @@ private:
             scalarField minThickness_;
 
 
-        scalar featureAngle_;
+        const scalar featureAngle_;
 
-        scalar mergePatchFacesAngle_;
+        const scalar mergePatchFacesAngle_;
 
-        scalar concaveAngle_;
+        const scalar concaveAngle_;
 
-        label nGrow_;
+        const label nGrow_;
 
-        scalar maxFaceThicknessRatio_;
+        const scalar maxFaceThicknessRatio_;
 
-        label nBufferCellsNoExtrude_;
+        const label nBufferCellsNoExtrude_;
 
-        label nLayerIter_;
+        const label nLayerIter_;
 
         label nRelaxedIter_;
 
         //- Any additional reporting
-        bool additionalReporting_;
+        const bool additionalReporting_;
 
-        word meshShrinker_;
+        const word meshShrinker_;
+
+        const label nOuterIter_;
 
 
     // Private Member Functions
@@ -277,6 +279,15 @@ public:
                 return nLayerIter_;
             }
 
+            //- Outer loop to add layer by layer. Can be set to >= max layers
+            //  in which case layers get added one at a time. This can help
+            //  layer insertion since the newly added layers get included in
+            //  the shrinking. Default is 1 -> add all layers in one go.
+            label nOuterIter() const
+            {
+                return nOuterIter_;
+            }
+
             //- Number of iterations after which relaxed motion rules
             //  are to be used.
             label nRelaxedIter() const
@@ -380,6 +391,16 @@ public:
                 const label nLayers,
                 const scalar expansionRatio
             );
+
+            //- Determine overall thickness of a slice (usually 1 layer)
+            static scalar layerThickness
+            (
+                const label nLayers,
+                const scalar layerThickness,    // overall thickness
+                const scalar expansionRatio,    // expansion ratio
+                const label layerStart,         // starting layer (0=firstLayer)
+                const label layerSize           // number of layers in slice
+            );
 };
 
 
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.C b/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.C
index 211b42411e2..16cfd83a766 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.C
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.C
@@ -311,4 +311,46 @@ Foam::labelList Foam::refinementParameters::unzonedLocations
 }
 
 
+Foam::List<Foam::pointField> Foam::refinementParameters::zonePoints
+(
+    const pointField& locationsInMesh,
+    const wordList& zonesInMesh,
+    const pointField& locationsOutsideMesh
+)
+{
+    // Sort locations according to zone. Add outside as last element
+    DynamicList<pointField> allLocations(zonesInMesh.size()+1);
+    DynamicList<word> allZoneNames(allLocations.size());
+
+    forAll(zonesInMesh, i)
+    {
+        const word name
+        (
+            zonesInMesh[i] == word::null
+          ? "none"
+          : zonesInMesh[i]
+        );
+        const point& pt = locationsInMesh[i];
+
+        const label index = allZoneNames.find(name);
+        if (index == -1)
+        {
+            allZoneNames.append(name);
+            allLocations.append(pointField(1, pt));
+        }
+        else
+        {
+            allLocations[index].append(pt);
+        }
+    }
+
+    allZoneNames.append("outside");
+    allLocations.append(locationsOutsideMesh);
+
+    allLocations.shrink();
+
+    return std::move(allLocations);
+}
+
+
 // ************************************************************************* //
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.H b/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.H
index cd7c4082ae2..9bdbba4f2e3 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.H
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/refinementParameters/refinementParameters.H
@@ -281,6 +281,15 @@ public:
             //- Extract indices of unnamed locations ('keepPoints')
             static labelList unzonedLocations(const wordList& zonesInMesh);
 
+            //- Helper: per zone (entry in zonesInMesh) the locations with
+            //          additionally locationsOutsideMesh as last. Used in
+            //          hole filling
+            static List<pointField> zonePoints
+            (
+                const pointField& locationsInMesh,
+                const wordList& zonesInMesh,
+                const pointField& locationsOutsideMesh
+            );
 };
 
 
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.C b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.C
index 5017e3b22cd..eef47627609 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.C
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.C
@@ -1005,7 +1005,6 @@ void Foam::snappyLayerDriver::setNumLayers
     const labelList& patchToNLayers,
     const labelList& patchIDs,
     const indirectPrimitivePatch& pp,
-    pointField& patchDisp,
     labelList& patchNLayers,
     List<extrudeMode>& extrudeStatus,
     label& nAddedCells
@@ -3364,169 +3363,117 @@ bool Foam::snappyLayerDriver::writeLayerData
 }
 
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::snappyLayerDriver::snappyLayerDriver
-(
-    meshRefinement& meshRefiner,
-    const labelList& globalToMasterPatch,
-    const labelList& globalToSlavePatch,
-    const bool dryRun
-)
-:
-    meshRefiner_(meshRefiner),
-    globalToMasterPatch_(globalToMasterPatch),
-    globalToSlavePatch_(globalToSlavePatch),
-    dryRun_(dryRun)
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::snappyLayerDriver::mergePatchFacesUndo
-(
-    const layerParameters& layerParams,
-    const dictionary& motionDict,
-    const meshRefinement::FaceMergeType mergeType
-)
-{
-    // Clip to 30 degrees. Not helpful!
-    //scalar planarAngle = min(30.0, layerParams.featureAngle());
-    scalar planarAngle = layerParams.mergePatchFacesAngle();
-    scalar minCos = Foam::cos(degToRad(planarAngle));
-
-    scalar concaveCos = Foam::cos(degToRad(layerParams.concaveAngle()));
-
-    Info<< nl
-        << "Merging all faces of a cell" << nl
-        << "---------------------------" << nl
-        << "    - which are on the same patch" << nl
-        << "    - which make an angle < " << planarAngle
-        << " degrees"
-        << " (cos:" << minCos << ')' << nl
-        << "    - as long as the resulting face doesn't become concave"
-        << " by more than "
-        << layerParams.concaveAngle() << " degrees" << nl
-        << "      (0=straight, 180=fully concave)" << nl
-        << endl;
-
-    const fvMesh& mesh = meshRefiner_.mesh();
-
-    List<labelPair> couples(localPointRegion::findDuplicateFacePairs(mesh));
-
-    labelList duplicateFace(mesh.nFaces(), -1);
-    forAll(couples, i)
-    {
-        const labelPair& cpl = couples[i];
-        duplicateFace[cpl[0]] = cpl[1];
-        duplicateFace[cpl[1]] = cpl[0];
-    }
-
-    label nChanged = meshRefiner_.mergePatchFacesUndo
-    (
-        minCos,
-        concaveCos,
-        meshRefiner_.meshedPatches(),
-        motionDict,
-        duplicateFace,
-        mergeType   // How to merge co-planar patch faces
-    );
-
-    nChanged += meshRefiner_.mergeEdgesUndo(minCos, motionDict);
-}
-
-
-void Foam::snappyLayerDriver::addLayers
+void Foam::snappyLayerDriver::dupFaceZonePoints
 (
-    const layerParameters& layerParams,
-    const dictionary& motionDict,
-    const labelList& patchIDs,
-    const label nAllowableErrors,
-    decompositionMethod& decomposer,
-    fvMeshDistribute& distributor
+    const labelList& patchIDs,  // patch indices
+    const labelList& numLayers, // number of layers per patch
+    List<labelPair> baffles,    // pairs of baffles (input & updated)
+    labelList& pointToMaster    // -1 or index of original point (duplicated
+                                // point)
 )
 {
     fvMesh& mesh = meshRefiner_.mesh();
 
+    // Check outside of baffles for non-manifoldness
 
-    // faceZones of type internal or baffle (for merging points across)
-    labelList internalOrBaffleFaceZones;
+    // Points that are candidates for duplication
+    labelList candidatePoints;
     {
-        List<surfaceZonesInfo::faceZoneType> fzTypes(2);
-        fzTypes[0] = surfaceZonesInfo::INTERNAL;
-        fzTypes[1] = surfaceZonesInfo::BAFFLE;
-        internalOrBaffleFaceZones = meshRefiner_.getZones(fzTypes);
-    }
-
-    // faceZones of type internal (for checking mesh quality across and
-    // merging baffles)
-    const labelList internalFaceZones
-    (
-        meshRefiner_.getZones
+        // Do full analysis to see if we need to extrude points
+        // so have to duplicate them
+        autoPtr<indirectPrimitivePatch> pp
         (
-            List<surfaceZonesInfo::faceZoneType>
+            meshRefinement::makePatch
             (
-                1,
-                surfaceZonesInfo::INTERNAL
+                mesh,
+                patchIDs
             )
-        )
-    );
+        );
 
-    // Create baffles (pairs of faces that share the same points)
-    // Baffles stored as owner and neighbour face that have been created.
-    List<labelPair> baffles;
-    {
-        labelList originatingFaceZone;
-        meshRefiner_.createZoneBaffles
+        // Displacement for all pp.localPoints. Set to large value to
+        // avoid truncation in syncPatchDisplacement because of
+        // minThickness.
+        vectorField patchDisp(pp().nPoints(), vector(GREAT, GREAT, GREAT));
+        labelList patchNLayers(pp().nPoints(), Zero);
+        label nIdealTotAddedCells = 0;
+        List<extrudeMode> extrudeStatus(pp().nPoints(), EXTRUDE);
+        // Get number of layers per point from number of layers per patch
+        setNumLayers
         (
-            identity(mesh.faceZones().size()),
-            baffles,
-            originatingFaceZone
+            numLayers,              // per patch the num layers
+            patchIDs,               // patches that are being moved
+            *pp,                    // indirectpatch for all faces moving
+
+            patchNLayers,
+            extrudeStatus,
+            nIdealTotAddedCells
+        );
+        // Make sure displacement is equal on both sides of coupled patches.
+        // Note that we explicitly disable the minThickness truncation
+        // of the patchDisp here.
+        syncPatchDisplacement
+        (
+            *pp,
+            scalarField(patchDisp.size(), Zero), //minThickness,
+            patchDisp,
+            patchNLayers,
+            extrudeStatus
         );
 
-        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+
+        // Do duplication only if all patch points decide to extrude. Ignore
+        // contribution from non-patch points. Note that we need to
+        // apply this to all mesh points
+        labelList minPatchState(mesh.nPoints(), labelMax);
+        forAll(extrudeStatus, patchPointi)
         {
-            const_cast<Time&>(mesh.time())++;
-            Info<< "Writing baffled mesh to time "
-                << meshRefiner_.timeName() << endl;
-            meshRefiner_.write
-            (
-                meshRefinement::debugType(debug),
-                meshRefinement::writeType
-                (
-                    meshRefinement::writeLevel()
-                  | meshRefinement::WRITEMESH
-                ),
-                mesh.time().path()/meshRefiner_.timeName()
-            );
+            label pointi = pp().meshPoints()[patchPointi];
+            minPatchState[pointi] = extrudeStatus[patchPointi];
         }
-    }
 
+        syncTools::syncPointList
+        (
+            mesh,
+            minPatchState,
+            minEqOp<label>(),   // combine op
+            labelMax            // null value
+        );
 
-    // Duplicate points on faceZones of type boundary. Should normally already
-    // be done by snapping phase
-    {
-        autoPtr<mapPolyMesh> map = meshRefiner_.dupNonManifoldBoundaryPoints();
-        if (map)
+        // So now minPatchState:
+        // - labelMax on non-patch points
+        // - NOEXTRUDE if any patch point was not extruded
+        // - EXTRUDE or EXTRUDEREMOVE if all patch points are extruded/
+        //   extrudeRemove.
+
+        label n = 0;
+        forAll(minPatchState, pointi)
         {
-            const labelList& reverseFaceMap = map->reverseFaceMap();
-            forAll(baffles, i)
+            label state = minPatchState[pointi];
+            if (state == EXTRUDE || state == EXTRUDEREMOVE)
             {
-                label f0 = reverseFaceMap[baffles[i].first()];
-                label f1 = reverseFaceMap[baffles[i].second()];
-                baffles[i] = labelPair(f0, f1);
+                n++;
+            }
+        }
+        candidatePoints.setSize(n);
+        n = 0;
+        forAll(minPatchState, pointi)
+        {
+            label state = minPatchState[pointi];
+            if (state == EXTRUDE || state == EXTRUDEREMOVE)
+            {
+                candidatePoints[n++] = pointi;
             }
         }
     }
 
+    // Not duplicate points on either side of baffles that don't get any
+    // layers
+    labelPairList nonDupBaffles;
 
-
-    // Now we have all patches determine the number of layer per patch
-    // This will be layerParams.numLayers() except for faceZones where one
-    // side does get layers and the other not in which case we want to
-    // suppress movement by explicitly setting numLayers 0
-    labelList numLayers(layerParams.numLayers());
     {
+        // faceZones that are not being duplicated
+        DynamicList<label> nonDupZones(mesh.faceZones().size());
+
         labelHashSet layerIDs(patchIDs);
         forAll(mesh.faceZones(), zonei)
         {
@@ -3539,1219 +3486,1809 @@ void Foam::snappyLayerDriver::addLayers
                 spi,
                 fzType
             );
-            if (hasInfo)
+            if (hasInfo && !layerIDs.found(mpi) && !layerIDs.found(spi))
             {
-                const polyBoundaryMesh& pbm = mesh.boundaryMesh();
-                if (layerIDs.found(mpi) && !layerIDs.found(spi))
-                {
-                    // Only layers on master side. Fix points on slave side
-                    Info<< "On faceZone " << mesh.faceZones()[zonei].name()
-                        << " adding layers to master patch " << pbm[mpi].name()
-                        << " only. Freezing points on slave patch "
-                        << pbm[spi].name() << endl;
-                    numLayers[spi] = 0;
-                }
-                else if (!layerIDs.found(mpi) && layerIDs.found(spi))
-                {
-                    // Only layers on slave side. Fix points on master side
-                    Info<< "On faceZone " << mesh.faceZones()[zonei].name()
-                        << " adding layers to slave patch " << pbm[spi].name()
-                        << " only. Freezing points on master patch "
-                        << pbm[mpi].name() << endl;
-                    numLayers[mpi] = 0;
-                }
+                nonDupZones.append(zonei);
             }
         }
+        nonDupBaffles = meshRefinement::subsetBaffles
+        (
+            mesh,
+            nonDupZones,
+            localPointRegion::findDuplicateFacePairs(mesh)
+        );
     }
 
 
+    const localPointRegion regionSide(mesh, nonDupBaffles, candidatePoints);
 
-    // Duplicate points on faceZones that layers are added to
-    labelList pointToMaster;
+    autoPtr<mapPolyMesh> map = meshRefiner_.dupNonManifoldPoints
+    (
+        regionSide
+    );
 
+    if (map)
     {
-        // Check outside of baffles for non-manifoldness
+        // Store point duplication
+        pointToMaster.setSize(mesh.nPoints(), -1);
 
-        // Points that are candidates for duplication
-        labelList candidatePoints;
-        {
-            // Do full analysis to see if we need to extrude points
-            // so have to duplicate them
-            autoPtr<indirectPrimitivePatch> pp
-            (
-                meshRefinement::makePatch
-                (
-                    mesh,
-                    patchIDs
-                )
-            );
-
-            // Displacement for all pp.localPoints. Set to large value to
-            // avoid truncation in syncPatchDisplacement because of
-            // minThickness.
-            vectorField patchDisp(pp().nPoints(), vector(GREAT, GREAT, GREAT));
-            labelList patchNLayers(pp().nPoints(), Zero);
-            label nIdealTotAddedCells = 0;
-            List<extrudeMode> extrudeStatus(pp().nPoints(), EXTRUDE);
-            // Get number of layers per point from number of layers per patch
-            setNumLayers
-            (
-                numLayers,              // per patch the num layers
-                patchIDs,               // patches that are being moved
-                *pp,                    // indirectpatch for all faces moving
+        const labelList& pointMap = map().pointMap();
+        const labelList& reversePointMap = map().reversePointMap();
 
-                patchDisp,
-                patchNLayers,
-                extrudeStatus,
-                nIdealTotAddedCells
-            );
-            // Make sure displacement is equal on both sides of coupled patches.
-            // Note that we explicitly disable the minThickness truncation
-            // of the patchDisp here.
-            syncPatchDisplacement
-            (
-                *pp,
-                scalarField(patchDisp.size(), Zero), //minThickness,
-                patchDisp,
-                patchNLayers,
-                extrudeStatus
-            );
+        forAll(pointMap, pointi)
+        {
+            label oldPointi = pointMap[pointi];
+            label newMasterPointi = reversePointMap[oldPointi];
 
+            if (newMasterPointi != pointi)
+            {
+                // Found slave. Mark both master and slave
+                pointToMaster[pointi] = newMasterPointi;
+                pointToMaster[newMasterPointi] = newMasterPointi;
+            }
+        }
 
-            // Do duplication only if all patch points decide to extrude. Ignore
-            // contribution from non-patch points. Note that we need to
-            // apply this to all mesh points
-            labelList minPatchState(mesh.nPoints(), labelMax);
-            forAll(extrudeStatus, patchPointi)
+        // Update baffle numbering
+        {
+            const labelList& reverseFaceMap = map().reverseFaceMap();
+            forAll(baffles, i)
             {
-                label pointi = pp().meshPoints()[patchPointi];
-                minPatchState[pointi] = extrudeStatus[patchPointi];
+                label f0 = reverseFaceMap[baffles[i].first()];
+                label f1 = reverseFaceMap[baffles[i].second()];
+                baffles[i] = labelPair(f0, f1);
             }
+        }
+
+
+        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+        {
+            const_cast<Time&>(mesh.time())++;
+            Info<< "Writing point-duplicate mesh to time "
+                << meshRefiner_.timeName() << endl;
 
-            syncTools::syncPointList
+            meshRefiner_.write
             (
-                mesh,
-                minPatchState,
-                minEqOp<label>(),   // combine op
-                labelMax            // null value
+                meshRefinement::debugType(debug),
+                meshRefinement::writeType
+                (
+                    meshRefinement::writeLevel()
+                  | meshRefinement::WRITEMESH
+                ),
+                mesh.time().path()/meshRefiner_.timeName()
             );
 
-            // So now minPatchState:
-            // - labelMax on non-patch points
-            // - NOEXTRUDE if any patch point was not extruded
-            // - EXTRUDE or EXTRUDEREMOVE if all patch points are extruded/
-            //   extrudeRemove.
-
-            label n = 0;
-            forAll(minPatchState, pointi)
-            {
-                label state = minPatchState[pointi];
-                if (state == EXTRUDE || state == EXTRUDEREMOVE)
-                {
-                    n++;
-                }
-            }
-            candidatePoints.setSize(n);
-            n = 0;
-            forAll(minPatchState, pointi)
+            OBJstream str
+            (
+                mesh.time().path()
+              / "duplicatePoints_"
+              + meshRefiner_.timeName()
+              + ".obj"
+            );
+            Info<< "Writing point-duplicates to " << str.name() << endl;
+            const pointField& p = mesh.points();
+            forAll(pointMap, pointi)
             {
-                label state = minPatchState[pointi];
-                if (state == EXTRUDE || state == EXTRUDEREMOVE)
+                label newMasteri = reversePointMap[pointMap[pointi]];
+
+                if (newMasteri != pointi)
                 {
-                    candidatePoints[n++] = pointi;
+                    str.write(linePointRef(p[pointi], p[newMasteri]));
                 }
             }
         }
+    }
+}
 
-        // Not duplicate points on either side of baffles that don't get any
-        // layers
-        labelPairList nonDupBaffles;
 
-        {
-            // faceZones that are not being duplicated
-            DynamicList<label> nonDupZones(mesh.faceZones().size());
+void Foam::snappyLayerDriver::mergeFaceZonePoints
+(
+    const labelList& pointToMaster, // -1 or index of duplicated point
+    labelList& cellNLayers,
+    scalarField& faceRealThickness,
+    scalarField& faceWantedThickness
+)
+{
+    // Near opposite of dupFaceZonePoints : merge points and baffles introduced
+    // for internal faceZones
 
-            labelHashSet layerIDs(patchIDs);
-            forAll(mesh.faceZones(), zonei)
+    fvMesh& mesh = meshRefiner_.mesh();
+
+    // Count duplicate points
+    label nPointPairs = 0;
+    forAll(pointToMaster, pointi)
+    {
+        label otherPointi = pointToMaster[pointi];
+        if (otherPointi != -1)
+        {
+            nPointPairs++;
+        }
+    }
+    reduce(nPointPairs, sumOp<label>());
+    if (nPointPairs > 0)
+    {
+        // Merge any duplicated points
+        Info<< "Merging " << nPointPairs << " duplicated points ..." << endl;
+
+        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+        {
+            OBJstream str
+            (
+                mesh.time().path()
+              / "mergePoints_"
+              + meshRefiner_.timeName()
+              + ".obj"
+            );
+            Info<< "Points to be merged to " << str.name() << endl;
+            forAll(pointToMaster, pointi)
             {
-                label mpi, spi;
-                surfaceZonesInfo::faceZoneType fzType;
-                bool hasInfo = meshRefiner_.getFaceZoneInfo
-                (
-                    mesh.faceZones()[zonei].name(),
-                    mpi,
-                    spi,
-                    fzType
-                );
-                if (hasInfo && !layerIDs.found(mpi) && !layerIDs.found(spi))
+                label otherPointi = pointToMaster[pointi];
+                if (otherPointi != -1)
                 {
-                    nonDupZones.append(zonei);
+                    const point& pt = mesh.points()[pointi];
+                    const point& otherPt = mesh.points()[otherPointi];
+                    str.write(linePointRef(pt, otherPt));
                 }
             }
-            nonDupBaffles = meshRefinement::subsetBaffles
-            (
-                mesh,
-                nonDupZones,
-                localPointRegion::findDuplicateFacePairs(mesh)
-            );
         }
 
 
-        const localPointRegion regionSide(mesh, nonDupBaffles, candidatePoints);
+        autoPtr<mapPolyMesh> map = meshRefiner_.mergePoints(pointToMaster);
+        if (map)
+        {
+            inplaceReorder(map().reverseCellMap(), cellNLayers);
+
+            const labelList& reverseFaceMap = map().reverseFaceMap();
+            inplaceReorder(reverseFaceMap, faceWantedThickness);
+            inplaceReorder(reverseFaceMap, faceRealThickness);
+
+            Info<< "Merged points in = "
+                << mesh.time().cpuTimeIncrement() << " s\n" << nl << endl;
+        }
+    }
+
+    if (mesh.faceZones().size() > 0)
+    {
+        // Merge any baffles
+        Info<< "Converting baffles back into zoned faces ..."
+            << endl;
 
-        autoPtr<mapPolyMesh> map = meshRefiner_.dupNonManifoldPoints
+        autoPtr<mapPolyMesh> map = meshRefiner_.mergeZoneBaffles
         (
-            regionSide
+            true,   // internal zones
+            false   // baffle zones
         );
-
         if (map)
         {
-            // Store point duplication
-            pointToMaster.setSize(mesh.nPoints(), -1);
+            inplaceReorder(map().reverseCellMap(), cellNLayers);
 
-            const labelList& pointMap = map().pointMap();
-            const labelList& reversePointMap = map().reversePointMap();
+            const labelList& faceMap = map().faceMap();
 
-            forAll(pointMap, pointi)
+            // Make sure to keep the max since on two patches only one has
+            // layers.
+            scalarField newFaceRealThickness(mesh.nFaces(), Zero);
+            scalarField newFaceWantedThickness(mesh.nFaces(), Zero);
+            forAll(newFaceRealThickness, facei)
             {
-                label oldPointi = pointMap[pointi];
-                label newMasterPointi = reversePointMap[oldPointi];
-
-                if (newMasterPointi != pointi)
+                label oldFacei = faceMap[facei];
+                if (oldFacei >= 0)
                 {
-                    // Found slave. Mark both master and slave
-                    pointToMaster[pointi] = newMasterPointi;
-                    pointToMaster[newMasterPointi] = newMasterPointi;
+                    scalar& realThick = newFaceRealThickness[facei];
+                    realThick = max(realThick, faceRealThickness[oldFacei]);
+                    scalar& wanted = newFaceWantedThickness[facei];
+                    wanted = max(wanted, faceWantedThickness[oldFacei]);
                 }
             }
+            faceRealThickness.transfer(newFaceRealThickness);
+            faceWantedThickness.transfer(newFaceWantedThickness);
+        }
 
-            // Update baffle numbering
-            {
-                const labelList& reverseFaceMap = map().reverseFaceMap();
-                forAll(baffles, i)
-                {
-                    label f0 = reverseFaceMap[baffles[i].first()];
-                    label f1 = reverseFaceMap[baffles[i].second()];
-                    baffles[i] = labelPair(f0, f1);
-                }
-            }
+        Info<< "Converted baffles in = "
+            << meshRefiner_.mesh().time().cpuTimeIncrement()
+            << " s\n" << nl << endl;
+    }
+}
 
 
-            if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
-            {
-                const_cast<Time&>(mesh.time())++;
-                Info<< "Writing point-duplicate mesh to time "
-                    << meshRefiner_.timeName() << endl;
+Foam::label Foam::snappyLayerDriver::setPointNumLayers
+(
+    const layerParameters& layerParams,
 
-                meshRefiner_.write
-                (
-                    meshRefinement::debugType(debug),
-                    meshRefinement::writeType
-                    (
-                        meshRefinement::writeLevel()
-                      | meshRefinement::WRITEMESH
-                    ),
-                    mesh.time().path()/meshRefiner_.timeName()
-                );
+    const labelList& numLayers,
+    const labelList& patchIDs,
+    const indirectPrimitivePatch& pp,
+    const labelListList& edgeGlobalFaces,
 
-                OBJstream str
-                (
-                    mesh.time().path()
-                  / "duplicatePoints_"
-                  + meshRefiner_.timeName()
-                  + ".obj"
-                );
-                Info<< "Writing point-duplicates to " << str.name() << endl;
-                const pointField& p = mesh.points();
-                forAll(pointMap, pointi)
-                {
-                    label newMasteri = reversePointMap[pointMap[pointi]];
+    vectorField& patchDisp,
+    labelList& patchNLayers,
+    List<extrudeMode>& extrudeStatus
+) const
+{
+    fvMesh& mesh = meshRefiner_.mesh();
 
-                    if (newMasteri != pointi)
-                    {
-                        str.write(linePointRef(p[pointi], p[newMasteri]));
-                    }
-                }
-            }
-        }
-    }
+    patchDisp.setSize(pp.nPoints());
+    patchDisp = vector(GREAT, GREAT, GREAT);
+
+    // Number of layers for all pp.localPoints. Note: only valid if
+    // extrudeStatus = EXTRUDE.
+    patchNLayers.setSize(pp.nPoints());
+    patchNLayers = Zero;
 
+    // Ideal number of cells added
+    label nIdealTotAddedCells = 0;
 
-    // Add layers to patches
-    // ~~~~~~~~~~~~~~~~~~~~~
+    // Whether to add edge for all pp.localPoints.
+    extrudeStatus.setSize(pp.nPoints());
+    extrudeStatus = EXTRUDE;
 
-    // Now we have
-    // - mesh with optional baffles and duplicated points for faceZones
-    //   where layers are to be added
-    // - pointToMaster    : correspondence for duplicated points
-    // - baffles          : list of pairs of faces
 
+    // Get number of layers per point from number of layers per patch
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    autoPtr<indirectPrimitivePatch> pp
+    setNumLayers
     (
-        meshRefinement::makePatch
-        (
-            mesh,
-            patchIDs
-        )
+        numLayers,                  // per patch the num layers
+        patchIDs,                   // patches that are being moved
+        pp,                         // indirectpatch for all faces moving
+
+        patchNLayers,
+        extrudeStatus,
+        nIdealTotAddedCells
     );
 
+    // Precalculate mesh edge labels for patch edges
+    labelList meshEdges(pp.meshEdges(mesh.edges(), mesh.pointEdges()));
 
-    // Global face indices engine
-    const globalIndex globalFaces(mesh.nFaces());
 
-    // Determine extrudePatch.edgeFaces in global numbering (so across
-    // coupled patches). This is used only to string up edges between coupled
-    // faces (all edges between same (global)face indices get extruded).
-    labelListList edgeGlobalFaces
+    // Disable extrusion on split strings of common points
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    handleNonStringConnected
     (
-        addPatchCellLayer::globalEdgeFaces
-        (
-            mesh,
-            globalFaces,
-            *pp
-        )
+        pp,
+        patchDisp,
+        patchNLayers,
+        extrudeStatus
     );
 
-    // Determine patches for extruded boundary edges. Calculates if any
-    // additional processor patches need to be constructed.
 
-    labelList edgePatchID;
-    labelList edgeZoneID;
-    boolList edgeFlip;
-    labelList inflateFaceID;
-    determineSidePatches
+    // Disable extrusion on non-manifold points
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    handleNonManifolds
     (
-        globalFaces,
+        pp,
+        meshEdges,
         edgeGlobalFaces,
-        *pp,
 
-        edgePatchID,
-        edgeZoneID,
-        edgeFlip,
-        inflateFaceID
+        patchDisp,
+        patchNLayers,
+        extrudeStatus
     );
 
+    // Disable extrusion on feature angles
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    // Point-wise extrusion data
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    // Displacement for all pp.localPoints. Set to large value so does
-    // not trigger the minThickness truncation (see syncPatchDisplacement below)
-    vectorField patchDisp(pp().nPoints(), vector(GREAT, GREAT, GREAT));
-
-    // Number of layers for all pp.localPoints. Note: only valid if
-    // extrudeStatus = EXTRUDE.
-    labelList patchNLayers(pp().nPoints(), Zero);
-
-    // Ideal number of cells added
-    label nIdealTotAddedCells = 0;
-
-    // Whether to add edge for all pp.localPoints.
-    List<extrudeMode> extrudeStatus(pp().nPoints(), EXTRUDE);
+    handleFeatureAngle
+    (
+        pp,
+        meshEdges,
+        layerParams.featureAngle(),
 
+        patchDisp,
+        patchNLayers,
+        extrudeStatus
+    );
 
+    // Disable extrusion on warped faces
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    // It is hard to calculate some length scale if not in relative
+    // mode so disable this check.
+    if (!layerParams.relativeSizes().found(false))
     {
-        // Get number of layers per point from number of layers per patch
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        // Undistorted edge length
+        const scalar edge0Len =
+            meshRefiner_.meshCutter().level0EdgeLength();
+        const labelList& cellLevel = meshRefiner_.meshCutter().cellLevel();
 
-        setNumLayers
+        handleWarpedFaces
         (
-            numLayers,                  // per patch the num layers
-            patchIDs,                   // patches that are being moved
-            *pp,                        // indirectpatch for all faces moving
+            pp,
+            layerParams.maxFaceThicknessRatio(),
+            layerParams.relativeSizes(),
+            edge0Len,
+            cellLevel,
 
             patchDisp,
             patchNLayers,
-            extrudeStatus,
-            nIdealTotAddedCells
+            extrudeStatus
         );
+    }
 
-        // Precalculate mesh edge labels for patch edges
-        labelList meshEdges(pp().meshEdges(mesh.edges(), mesh.pointEdges()));
-
+    //// Disable extrusion on cells with multiple patch faces
+    //// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    //
+    //handleMultiplePatchFaces
+    //(
+    //    pp,
+    //
+    //    patchDisp,
+    //    patchNLayers,
+    //    extrudeStatus
+    //);
 
-        // Disable extrusion on split strings of common points
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    addProfiling(grow, "snappyHexMesh::layers::grow");
 
-        handleNonStringConnected
+    // Grow out region of non-extrusion
+    for (label i = 0; i < layerParams.nGrow(); i++)
+    {
+        growNoExtrusion
         (
-            *pp,
+            pp,
             patchDisp,
             patchNLayers,
             extrudeStatus
         );
+    }
+    return nIdealTotAddedCells;
+}
 
 
-        // Disable extrusion on non-manifold points
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Foam::autoPtr<Foam::externalDisplacementMeshMover>
+Foam::snappyLayerDriver::makeMeshMover
+(
+    const layerParameters& layerParams,
+    const dictionary& motionDict,
+    const labelList& internalFaceZones,
+    const scalarIOField& minThickness,
+    pointVectorField& displacement
+) const
+{
+    // Allocate run-time selectable mesh mover
 
-        handleNonManifolds
-        (
-            *pp,
-            meshEdges,
-            edgeGlobalFaces,
+    fvMesh& mesh = meshRefiner_.mesh();
 
-            patchDisp,
-            patchNLayers,
-            extrudeStatus
-        );
 
-        // Disable extrusion on feature angles
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    // Set up controls for meshMover
+    dictionary combinedDict(layerParams.dict());
+    // Add mesh quality constraints
+    combinedDict.merge(motionDict);
+    // Where to get minThickness from
+    combinedDict.add("minThicknessName", minThickness.name());
 
-        handleFeatureAngle
+    const List<labelPair> internalBaffles
+    (
+        meshRefinement::subsetBaffles
         (
-            *pp,
-            meshEdges,
-            layerParams.featureAngle(),
+            mesh,
+            internalFaceZones,
+            localPointRegion::findDuplicateFacePairs(mesh)
+        )
+    );
 
-            patchDisp,
-            patchNLayers,
-            extrudeStatus
-        );
+    // Take over patchDisp as boundary conditions on displacement
+    // pointVectorField
+    autoPtr<Foam::externalDisplacementMeshMover> medialAxisMoverPtr
+    (
+        externalDisplacementMeshMover::New
+        (
+            layerParams.meshShrinker(),
+            combinedDict,
+            internalBaffles,
+            displacement
+        )
+    );
 
-        // Disable extrusion on warped faces
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-        // It is hard to calculate some length scale if not in relative
-        // mode so disable this check.
-        if (!layerParams.relativeSizes().found(false))
-        {
-            // Undistorted edge length
-            const scalar edge0Len =
-                meshRefiner_.meshCutter().level0EdgeLength();
-            const labelList& cellLevel = meshRefiner_.meshCutter().cellLevel();
 
-            handleWarpedFaces
-            (
-                *pp,
-                layerParams.maxFaceThicknessRatio(),
-                layerParams.relativeSizes(),
-                edge0Len,
-                cellLevel,
+    if (dryRun_)
+    {
+        string errorMsg(FatalError.message());
+        string IOerrorMsg(FatalIOError.message());
 
-                patchDisp,
-                patchNLayers,
-                extrudeStatus
-            );
+        if (errorMsg.size() || IOerrorMsg.size())
+        {
+            //errorMsg = "[dryRun] " + errorMsg;
+            //errorMsg.replaceAll("\n", "\n[dryRun] ");
+            //IOerrorMsg = "[dryRun] " + IOerrorMsg;
+            //IOerrorMsg.replaceAll("\n", "\n[dryRun] ");
+
+            IOWarningInFunction(combinedDict)
+                << nl
+                << "Missing/incorrect required dictionary entries:"
+                << nl << nl
+                << IOerrorMsg.c_str() << nl
+                << errorMsg.c_str() << nl << nl
+                << "Exiting dry-run" << nl << endl;
+
+            if (Pstream::parRun())
+            {
+                Perr<< "\nFOAM parallel run exiting\n" << endl;
+                Pstream::exit(0);
+            }
+            else
+            {
+                Perr<< "\nFOAM exiting\n" << endl;
+                std::exit(0);
+            }
         }
+    }
+    return medialAxisMoverPtr;
+}
 
-        //// Disable extrusion on cells with multiple patch faces
-        //// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-        //
-        //handleMultiplePatchFaces
-        //(
-        //    *pp,
-        //
-        //    patchDisp,
-        //    patchNLayers,
-        //    extrudeStatus
-        //);
-
-        addProfiling(grow, "snappyHexMesh::layers::grow");
 
-        // Grow out region of non-extrusion
-        for (label i = 0; i < layerParams.nGrow(); i++)
-        {
-            growNoExtrusion
-            (
-                *pp,
-                patchDisp,
-                patchNLayers,
-                extrudeStatus
-            );
-        }
-    }
+void Foam::snappyLayerDriver::addLayers
+(
+    const layerParameters& layerParams,
+    const label nLayerIter,
 
+    // Mesh quality provision
+    const dictionary& motionDict,
+    const label nRelaxedIter,
+    const label nAllowableErrors,
 
-    // Undistorted edge length
-    const scalar edge0Len = meshRefiner_.meshCutter().level0EdgeLength();
-    const labelList& cellLevel = meshRefiner_.meshCutter().cellLevel();
+    const labelList& patchIDs,
+    const labelList& internalFaceZones,
+    const List<labelPair>& baffles,
+    const labelList& numLayers,
+    const label nIdealTotAddedCells,
+
+    const globalIndex& globalFaces,
+    indirectPrimitivePatch& pp,
+
+    const labelListList& edgeGlobalFaces,
+    const labelList& edgePatchID,
+    const labelList& edgeZoneID,
+    const boolList& edgeFlip,
+    const labelList& inflateFaceID,
+
+    const scalarField& thickness,
+    const scalarIOField& minThickness,
+    const scalarField& expansionRatio,
+
+    // Displacement for all pp.localPoints. Set to large value so does
+    // not trigger the minThickness truncation (see syncPatchDisplacement below)
+    vectorField& patchDisp,
+
+    // Number of layers for all pp.localPoints. Note: only valid if
+    // extrudeStatus = EXTRUDE.
+    labelList& patchNLayers,
+
+    // Whether to add edge for all pp.localPoints.
+    List<extrudeMode>& extrudeStatus,
+
+
+    polyTopoChange& savedMeshMod,
+
+
+    // Per cell 0 or number of layers in the cell column it is part of
+    labelList& cellNLayers,
+    // Per face actual overall layer thickness
+    scalarField& faceRealThickness
+)
+{
+    fvMesh& mesh = meshRefiner_.mesh();
 
-    // Determine (wanted) point-wise overall layer thickness and expansion
-    // ratio
-    scalarField thickness(pp().nPoints());
-    scalarIOField minThickness
+    // Overall displacement field
+    pointVectorField displacement
     (
-        IOobject
+        makeLayerDisplacementField
         (
-            "minThickness",
-            meshRefiner_.timeName(),
-            mesh,
-            IOobject::NO_READ
-        ),
-        pp().nPoints()
+            pointMesh::New(mesh),
+            numLayers
+        )
     );
-    scalarField expansionRatio(pp().nPoints());
-    calculateLayerThickness
-    (
-        *pp,
-        patchIDs,
-        layerParams,
-        cellLevel,
-        patchNLayers,
-        edge0Len,
 
-        thickness,
-        minThickness,
-        expansionRatio
+    // Allocate run-time selectable mesh mover
+    autoPtr<externalDisplacementMeshMover> medialAxisMoverPtr
+    (
+        makeMeshMover
+        (
+            layerParams,
+            motionDict,
+            internalFaceZones,
+            minThickness,
+            displacement
+        )
     );
 
 
+    // Saved old points
+    const pointField oldPoints(mesh.points());
 
-    // Current set of topology changes. (changing mesh clears out
-    // polyTopoChange)
-    polyTopoChange savedMeshMod(mesh.boundaryMesh().size());
-    // Per cell 0 or number of layers in the cell column it is part of
-    labelList cellNLayers;
-    // Per face actual overall layer thickness
-    scalarField faceRealThickness;
-    // Per face wanted overall layer thickness
-    scalarField faceWantedThickness(mesh.nFaces(), Zero);
+    for (label iteration = 0; iteration < nLayerIter; iteration++)
     {
-        UIndirectList<scalar>(faceWantedThickness, pp->addressing()) =
-            avgPointData(*pp, thickness);
-    }
+        Info<< nl
+            << "Layer addition iteration " << iteration << nl
+            << "--------------------------" << endl;
 
 
-    {
-        // Overall displacement field
-        pointVectorField displacement
+        // Unset the extrusion at the pp.
+        const dictionary& meshQualityDict =
         (
-            makeLayerDisplacementField
-            (
-                pointMesh::New(mesh),
-                numLayers
-            )
+            iteration < nRelaxedIter
+          ? motionDict
+          : motionDict.subDict("relaxed")
         );
 
-        // Allocate run-time selectable mesh mover
-        autoPtr<externalDisplacementMeshMover> medialAxisMoverPtr;
+        if (iteration >= nRelaxedIter)
         {
-            // Set up controls for meshMover
-            dictionary combinedDict(layerParams.dict());
-            // Add mesh quality constraints
-            combinedDict.merge(motionDict);
-            // Where to get minThickness from
-            combinedDict.add("minThicknessName", minThickness.name());
-
-            const List<labelPair> internalBaffles
-            (
-                meshRefinement::subsetBaffles
-                (
-                    mesh,
-                    internalFaceZones,
-                    localPointRegion::findDuplicateFacePairs(mesh)
-                )
-            );
+            Info<< "Switched to relaxed meshQuality constraints." << endl;
+        }
 
-            // Take over patchDisp as boundary conditions on displacement
-            // pointVectorField
-            medialAxisMoverPtr = externalDisplacementMeshMover::New
-            (
-                layerParams.meshShrinker(),
-                combinedDict,
-                internalBaffles,
-                displacement
-            );
 
 
-            if (dryRun_)
-            {
-                string errorMsg(FatalError.message());
-                string IOerrorMsg(FatalIOError.message());
+        // Make sure displacement is equal on both sides of coupled patches.
+        // Note that this also does the patchDisp < minThickness truncation
+        // so for the first pass make sure the patchDisp is larger than
+        // that.
+        syncPatchDisplacement
+        (
+            pp,
+            minThickness,
+            patchDisp,
+            patchNLayers,
+            extrudeStatus
+        );
 
-                if (errorMsg.size() || IOerrorMsg.size())
-                {
-                    //errorMsg = "[dryRun] " + errorMsg;
-                    //errorMsg.replaceAll("\n", "\n[dryRun] ");
-                    //IOerrorMsg = "[dryRun] " + IOerrorMsg;
-                    //IOerrorMsg.replaceAll("\n", "\n[dryRun] ");
-
-                    IOWarningInFunction(combinedDict)
-                        << nl
-                        << "Missing/incorrect required dictionary entries:"
-                        << nl << nl
-                        << IOerrorMsg.c_str() << nl
-                        << errorMsg.c_str() << nl << nl
-                        << "Exiting dry-run" << nl << endl;
-
-                    if (Pstream::parRun())
-                    {
-                        Perr<< "\nFOAM parallel run exiting\n" << endl;
-                        Pstream::exit(0);
-                    }
-                    else
-                    {
-                        Perr<< "\nFOAM exiting\n" << endl;
-                        std::exit(0);
-                    }
-                }
-            }
-        }
+        // Displacement acc. to pointnormals
+        getPatchDisplacement
+        (
+            pp,
+            thickness,
+            minThickness,
+            expansionRatio,
 
+            patchDisp,
+            patchNLayers,
+            extrudeStatus
+        );
 
-        // Saved old points
-        const pointField oldPoints(mesh.points());
+        // Shrink mesh by displacement value first.
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-        for
-        (
-            label iteration = 0;
-            iteration < layerParams.nLayerIter();
-            iteration++
-        )
         {
-            Info<< nl
-                << "Layer addition iteration " << iteration << nl
-                << "--------------------------" << endl;
+            const pointField oldPatchPos(pp.localPoints());
 
+            // We have patchDisp which is the outwards pointing
+            // extrusion distance. Convert into an inwards pointing
+            // shrink distance
+            patchDisp = -patchDisp;
 
-            // Unset the extrusion at the pp.
-            const dictionary& meshQualityDict =
+            // Take over patchDisp into pointDisplacement field and
+            // adjust both for multi-patch constraints
+            motionSmootherAlgo::setDisplacement
             (
-                iteration < layerParams.nRelaxedIter()
-              ? motionDict
-              : motionDict.subDict("relaxed")
+                patchIDs,
+                pp,
+                patchDisp,
+                displacement
             );
 
-            if (iteration >= layerParams.nRelaxedIter())
-            {
-                Info<< "Switched to relaxed meshQuality constraints." << endl;
-            }
 
+            // Move mesh
+            // ~~~~~~~~~
 
+            // Set up controls for meshMover
+            dictionary combinedDict(layerParams.dict());
+            // Add standard quality constraints
+            combinedDict.merge(motionDict);
+            // Add relaxed constraints (overrides standard ones)
+            combinedDict.merge(meshQualityDict);
+            // Where to get minThickness from
+            combinedDict.add("minThicknessName", minThickness.name());
 
-            // Make sure displacement is equal on both sides of coupled patches.
-            // Note that this also does the patchDisp < minThickness truncation
-            // so for the first pass make sure the patchDisp is larger than
-            // that.
-            syncPatchDisplacement
+            labelList checkFaces(identity(mesh.nFaces()));
+            medialAxisMoverPtr().move
             (
-                *pp,
-                minThickness,
-                patchDisp,
-                patchNLayers,
-                extrudeStatus
+                combinedDict,
+                nAllowableErrors,
+                checkFaces
             );
 
-            // Displacement acc. to pointnormals
-            getPatchDisplacement
-            (
-                *pp,
-                thickness,
-                minThickness,
-                expansionRatio,
+            pp.movePoints(mesh.points());
+
+            // Update patchDisp (since not all might have been honoured)
+            patchDisp = oldPatchPos - pp.localPoints();
+        }
+
+        // Truncate displacements that are too small (this will do internal
+        // ones, coupled ones have already been truncated by
+        // syncPatchDisplacement)
+        faceSet dummySet(mesh, "wrongPatchFaces", 0);
+        truncateDisplacement
+        (
+            globalFaces,
+            edgeGlobalFaces,
+            pp,
+            minThickness,
+            dummySet,
+            patchDisp,
+            patchNLayers,
+            extrudeStatus
+        );
 
+
+        // Dump to .obj file for debugging.
+        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+        {
+            dumpDisplacement
+            (
+                mesh.time().path()/"layer_" + meshRefiner_.timeName(),
+                pp,
                 patchDisp,
-                patchNLayers,
                 extrudeStatus
             );
 
-            // Shrink mesh by displacement value first.
-            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-            {
-                const pointField oldPatchPos(pp().localPoints());
+            const_cast<Time&>(mesh.time())++;
+            Info<< "Writing shrunk mesh to time "
+                << meshRefiner_.timeName() << endl;
 
-                // We have patchDisp which is the outwards pointing
-                // extrusion distance. Convert into an inwards pointing
-                // shrink distance
-                patchDisp = -patchDisp;
+            // See comment in snappySnapDriver why we should not remove
+            // meshPhi using mesh.clearOut().
 
-                // Take over patchDisp into pointDisplacement field and
-                // adjust both for multi-patch constraints
-                motionSmootherAlgo::setDisplacement
+            meshRefiner_.write
+            (
+                meshRefinement::debugType(debug),
+                meshRefinement::writeType
                 (
-                    patchIDs,
-                    *pp,
-                    patchDisp,
-                    displacement
-                );
+                    meshRefinement::writeLevel()
+                  | meshRefinement::WRITEMESH
+                ),
+                mesh.time().path()/meshRefiner_.timeName()
+            );
+        }
 
 
-                // Move mesh
-                // ~~~~~~~~~
+        // Mesh topo change engine. Insert current mesh.
+        polyTopoChange meshMod(mesh);
 
-                // Set up controls for meshMover
-                dictionary combinedDict(layerParams.dict());
-                // Add standard quality constraints
-                combinedDict.merge(motionDict);
-                // Add relaxed constraints (overrides standard ones)
-                combinedDict.merge(meshQualityDict);
-                // Where to get minThickness from
-                combinedDict.add("minThicknessName", minThickness.name());
+        // Grow layer of cells on to patch. Handles zero sized displacement.
+        addPatchCellLayer addLayer(mesh);
 
-                labelList checkFaces(identity(mesh.nFaces()));
-                medialAxisMoverPtr().move
-                (
-                    combinedDict,
-                    nAllowableErrors,
-                    checkFaces
-                );
+        // Determine per point/per face number of layers to extrude. Also
+        // handles the slow termination of layers when going switching
+        // layers
 
-                pp().movePoints(mesh.points());
+        labelList nPatchPointLayers(pp.nPoints(), -1);
+        labelList nPatchFaceLayers(pp.size(), -1);
+        setupLayerInfoTruncation
+        (
+            pp,
+            patchNLayers,
+            extrudeStatus,
+            layerParams.nBufferCellsNoExtrude(),
+            nPatchPointLayers,
+            nPatchFaceLayers
+        );
 
-                // Update patchDisp (since not all might have been honoured)
-                patchDisp = oldPatchPos - pp().localPoints();
-            }
+        // Calculate displacement for final layer for addPatchLayer.
+        // (layer of cells next to the original mesh)
+        vectorField finalDisp(patchNLayers.size(), Zero);
 
-            // Truncate displacements that are too small (this will do internal
-            // ones, coupled ones have already been truncated by
-            // syncPatchDisplacement)
-            faceSet dummySet(mesh, "wrongPatchFaces", 0);
-            truncateDisplacement
+        forAll(nPatchPointLayers, i)
+        {
+            scalar ratio = layerParameters::finalLayerThicknessRatio
             (
-                globalFaces,
-                edgeGlobalFaces,
-                *pp,
-                minThickness,
-                dummySet,
-                patchDisp,
-                patchNLayers,
-                extrudeStatus
+                nPatchPointLayers[i],
+                expansionRatio[i]
             );
+            finalDisp[i] = ratio*patchDisp[i];
+        }
 
 
-            // Dump to .obj file for debugging.
-            if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
-            {
-                dumpDisplacement
-                (
-                    mesh.time().path()/"layer_" + meshRefiner_.timeName(),
-                    pp(),
-                    patchDisp,
-                    extrudeStatus
-                );
+        const scalarField invExpansionRatio(1.0/expansionRatio);
 
-                const_cast<Time&>(mesh.time())++;
-                Info<< "Writing shrunk mesh to time "
-                    << meshRefiner_.timeName() << endl;
+        // Add topo regardless of whether extrudeStatus is extruderemove.
+        // Not add layer if patchDisp is zero.
+        addLayer.setRefinement
+        (
+            globalFaces,
+            edgeGlobalFaces,
 
-                // See comment in snappySnapDriver why we should not remove
-                // meshPhi using mesh.clearOut().
+            invExpansionRatio,
+            pp,
+            bitSet(pp.size()),    // no flip
 
-                meshRefiner_.write
-                (
-                    meshRefinement::debugType(debug),
-                    meshRefinement::writeType
-                    (
-                        meshRefinement::writeLevel()
-                      | meshRefinement::WRITEMESH
-                    ),
-                    mesh.time().path()/meshRefiner_.timeName()
-                );
-            }
+            edgePatchID,    // boundary patch for extruded boundary edges
+            edgeZoneID,     // zone for extruded edges
+            edgeFlip,
+            inflateFaceID,
 
 
-            // Mesh topo change engine. Insert current mesh.
-            polyTopoChange meshMod(mesh);
+            labelList(0),   // exposed patchIDs, not used for adding layers
+            nPatchFaceLayers,   // layers per face
+            nPatchPointLayers,  // layers per point
+            finalDisp,      // thickness of layer nearest internal mesh
+            meshMod
+        );
 
-            // Grow layer of cells on to patch. Handles zero sized displacement.
-            addPatchCellLayer addLayer(mesh);
+        if (debug)
+        {
+            const_cast<Time&>(mesh.time())++;
+        }
 
-            // Determine per point/per face number of layers to extrude. Also
-            // handles the slow termination of layers when going switching
-            // layers
+        // Compact storage
+        meshMod.shrink();
 
-            labelList nPatchPointLayers(pp().nPoints(), -1);
-            labelList nPatchFaceLayers(pp().size(), -1);
-            setupLayerInfoTruncation
-            (
-                *pp,
-                patchNLayers,
-                extrudeStatus,
-                layerParams.nBufferCellsNoExtrude(),
-                nPatchPointLayers,
-                nPatchFaceLayers
-            );
+        // Store mesh changes for if mesh is correct.
+        savedMeshMod = meshMod;
 
-            // Calculate displacement for final layer for addPatchLayer.
-            // (layer of cells next to the original mesh)
-            vectorField finalDisp(patchNLayers.size(), Zero);
 
-            forAll(nPatchPointLayers, i)
-            {
-                scalar ratio = layerParameters::finalLayerThicknessRatio
-                (
-                    nPatchPointLayers[i],
-                    expansionRatio[i]
-                );
-                finalDisp[i] = ratio*patchDisp[i];
-            }
+        // With the stored topo changes we create a new mesh so we can
+        // undo if necessary.
 
+        autoPtr<fvMesh> newMeshPtr;
+        autoPtr<mapPolyMesh> mapPtr = meshMod.makeMesh
+        (
+            newMeshPtr,
+            IOobject
+            (
+                //mesh.name()+"_layer",
+                mesh.name(),
+                static_cast<polyMesh&>(mesh).instance(),
+                mesh.time(),  // register with runTime
+                IOobject::READ_IF_PRESENT,  // read fv* if present
+                static_cast<polyMesh&>(mesh).writeOpt()
+            ),              // io params from original mesh but new name
+            mesh,           // original mesh
+            true            // parallel sync
+        );
+        fvMesh& newMesh = *newMeshPtr;
+        mapPolyMesh& map = *mapPtr;
 
-            const scalarField invExpansionRatio(1.0/expansionRatio);
+        // Get timing, but more importantly get memory information
+        addProfiling(grow, "snappyHexMesh::layers::updateMesh");
 
-            // Add topo regardless of whether extrudeStatus is extruderemove.
-            // Not add layer if patchDisp is zero.
-            addLayer.setRefinement
-            (
-                globalFaces,
-                edgeGlobalFaces,
+        //?necessary? Update fields
+        newMesh.updateMesh(map);
 
-                invExpansionRatio,
-                pp(),
+        newMesh.setInstance(meshRefiner_.timeName());
+
+        // Update numbering on addLayer:
+        // - cell/point labels to be newMesh.
+        // - patchFaces to remain in oldMesh order.
+        addLayer.updateMesh
+        (
+            map,
+            identity(pp.size()),
+            identity(pp.nPoints())
+        );
 
-                edgePatchID,    // boundary patch for extruded boundary edges
-                edgeZoneID,     // zone for extruded edges
-                edgeFlip,
-                inflateFaceID,
+        // Collect layer faces and cells for outside loop.
+        getLayerCellsFaces
+        (
+            newMesh,
+            addLayer,
+            avgPointData(pp, mag(patchDisp))(), // current thickness
 
+            cellNLayers,
+            faceRealThickness
+        );
 
-                labelList(0),   // exposed patchIDs, not used for adding layers
-                nPatchFaceLayers,   // layers per face
-                nPatchPointLayers,  // layers per point
-                finalDisp,      // thickness of layer nearest internal mesh
-                meshMod
-            );
 
-            if (debug)
+        // Count number of added cells
+        label nAddedCells = 0;
+        forAll(cellNLayers, celli)
+        {
+            if (cellNLayers[celli] > 0)
             {
-                const_cast<Time&>(mesh.time())++;
+                nAddedCells++;
             }
+        }
 
-            // Store mesh changes for if mesh is correct.
-            savedMeshMod = meshMod;
 
+        if (debug&meshRefinement::MESH)
+        {
+            Info<< "Writing layer mesh to time " << meshRefiner_.timeName()
+                << endl;
+            newMesh.write();
+            writeLayerSets(newMesh, cellNLayers, faceRealThickness);
+
+            // Reset the instance of the original mesh so next iteration
+            // it dumps a complete mesh. This is just so that the inbetween
+            // newMesh does not upset e.g. paraFoam cycling through the
+            // times.
+            mesh.setInstance(meshRefiner_.timeName());
+        }
 
-            // With the stored topo changes we create a new mesh so we can
-            // undo if necessary.
 
-            autoPtr<fvMesh> newMeshPtr;
-            autoPtr<mapPolyMesh> mapPtr = meshMod.makeMesh
+        //- Get baffles in newMesh numbering. Note that we cannot detect
+        //  baffles here since the points are duplicated
+        List<labelPair> internalBaffles;
+        {
+            // From old mesh face to corresponding newMesh boundary face
+            labelList meshToNewMesh(mesh.nFaces(), -1);
+            for
             (
-                newMeshPtr,
-                IOobject
-                (
-                    //mesh.name()+"_layer",
-                    mesh.name(),
-                    static_cast<polyMesh&>(mesh).instance(),
-                    mesh.time(),  // register with runTime
-                    IOobject::READ_IF_PRESENT,  // read fv* if present
-                    static_cast<polyMesh&>(mesh).writeOpt()
-                ),              // io params from original mesh but new name
-                mesh,           // original mesh
-                true            // parallel sync
-            );
-            fvMesh& newMesh = *newMeshPtr;
-            mapPolyMesh& map = *mapPtr;
-
-            // Get timing, but more importantly get memory information
-            addProfiling(grow, "snappyHexMesh::layers::updateMesh");
-
-            //?necessary? Update fields
-            newMesh.updateMesh(map);
+                label facei = newMesh.nInternalFaces();
+                facei < newMesh.nFaces();
+                facei++
+            )
+            {
+                label newMeshFacei = map.faceMap()[facei];
+                if (newMeshFacei != -1)
+                {
+                    meshToNewMesh[newMeshFacei] = facei;
+                }
+            }
 
-            newMesh.setInstance(meshRefiner_.timeName());
+            List<labelPair> newMeshBaffles(baffles.size());
+            label newi = 0;
+            forAll(baffles, i)
+            {
+                const labelPair& p = baffles[i];
+                labelPair newMeshBaffle
+                (
+                    meshToNewMesh[p[0]],
+                    meshToNewMesh[p[1]]
+                );
+                if (newMeshBaffle[0] != -1 && newMeshBaffle[1] != -1)
+                {
+                    newMeshBaffles[newi++] = newMeshBaffle;
+                }
+            }
+            newMeshBaffles.setSize(newi);
 
-            // Update numbering on addLayer:
-            // - cell/point labels to be newMesh.
-            // - patchFaces to remain in oldMesh order.
-            addLayer.updateMesh
+            internalBaffles = meshRefinement::subsetBaffles
             (
-                map,
-                identity(pp().size()),
-                identity(pp().nPoints())
+                newMesh,
+                internalFaceZones,
+                newMeshBaffles
             );
 
-            // Collect layer faces and cells for outside loop.
-            getLayerCellsFaces
-            (
-                newMesh,
-                addLayer,
-                avgPointData(*pp, mag(patchDisp))(), // current thickness
+            Info<< "Detected "
+                << returnReduce(internalBaffles.size(), sumOp<label>())
+                << " baffles across faceZones of type internal" << nl
+                << endl;
+        }
 
-                cellNLayers,
-                faceRealThickness
+        label nTotChanged = checkAndUnmark
+        (
+            addLayer,
+            meshQualityDict,
+            layerParams.additionalReporting(),
+            internalBaffles,
+            pp,
+            newMesh,
+
+            patchDisp,
+            patchNLayers,
+            extrudeStatus
+        );
+
+        label nTotExtruded = countExtrusion(pp, extrudeStatus);
+        label nTotFaces = returnReduce(pp.size(), sumOp<label>());
+        label nTotAddedCells = returnReduce(nAddedCells, sumOp<label>());
+
+        Info<< "Extruding " << nTotExtruded
+            << " out of " << nTotFaces
+            << " faces (" << 100.0*nTotExtruded/nTotFaces << "%)."
+            << " Removed extrusion at " << nTotChanged << " faces."
+            << endl
+            << "Added " << nTotAddedCells << " out of "
+            << nIdealTotAddedCells
+            << " cells (" << 100.0*nTotAddedCells/nIdealTotAddedCells
+            << "%)." << endl;
+
+        if (nTotChanged == 0)
+        {
+            break;
+        }
+
+        // Reset mesh points and start again
+        mesh.movePoints(oldPoints);
+        pp.movePoints(mesh.points());
+        medialAxisMoverPtr().movePoints(mesh.points());
+
+        // Grow out region of non-extrusion
+        for (label i = 0; i < layerParams.nGrow(); i++)
+        {
+            growNoExtrusion
+            (
+                pp,
+                patchDisp,
+                patchNLayers,
+                extrudeStatus
             );
+        }
+
+        Info<< endl;
+    }
+}
+
+
+void Foam::snappyLayerDriver::mapFaceZonePoints
+(
+    const mapPolyMesh& map,
+    labelPairList& baffles,
+    labelList& pointToMaster
+) const
+{
+    fvMesh& mesh = meshRefiner_.mesh();
+
+    // Use geometric detection of points-to-be-merged
+    //  - detect any boundary face created from a duplicated face (=baffle)
+    //  - on these mark any point created from a duplicated point
+    if (returnReduce(pointToMaster.size(), sumOp<label>()))
+    {
+        // Estimate number of points-to-be-merged
+        DynamicList<label> candidates(baffles.size()*4);
+
+        // The problem is that all the internal layer faces also
+        // have reverseFaceMap pointing to the old baffle face. So instead
+        // loop over all the boundary faces and see which pair of new boundary
+        // faces corresponds to the old baffles.
+
 
+        // Mark whether old face was on baffle
+        Map<label> oldFaceToBaffle(2*baffles.size());
+        forAll(baffles, i)
+        {
+            const labelPair& baffle = baffles[i];
+            oldFaceToBaffle.insert(baffle[0], i);
+            oldFaceToBaffle.insert(baffle[1], i);
+        }
 
-            // Count number of added cells
-            label nAddedCells = 0;
-            forAll(cellNLayers, celli)
+        labelPairList newBaffles(baffles.size(), labelPair(-1, -1));
+
+        for
+        (
+            label facei = mesh.nInternalFaces();
+            facei < mesh.nFaces();
+            facei++
+        )
+        {
+            const label oldFacei = map.faceMap()[facei];
+            const auto iter = oldFaceToBaffle.find(oldFacei);
+            if (oldFacei != -1 && iter.found())
             {
-                if (cellNLayers[celli] > 0)
+                const label bafflei = iter();
+                auto& newBaffle = newBaffles[bafflei];
+                if (newBaffle[0] == -1)
+                {
+                    newBaffle[0] = facei;
+                }
+                else if (newBaffle[1] == -1)
+                {
+                    newBaffle[1] = facei;
+                }
+                else
+                {
+                    FatalErrorInFunction << "face:" << facei
+                        << " at:" << mesh.faceCentres()[facei]
+                        << " already maps to baffle faces:"
+                        << newBaffle[0]
+                        << " at:" << mesh.faceCentres()[newBaffle[0]]
+                        << " and " << newBaffle[1]
+                        << " at:" << mesh.faceCentres()[newBaffle[1]]
+                        << exit(FatalError);
+                }
+
+                const face& f = mesh.faces()[facei];
+                forAll(f, fp)
                 {
-                    nAddedCells++;
+                    label pointi = f[fp];
+                    label oldPointi = map.pointMap()[pointi];
+
+                    if (pointToMaster[oldPointi] != -1)
+                    {
+                        candidates.append(pointi);
+                    }
                 }
             }
+        }
 
 
-            if (debug&meshRefinement::MESH)
+        // Compact newBaffles
+        {
+            label n = 0;
+            forAll(newBaffles, i)
             {
-                Info<< "Writing layer mesh to time " << meshRefiner_.timeName()
-                    << endl;
-                newMesh.write();
-                writeLayerSets(newMesh, cellNLayers, faceRealThickness);
-
-                // Reset the instance of the original mesh so next iteration
-                // it dumps a complete mesh. This is just so that the inbetween
-                // newMesh does not upset e.g. paraFoam cycling through the
-                // times.
-                mesh.setInstance(meshRefiner_.timeName());
+                const labelPair& newBaffle = newBaffles[i];
+                if (newBaffle[0] != -1 && newBaffle[1] != -1)
+                {
+                    newBaffles[n++] = newBaffle;
+                }
             }
 
+            newBaffles.setSize(n);
+            baffles.transfer(newBaffles);
+        }
+
+
+        // Do geometric merge. Ideally we'd like to use a topological
+        // merge but we've thrown away all layer-wise addressing when
+        // throwing away addPatchCellLayer engine. Also the addressing
+        // is extremely complicated. There is no problem with merging
+        // too many points; the problem would be if merging baffles.
+        // Trust mergeZoneBaffles to do sufficient checks.
+        labelList oldToNew;
+        label nNew = mergePoints
+        (
+            pointField(mesh.points(), candidates),
+            meshRefiner_.mergeDistance(),
+            false,
+            oldToNew
+        );
+
+        // Extract points to be merged (i.e. multiple points originating
+        // from a single one)
+
+        labelListList newToOld(invertOneToMany(nNew, oldToNew));
 
-            //- Get baffles in newMesh numbering. Note that we cannot detect
-            //  baffles here since the points are duplicated
-            List<labelPair> internalBaffles;
+        // Extract points with more than one old one
+        pointToMaster.setSize(mesh.nPoints());
+        pointToMaster = -1;
+
+        forAll(newToOld, newi)
+        {
+            const labelList& oldPoints = newToOld[newi];
+            if (oldPoints.size() > 1)
             {
-                // From old mesh face to corresponding newMesh boundary face
-                labelList meshToNewMesh(mesh.nFaces(), -1);
-                for
+                labelList meshPoints
                 (
-                    label facei = newMesh.nInternalFaces();
-                    facei < newMesh.nFaces();
-                    facei++
-                )
+                    labelUIndList(candidates, oldPoints)
+                );
+                label masteri = min(meshPoints);
+                forAll(meshPoints, i)
                 {
-                    label newMeshFacei = map.faceMap()[facei];
-                    if (newMeshFacei != -1)
-                    {
-                        meshToNewMesh[newMeshFacei] = facei;
-                    }
+                    pointToMaster[meshPoints[i]] = masteri;
                 }
+            }
+        }
+    }
+}
+
+
+void Foam::snappyLayerDriver::updatePatch
+(
+    const labelList& patchIDs,
+    const mapPolyMesh& map,
+    autoPtr<indirectPrimitivePatch>& pp,
+    labelList& newToOldPatchPoints
+) const
+{
+    // Update the pp to be consistent with the new mesh
+
+    fvMesh& mesh = meshRefiner_.mesh();
+
+    autoPtr<indirectPrimitivePatch> newPp
+    (
+        meshRefinement::makePatch
+        (
+            mesh,
+            patchIDs
+        )
+    );
+
+    // Map from new back to old patch points
+    newToOldPatchPoints.setSize(newPp().nPoints());
+    newToOldPatchPoints = -1;
+    {
+        const Map<label>& baseMap = pp().meshPointMap();
+        const labelList& newMeshPoints = newPp().meshPoints();
+
+        forAll(newMeshPoints, newPointi)
+        {
+            const label newMeshPointi = newMeshPoints[newPointi];
+            const label oldMeshPointi =
+                map.pointMap()[newMeshPointi];
+            const auto iter = baseMap.find(oldMeshPointi);
+            if (iter.found())
+            {
+                newToOldPatchPoints[newPointi] = iter();
+            }
+        }
+    }
+
+
+    // Reset pp. Note: make sure to use std::move - otherwise it
+    // will release the pointer before copying and you get
+    // memory error. Same if using autoPtr::reset.
+    pp = std::move(newPp);
+
+    // Make sure pp has adressing cached
+    (void)pp().meshPoints();
+    (void)pp().meshPointMap();
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::snappyLayerDriver::snappyLayerDriver
+(
+    meshRefinement& meshRefiner,
+    const labelList& globalToMasterPatch,
+    const labelList& globalToSlavePatch,
+    const bool dryRun
+)
+:
+    meshRefiner_(meshRefiner),
+    globalToMasterPatch_(globalToMasterPatch),
+    globalToSlavePatch_(globalToSlavePatch),
+    dryRun_(dryRun)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::snappyLayerDriver::mergePatchFacesUndo
+(
+    const layerParameters& layerParams,
+    const dictionary& motionDict,
+    const meshRefinement::FaceMergeType mergeType
+)
+{
+    // Clip to 30 degrees. Not helpful!
+    //scalar planarAngle = min(30.0, layerParams.featureAngle());
+    scalar planarAngle = layerParams.mergePatchFacesAngle();
+    scalar minCos = Foam::cos(degToRad(planarAngle));
+
+    scalar concaveCos = Foam::cos(degToRad(layerParams.concaveAngle()));
+
+    Info<< nl
+        << "Merging all faces of a cell" << nl
+        << "---------------------------" << nl
+        << "    - which are on the same patch" << nl
+        << "    - which make an angle < " << planarAngle
+        << " degrees"
+        << " (cos:" << minCos << ')' << nl
+        << "    - as long as the resulting face doesn't become concave"
+        << " by more than "
+        << layerParams.concaveAngle() << " degrees" << nl
+        << "      (0=straight, 180=fully concave)" << nl
+        << endl;
+
+    const fvMesh& mesh = meshRefiner_.mesh();
+
+    List<labelPair> couples(localPointRegion::findDuplicateFacePairs(mesh));
+
+    labelList duplicateFace(mesh.nFaces(), -1);
+    forAll(couples, i)
+    {
+        const labelPair& cpl = couples[i];
+        duplicateFace[cpl[0]] = cpl[1];
+        duplicateFace[cpl[1]] = cpl[0];
+    }
+
+    label nChanged = meshRefiner_.mergePatchFacesUndo
+    (
+        minCos,
+        concaveCos,
+        meshRefiner_.meshedPatches(),
+        motionDict,
+        duplicateFace,
+        mergeType   // How to merge co-planar patch faces
+    );
+
+    nChanged += meshRefiner_.mergeEdgesUndo(minCos, motionDict);
+}
+
+
+void Foam::snappyLayerDriver::addLayers
+(
+    const layerParameters& layerParams,
+    const dictionary& motionDict,
+    const labelList& patchIDs,
+    const label nAllowableErrors,
+    decompositionMethod& decomposer,
+    fvMeshDistribute& distributor
+)
+{
+    fvMesh& mesh = meshRefiner_.mesh();
+
+    // Undistorted edge length
+    const scalar edge0Len = meshRefiner_.meshCutter().level0EdgeLength();
+
+    // faceZones of type internal or baffle (for merging points across)
+    labelList internalOrBaffleFaceZones;
+    {
+        List<surfaceZonesInfo::faceZoneType> fzTypes(2);
+        fzTypes[0] = surfaceZonesInfo::INTERNAL;
+        fzTypes[1] = surfaceZonesInfo::BAFFLE;
+        internalOrBaffleFaceZones = meshRefiner_.getZones(fzTypes);
+    }
+
+    // faceZones of type internal (for checking mesh quality across and
+    // merging baffles)
+    const labelList internalFaceZones
+    (
+        meshRefiner_.getZones
+        (
+            List<surfaceZonesInfo::faceZoneType>
+            (
+                1,
+                surfaceZonesInfo::INTERNAL
+            )
+        )
+    );
+
+    // Create baffles (pairs of faces that share the same points)
+    // Baffles stored as owner and neighbour face that have been created.
+    List<labelPair> baffles;
+    {
+        labelList originatingFaceZone;
+        meshRefiner_.createZoneBaffles
+        (
+            identity(mesh.faceZones().size()),
+            baffles,
+            originatingFaceZone
+        );
+
+        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+        {
+            const_cast<Time&>(mesh.time())++;
+            Info<< "Writing baffled mesh to time "
+                << meshRefiner_.timeName() << endl;
+            meshRefiner_.write
+            (
+                meshRefinement::debugType(debug),
+                meshRefinement::writeType
+                (
+                    meshRefinement::writeLevel()
+                  | meshRefinement::WRITEMESH
+                ),
+                mesh.time().path()/meshRefiner_.timeName()
+            );
+        }
+    }
+
+
+    // Duplicate points on faceZones of type boundary. Should normally already
+    // be done by snapping phase
+    {
+        autoPtr<mapPolyMesh> map = meshRefiner_.dupNonManifoldBoundaryPoints();
+        if (map)
+        {
+            const labelList& reverseFaceMap = map->reverseFaceMap();
+            forAll(baffles, i)
+            {
+                label f0 = reverseFaceMap[baffles[i].first()];
+                label f1 = reverseFaceMap[baffles[i].second()];
+                baffles[i] = labelPair(f0, f1);
+            }
+        }
+    }
 
-                List<labelPair> newMeshBaffles(baffles.size());
-                label newi = 0;
-                forAll(baffles, i)
+
+
+    // Now we have all patches determine the number of layer per patch
+    // This will be layerParams.numLayers() except for faceZones where one
+    // side does get layers and the other not in which case we want to
+    // suppress movement by explicitly setting numLayers 0
+    labelList numLayers(layerParams.numLayers());
+    {
+        labelHashSet layerIDs(patchIDs);
+        forAll(mesh.faceZones(), zonei)
+        {
+            label mpi, spi;
+            surfaceZonesInfo::faceZoneType fzType;
+            bool hasInfo = meshRefiner_.getFaceZoneInfo
+            (
+                mesh.faceZones()[zonei].name(),
+                mpi,
+                spi,
+                fzType
+            );
+            if (hasInfo)
+            {
+                const polyBoundaryMesh& pbm = mesh.boundaryMesh();
+                if (layerIDs.found(mpi) && !layerIDs.found(spi))
                 {
-                    const labelPair& p = baffles[i];
-                    labelPair newMeshBaffle
-                    (
-                        meshToNewMesh[p[0]],
-                        meshToNewMesh[p[1]]
-                    );
-                    if (newMeshBaffle[0] != -1 && newMeshBaffle[1] != -1)
-                    {
-                        newMeshBaffles[newi++] = newMeshBaffle;
-                    }
+                    // Only layers on master side. Fix points on slave side
+                    Info<< "On faceZone " << mesh.faceZones()[zonei].name()
+                        << " adding layers to master patch " << pbm[mpi].name()
+                        << " only. Freezing points on slave patch "
+                        << pbm[spi].name() << endl;
+                    numLayers[spi] = 0;
+                }
+                else if (!layerIDs.found(mpi) && layerIDs.found(spi))
+                {
+                    // Only layers on slave side. Fix points on master side
+                    Info<< "On faceZone " << mesh.faceZones()[zonei].name()
+                        << " adding layers to slave patch " << pbm[spi].name()
+                        << " only. Freezing points on master patch "
+                        << pbm[mpi].name() << endl;
+                    numLayers[mpi] = 0;
                 }
-                newMeshBaffles.setSize(newi);
+            }
+        }
+    }
+
+
+    // Duplicate points on faceZones that layers are added to
+    labelList pointToMaster;
+    dupFaceZonePoints
+    (
+        patchIDs,  // patch indices
+        numLayers, // number of layers per patch
+        baffles,
+        pointToMaster
+    );
+
+
+    // Add layers to patches
+    // ~~~~~~~~~~~~~~~~~~~~~
+
+    // Now we have
+    // - mesh with optional baffles and duplicated points for faceZones
+    //   where layers are to be added
+    // - pointToMaster    : correspondence for duplicated points
+    // - baffles          : list of pairs of faces
+
+
+    // Calculate 'base' point extrusion
+    autoPtr<indirectPrimitivePatch> pp
+    (
+        meshRefinement::makePatch
+        (
+            mesh,
+            patchIDs
+        )
+    );
+    // Make sure pp has adressing cached before changing mesh later on
+    (void)pp().meshPoints();
+    (void)pp().meshPointMap();
+
+    // Global face indices engine
+    globalIndex globalFaces(mesh.nFaces());
+
+    // Determine extrudePatch.edgeFaces in global numbering (so across
+    // coupled patches). This is used only to string up edges between coupled
+    // faces (all edges between same (global)face indices get extruded).
+    labelListList edgeGlobalFaces
+    (
+        addPatchCellLayer::globalEdgeFaces
+        (
+            mesh,
+            globalFaces,
+            *pp
+        )
+    );
+
+
+    // Point-wise extrusion data
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Displacement for all pp.localPoints. Set to large value so does
+    // not trigger the minThickness truncation (see syncPatchDisplacement below)
+    vectorField basePatchDisp(pp().nPoints(), vector(GREAT, GREAT, GREAT));
+
+    // Number of layers for all pp.localPoints. Note: only valid if
+    // extrudeStatus = EXTRUDE.
+    labelList basePatchNLayers(pp().nPoints(), Zero);
+
+    // Whether to add edge for all pp.localPoints.
+    List<extrudeMode> baseExtrudeStatus(pp().nPoints(), EXTRUDE);
+
+    // Ideal number of cells added
+    const label nIdealTotAddedCells = setPointNumLayers
+    (
+        layerParams,
+
+        numLayers,
+        patchIDs,
+        pp(),
+        edgeGlobalFaces,
+
+        basePatchDisp,
+        basePatchNLayers,
+        baseExtrudeStatus
+    );
+
+    // Overall thickness of all layers
+    scalarField baseThickness(pp().nPoints());
+    // Truncation thickness - when to truncate layers
+    scalarIOField baseMinThickness
+    (
+        IOobject
+        (
+            "minThickness",
+            meshRefiner_.timeName(),
+            mesh,
+            IOobject::NO_READ
+        ),
+        pp().nPoints()
+    );
+    // Expansion ratio
+    scalarField baseExpansionRatio(pp().nPoints());
+    calculateLayerThickness
+    (
+        pp(),
+        patchIDs,
+        layerParams,
+        meshRefiner_.meshCutter().cellLevel(),
+        basePatchNLayers,
+        edge0Len,
+
+        baseThickness,
+        baseMinThickness,
+        baseExpansionRatio
+    );
+
+
+    // Per cell 0 or number of layers in the cell column it is part of
+    labelList cellNLayers;
+    // Per face actual overall layer thickness
+    scalarField faceRealThickness;
+    // Per face wanted overall layer thickness
+    scalarField faceWantedThickness(mesh.nFaces(), Zero);
+    {
+        UIndirectList<scalar>(faceWantedThickness, pp().addressing()) =
+            avgPointData(pp(), baseThickness);
+    }
+
+
+    // Per patch point the number of layers to add. Is basePatchNLayers
+    // for nOuterIter = 1.
+    labelList deltaNLayers
+    (
+        (basePatchNLayers+layerParams.nOuterIter()-1)
+       /layerParams.nOuterIter()
+    );
+
+    // Per patch point the sum of added layers so far
+    labelList nAddedLayers(basePatchNLayers.size(), 0);
+
+
+    for (label layeri = 0; layeri < layerParams.nOuterIter(); layeri++)
+    {
+        // Divide layer addition into outer iterations. E.g. if
+        // nOutIter = 2, numLayers is 20 for patchA and 1 for patchB
+        // this will
+        // - iter0:
+        //  - add 10 layers to patchA and 1 to patchB
+        //  - layers are finalLayerThickness down to the number of layers
+        // - iter1 : add 10 layer to patchA and 0 to patchB
+
+
+        // Exit if nothing to be added
+        const label nToAdd = gSum(deltaNLayers);
+        if (debug)
+        {
+            Info<< "Outer iteration : " << layeri
+                << " to add in current iteration : " << nToAdd << endl;
+        }
+        if (nToAdd == 0)
+        {
+            break;
+        }
+
+
+        // Determine patches for extruded boundary edges. Calculates if any
+        // additional processor patches need to be constructed.
+
+        labelList edgePatchID;
+        labelList edgeZoneID;
+        boolList edgeFlip;
+        labelList inflateFaceID;
+        determineSidePatches
+        (
+            globalFaces,
+            edgeGlobalFaces,
+            *pp,
+
+            edgePatchID,
+            edgeZoneID,
+            edgeFlip,
+            inflateFaceID
+        );
 
-                internalBaffles = meshRefinement::subsetBaffles
-                (
-                    newMesh,
-                    internalFaceZones,
-                    newMeshBaffles
-                );
 
-                Info<< "Detected "
-                    << returnReduce(internalBaffles.size(), sumOp<label>())
-                    << " baffles across faceZones of type internal" << nl
-                    << endl;
-            }
+        // Point-wise extrusion data
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~
 
-            label nTotChanged = checkAndUnmark
-            (
-                addLayer,
-                meshQualityDict,
-                layerParams.additionalReporting(),
-                internalBaffles,
-                pp(),
-                newMesh,
+        // Displacement for all pp.localPoints. Set to large value so does
+        // not trigger the minThickness truncation (see syncPatchDisplacement
+        // below)
+        vectorField patchDisp(basePatchDisp);
 
-                patchDisp,
-                patchNLayers,
-                extrudeStatus
-            );
+        // Number of layers for all pp.localPoints. Note: only valid if
+        // extrudeStatus = EXTRUDE.
+        labelList patchNLayers(deltaNLayers);
 
-            label nTotExtruded = countExtrusion(*pp, extrudeStatus);
-            label nTotFaces = returnReduce(pp().size(), sumOp<label>());
-            label nTotAddedCells = returnReduce(nAddedCells, sumOp<label>());
-
-            Info<< "Extruding " << nTotExtruded
-                << " out of " << nTotFaces
-                << " faces (" << 100.0*nTotExtruded/nTotFaces << "%)."
-                << " Removed extrusion at " << nTotChanged << " faces."
-                << endl
-                << "Added " << nTotAddedCells << " out of "
-                << nIdealTotAddedCells
-                << " cells (" << 100.0*nTotAddedCells/nIdealTotAddedCells
-                << "%)." << endl;
-
-            if (nTotChanged == 0)
-            {
-                break;
-            }
+        // Whether to add edge for all pp.localPoints.
+        List<extrudeMode> extrudeStatus(baseExtrudeStatus);
 
-            // Reset mesh points and start again
-            mesh.movePoints(oldPoints);
-            pp().movePoints(mesh.points());
-            medialAxisMoverPtr().movePoints(mesh.points());
+        // At this point
+        //  - patchDisp is either zero or a large value
+        //  - patchNLayers is the overall number of layers
+        //  - extrudeStatus is EXTRUDE or NOEXTRUDE
 
-            // Grow out region of non-extrusion
-            for (label i = 0; i < layerParams.nGrow(); i++)
+        // Determine (wanted) point-wise overall layer thickness and expansion
+        // ratio for this deltaNLayers slice of the overall layers
+        scalarField sliceThickness(pp().nPoints());
+        {
+            forAll(baseThickness, pointi)
             {
-                growNoExtrusion
+                sliceThickness[pointi] = layerParameters::layerThickness
                 (
-                    *pp,
-                    patchDisp,
-                    patchNLayers,
-                    extrudeStatus
+                    basePatchNLayers[pointi],   // overall number of layers
+                    baseThickness[pointi],      // overall thickness
+                    baseExpansionRatio[pointi], // expansion ratio
+                    basePatchNLayers[pointi]
+                   -nAddedLayers[pointi]
+                   -patchNLayers[pointi],       // start index
+                    patchNLayers[pointi]        // nLayers to add
                 );
             }
-
-            Info<< endl;
         }
-    }
 
 
-    // At this point we have a (shrunk) mesh and a set of topology changes
-    // which will make a valid mesh with layer. Apply these changes to the
-    // current mesh.
+        // Current set of topology changes. (changing mesh clears out
+        // polyTopoChange)
+        polyTopoChange meshMod(mesh.boundaryMesh().size());
 
-    {
-        // Apply the stored topo changes to the current mesh.
-        autoPtr<mapPolyMesh> mapPtr = savedMeshMod.changeMesh(mesh, false);
-        mapPolyMesh& map = *mapPtr;
+        // Shrink mesh, add layers. Returns with any mesh changes in meshMod
+        labelList sliceCellNLayers;
+        scalarField sliceFaceRealThickness;
 
-        // Hack to remove meshPhi - mapped incorrectly. TBD.
-        mesh.clearOut();
+        addLayers
+        (
+            layerParams,
+            layerParams.nLayerIter(),
 
-        // Update fields
-        mesh.updateMesh(map);
+            // Mesh quality
+            motionDict,
+            layerParams.nRelaxedIter(),
+            nAllowableErrors,
 
-        // Move mesh (since morphing does not do this)
-        if (map.hasMotionPoints())
-        {
-            mesh.movePoints(map.preMotionPoints());
-        }
-        else
-        {
-            // Delete mesh volumes.
-            mesh.clearOut();
-        }
+            patchIDs,
+            internalFaceZones,
+            baffles,
+            numLayers,
+            nIdealTotAddedCells,
 
-        // Reset the instance for if in overwrite mode
-        mesh.setInstance(meshRefiner_.timeName());
+            // Patch information
+            globalFaces,
+            pp(),
+            edgeGlobalFaces,
+            edgePatchID,
+            edgeZoneID,
+            edgeFlip,
+            inflateFaceID,
 
-        meshRefiner_.updateMesh(map, labelList(0));
+            // Per patch point the wanted thickness
+            sliceThickness,
+            baseMinThickness,
+            baseExpansionRatio,
 
-        // Update numbering of faceWantedThickness
-        meshRefinement::updateList
-        (
-            map.faceMap(),
-            scalar(0),
-            faceWantedThickness
-        );
+            // Per patch point the wanted&obtained layers and thickness
+            patchDisp,
+            patchNLayers,
+            extrudeStatus,
 
-        // Print data now that we still have patches for the zones
-        //if (meshRefinement::outputLevel() & meshRefinement::OUTPUTLAYERINFO)
-        printLayerData
-        (
-            mesh,
-            patchIDs,
-            cellNLayers,
-            faceWantedThickness,
-            faceRealThickness
+            // Complete mesh changes
+            meshMod,
+
+            // Stats on new mesh
+            sliceCellNLayers,       //cellNLayers,
+            sliceFaceRealThickness  //faceRealThickness
         );
 
 
-        // Dump for debugging
-        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+        // Exit if nothing added
+        const label nTotalAdded = gSum(patchNLayers);
+        if (debug)
         {
-            const_cast<Time&>(mesh.time())++;
-            Info<< "Writing mesh with layers but disconnected to time "
-                << meshRefiner_.timeName() << endl;
-            meshRefiner_.write
-            (
-                meshRefinement::debugType(debug),
-                meshRefinement::writeType
-                (
-                    meshRefinement::writeLevel()
-                  | meshRefinement::WRITEMESH
-                ),
-                mesh.time().path()/meshRefiner_.timeName()
-            );
+            Info<< "Outer iteration : " << layeri
+                << " added in current iteration : " << nTotalAdded
+                << " out of : " << gSum(deltaNLayers) << endl;
+        }
+        if (nTotalAdded == 0)
+        {
+            break;
         }
 
 
-        // Use geometric detection of points-to-be-merged
-        //  - detect any boundary face created from a duplicated face (=baffle)
-        //  - on these mark any point created from a duplicated point
-        if (returnReduce(pointToMaster.size(), sumOp<label>()))
+        // Update wanted layer statistics
+        forAll(patchNLayers, pointi)
         {
-            // Estimate number of points-to-be-merged
-            DynamicList<label> candidates(baffles.size()*4);
+            nAddedLayers[pointi] += patchNLayers[pointi];
 
-            // Mark whether old face was on baffle
-            bitSet oldBaffleFace(map.nOldFaces());
-            forAll(baffles, i)
+            if (patchNLayers[pointi] == 0)
             {
-                const labelPair& baffle = baffles[i];
-                oldBaffleFace.set(baffle[0]);
-                oldBaffleFace.set(baffle[1]);
+                // No layers were added. Make sure that overall extrusion
+                // gets reset as well
+                unmarkExtrusion
+                (
+                    pointi,
+                    basePatchDisp,
+                    basePatchNLayers,
+                    baseExtrudeStatus
+                );
+                basePatchNLayers[pointi] = nAddedLayers[pointi];
+                deltaNLayers[pointi] = 0;
             }
-
-            // Collect candidate if
-            //  - point on boundary face originating from baffle
-            //  - and point originating from duplicate
-            for
-            (
-                label facei = mesh.nInternalFaces();
-                facei < mesh.nFaces();
-                facei++
-            )
+            else
             {
-                label oldFacei = map.faceMap()[facei];
-                if (oldFacei != -1 && oldBaffleFace.test(oldFacei))
-                {
-                    const face& f = mesh.faces()[facei];
-                    forAll(f, fp)
-                    {
-                        label pointi = f[fp];
-                        label oldPointi = map.pointMap()[pointi];
-
-                        if (pointToMaster[oldPointi] != -1)
-                        {
-                            candidates.append(pointi);
-                        }
-                    }
-                }
+                // Adjust the number of layers for the next iteration.
+                // Can never be
+                // higher than the adjusted overall number of layers.
+                // Note: is min necessary?
+                deltaNLayers[pointi] = max
+                (
+                    0,
+                    min
+                    (
+                        deltaNLayers[pointi],
+                        basePatchNLayers[pointi] - nAddedLayers[pointi]
+                    )
+                );
             }
+        }
 
 
-            // Do geometric merge. Ideally we'd like to use a topological
-            // merge but we've thrown away all layer-wise addressing when
-            // throwing away addPatchCellLayer engine. Also the addressing
-            // is extremely complicated. There is no problem with merging
-            // too many points; the problem would be if merging baffles.
-            // Trust mergeZoneBaffles to do sufficient checks.
-            labelList oldToNew;
-            label nNew = mergePoints
-            (
-                pointField(mesh.points(), candidates),
-                meshRefiner_.mergeDistance(),
-                false,
-                oldToNew
-            );
+        // At this point we have a (shrunk) mesh and a set of topology changes
+        // which will make a valid mesh with layer. Apply these changes to the
+        // current mesh.
 
-            // Extract points to be merged (i.e. multiple points originating
-            // from a single one)
+        {
+            // Apply the stored topo changes to the current mesh.
+            autoPtr<mapPolyMesh> mapPtr = meshMod.changeMesh(mesh, false);
+            mapPolyMesh& map = *mapPtr;
 
-            labelListList newToOld(invertOneToMany(nNew, oldToNew));
+            // Hack to remove meshPhi - mapped incorrectly. TBD.
+            mesh.clearOut();
 
-            // Extract points with more than one old one
-            pointToMaster.setSize(mesh.nPoints());
-            pointToMaster = -1;
+            // Update fields
+            mesh.updateMesh(map);
 
-            forAll(newToOld, newi)
+            // Move mesh (since morphing does not do this)
+            if (map.hasMotionPoints())
             {
-                const labelList& oldPoints = newToOld[newi];
-                if (oldPoints.size() > 1)
-                {
-                    labelList meshPoints
-                    (
-                        labelUIndList(candidates, oldPoints)
-                    );
-                    label masteri = min(meshPoints);
-                    forAll(meshPoints, i)
-                    {
-                        pointToMaster[meshPoints[i]] = masteri;
-                    }
-                }
+                mesh.movePoints(map.preMotionPoints());
+            }
+            else
+            {
+                // Delete mesh volumes.
+                mesh.clearOut();
             }
-        }
-    }
 
+            // Reset the instance for if in overwrite mode
+            mesh.setInstance(meshRefiner_.timeName());
 
+            meshRefiner_.updateMesh(map, labelList(0));
 
+            // Update numbering of accumulated cells
+            cellNLayers.setSize(map.nOldCells(), 0);
+            meshRefinement::updateList
+            (
+                map.cellMap(),
+                0,
+                cellNLayers
+            );
+            forAll(cellNLayers, i)
+            {
+                cellNLayers[i] += sliceCellNLayers[i];
+            }
 
+            faceRealThickness.setSize(map.nOldFaces(), scalar(0));
+            meshRefinement::updateList
+            (
+                map.faceMap(),
+                scalar(0),
+                faceRealThickness
+            );
+            faceRealThickness += sliceFaceRealThickness;
 
-    // Count duplicate points
-    label nPointPairs = 0;
-    forAll(pointToMaster, pointi)
-    {
-        label otherPointi = pointToMaster[pointi];
-        if (otherPointi != -1)
-        {
-            nPointPairs++;
-        }
-    }
-    reduce(nPointPairs, sumOp<label>());
-    if (nPointPairs > 0)
-    {
-        // Merge any duplicated points
-        Info<< "Merging " << nPointPairs << " duplicated points ..." << endl;
+            meshRefinement::updateList
+            (
+                map.faceMap(),
+                scalar(0),
+                faceWantedThickness
+            );
 
-        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
-        {
-            OBJstream str
+            // Print data now that we still have patches for the zones
+            //if (meshRefinement::outputLevel() & meshRefinement::OUTPUTLAYERINFO)
+            printLayerData
             (
-                mesh.time().path()
-              / "mergePoints_"
-              + meshRefiner_.timeName()
-              + ".obj"
+                mesh,
+                patchIDs,
+                cellNLayers,
+                faceWantedThickness,
+                faceRealThickness
             );
-            Info<< "Points to be merged to " << str.name() << endl;
-            forAll(pointToMaster, pointi)
+
+
+            // Dump for debugging
+            if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
             {
-                label otherPointi = pointToMaster[pointi];
-                if (otherPointi != -1)
-                {
-                    const point& pt = mesh.points()[pointi];
-                    const point& otherPt = mesh.points()[otherPointi];
-                    str.write(linePointRef(pt, otherPt));
-                }
+                const_cast<Time&>(mesh.time())++;
+                Info<< "Writing mesh with layers but disconnected to time "
+                    << meshRefiner_.timeName() << endl;
+                meshRefiner_.write
+                (
+                    meshRefinement::debugType(debug),
+                    meshRefinement::writeType
+                    (
+                        meshRefinement::writeLevel()
+                      | meshRefinement::WRITEMESH
+                    ),
+                    mesh.time().path()/meshRefiner_.timeName()
+                );
             }
-        }
 
 
-        autoPtr<mapPolyMesh> map = meshRefiner_.mergePoints(pointToMaster);
-        if (map)
-        {
-            inplaceReorder(map().reverseCellMap(), cellNLayers);
+            // Map baffles, pointToMaster
+            mapFaceZonePoints(map, baffles, pointToMaster);
 
-            const labelList& reverseFaceMap = map().reverseFaceMap();
-            inplaceReorder(reverseFaceMap, faceWantedThickness);
-            inplaceReorder(reverseFaceMap, faceRealThickness);
+            // Map patch and layer settings
+            labelList newToOldPatchPoints;
+            updatePatch(patchIDs, map, pp, newToOldPatchPoints);
 
-            Info<< "Merged points in = "
-                << mesh.time().cpuTimeIncrement() << " s\n" << nl << endl;
-        }
-    }
+            // Global face indices engine
+            globalFaces.reset(mesh.nFaces());
 
-    if (mesh.faceZones().size() > 0)
-    {
-        // Merge any baffles
-        Info<< "Converting baffles back into zoned faces ..."
-            << endl;
+            // Patch-edges to global faces using them
+            edgeGlobalFaces = addPatchCellLayer::globalEdgeFaces
+            (
+                mesh,
+                globalFaces,
+                pp()
+            );
 
-        autoPtr<mapPolyMesh> map = meshRefiner_.mergeZoneBaffles
-        (
-            true,   // internal zones
-            false   // baffle zones
-        );
-        if (map)
-        {
-            inplaceReorder(map().reverseCellMap(), cellNLayers);
+            // Map patch-point based data
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                vector::uniform(-1),
+                basePatchDisp
+            );
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                0,
+                basePatchNLayers
+            );
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                extrudeMode::NOEXTRUDE,
+                baseExtrudeStatus
+            );
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                scalar(0),
+                baseThickness
+            );
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                scalar(0),
+                baseMinThickness
+            );
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                GREAT,
+                baseExpansionRatio
+            );
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                0,
+                deltaNLayers
+            );
+            meshRefinement::updateList
+            (
+                newToOldPatchPoints,
+                0,
+                nAddedLayers
+            );
+        }
+    }
 
-            const labelList& faceMap = map().faceMap();
 
-            // Make sure to keep the max since on two patches only one has
-            // layers.
-            scalarField newFaceRealThickness(mesh.nFaces(), Zero);
-            scalarField newFaceWantedThickness(mesh.nFaces(), Zero);
-            forAll(newFaceRealThickness, facei)
-            {
-                label oldFacei = faceMap[facei];
-                if (oldFacei >= 0)
-                {
-                    scalar& realThick = newFaceRealThickness[facei];
-                    realThick = max(realThick, faceRealThickness[oldFacei]);
-                    scalar& wanted = newFaceWantedThickness[facei];
-                    wanted = max(wanted, faceWantedThickness[oldFacei]);
-                }
-            }
-            faceRealThickness.transfer(newFaceRealThickness);
-            faceWantedThickness.transfer(newFaceWantedThickness);
-        }
+    // Merge baffles
+    mergeFaceZonePoints
+    (
+        pointToMaster,  // per new mesh point : -1 or index of duplicated point
+        cellNLayers,        // per new cell : number of layers
+        faceRealThickness,  // per new face : actual thickness
+        faceWantedThickness // per new face : wanted thickness
+    );
 
-        Info<< "Converted baffles in = "
-            << meshRefiner_.mesh().time().cpuTimeIncrement()
-            << " s\n" << nl << endl;
-    }
 
     // Do final balancing
     // ~~~~~~~~~~~~~~~~~~
@@ -5032,15 +5569,31 @@ void Foam::snappyLayerDriver::doLayers
 
 
         // Do all topo changes
-        addLayers
-        (
-            layerParams,
-            motionDict,
-            patchIDs,
-            nInitErrors,
-            decomposer,
-            distributor
-        );
+        if (layerParams.nOuterIter() == -1)
+        {
+            // For testing. Can be removed once addLayers below works
+            addLayersSinglePass
+            (
+                layerParams,
+                motionDict,
+                patchIDs,
+                nInitErrors,
+                decomposer,
+                distributor
+            );
+        }
+        else
+        {
+            addLayers
+            (
+                layerParams,
+                motionDict,
+                patchIDs,
+                nInitErrors,
+                decomposer,
+                distributor
+            );
+        }
     }
 }
 
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.H b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.H
index 453cd5554b3..e24d83d11cf 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.H
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriver.H
@@ -32,6 +32,7 @@ Description
 
 SourceFiles
     snappyLayerDriver.C
+    snappyLayerDriverOneByOne.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -39,6 +40,7 @@ SourceFiles
 #define snappyLayerDriver_H
 
 #include "meshRefinement.H"
+#include "scalarIOField.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -52,6 +54,7 @@ class motionSmoother;
 class addPatchCellLayer;
 class faceSet;
 class layerParameters;
+class externalDisplacementMeshMover;
 
 /*---------------------------------------------------------------------------*\
                            Class snappyLayerDriver Declaration
@@ -250,12 +253,50 @@ private:
                     const labelList& patchToNLayers,
                     const labelList& patchIDs,
                     const indirectPrimitivePatch& pp,
-                    pointField& patchDisp,
+
                     labelList& patchNLayers,
                     List<extrudeMode>& extrudeStatus,
                     label& nIdealAddedCells
                 ) const;
 
+                //- Determine number of layers per point; include static checks
+                //- on invalid extrusion (e.g. non-manifold)
+                label setPointNumLayers
+                (
+                    const layerParameters& layerParams,
+
+                    const labelList& numLayers,
+                    const labelList& patchIDs,
+                    const indirectPrimitivePatch& pp,
+                    const labelListList& edgeGlobalFaces,
+
+                    vectorField& patchDisp,
+                    labelList& patchNLayers,
+                    List<extrudeMode>&
+                ) const;
+                autoPtr<externalDisplacementMeshMover> makeMeshMover
+                (
+                    const layerParameters& layerParams,
+                    const dictionary& motionDict,
+                    const labelList& internalFaceZones,
+                    const scalarIOField& minThickness,
+                    pointVectorField& displacement
+                ) const;
+                void mapFaceZonePoints
+                (
+                    const mapPolyMesh& map,
+                    labelPairList& baffles,
+                    labelList& pointToMaster
+                ) const;
+                void updatePatch
+                (
+                    const labelList& patchIDs,
+                    const mapPolyMesh& map,
+                    autoPtr<indirectPrimitivePatch>& pp,
+                    labelList& newToOldPatchPoints
+                ) const;
+
+
                 //- Helper function to make a pointVectorField with correct
                 //  bcs for layer addition:
                 //  - numLayers > 0         : fixedValue
@@ -278,6 +319,25 @@ private:
                     List<extrudeMode>& extrudeStatus
                 ) const;
 
+                //- Duplicate points on faceZones with layers
+                void dupFaceZonePoints
+                (
+                    const labelList& patchIDs,  // patch indices
+                    const labelList& numLayers, // number of layers per patch
+                    List<labelPair> baffles,
+                    labelList& pointToMaster
+                );
+
+                //- Re-merge points/faces on faceZones. Opposite of
+                //- dupFaceZonePoints above
+                void mergeFaceZonePoints
+                (
+                    const labelList& pointToMaster,
+                    labelList& cellNLayers,
+                    scalarField& faceRealThickness,
+                    scalarField& faceWantedThickness
+                );
+
                 //- See what zones and patches edges should be extruded into
                 void determineSidePatches
                 (
@@ -600,6 +660,39 @@ public:
                 const meshRefinement::FaceMergeType mergeType
             );
 
+            void addLayers
+            (
+                const layerParameters& layerParams,
+                const label nLayerIter,
+
+                const dictionary& motionDict,
+                const label nRelaxedIter,
+                const label nAllowableErrors,
+
+                const labelList& patchIDs,
+                const labelList& internalFaceZones,
+                const List<labelPair>& baffles,
+                const labelList& numLayers,
+                const label nIdealTotAddedCells,
+
+                const globalIndex& globalFaces,
+                indirectPrimitivePatch& pp,
+                const labelListList& edgeGlobalFaces,
+                const labelList& edgePatchID,
+                const labelList& edgeZoneID,
+                const boolList& edgeFlip,
+                const labelList& inflateFaceID,
+                const scalarField& thickness,
+                const scalarIOField& minThickness,
+                const scalarField& expansionRatio,
+                vectorField& patchDisp,
+                labelList& patchNLayers,
+                List<extrudeMode>& extrudeStatus,
+                polyTopoChange& savedMeshMod,
+                labelList& cellNLayers,
+                scalarField& faceRealThickness
+            );
+
             //- Add cell layers
             void addLayers
             (
@@ -611,6 +704,17 @@ public:
                 fvMeshDistribute& distributor
             );
 
+            //- For debugging. Can be removed.
+            void addLayersSinglePass
+            (
+                const layerParameters& layerParams,
+                const dictionary& motionDict,
+                const labelList& patchIDs,
+                const label nAllowableErrors,
+                decompositionMethod& decomposer,
+                fvMeshDistribute& distributor
+            );
+
             //- Add layers according to the dictionary settings
             void doLayers
             (
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriverSinglePass.C b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriverSinglePass.C
new file mode 100644
index 00000000000..12eb3eaff5e
--- /dev/null
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyLayerDriverSinglePass.C
@@ -0,0 +1,500 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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/>.
+
+Description
+    Single pass layer addition. Can be removed once multi-pass works ok.
+
+\*----------------------------------------------------------------------------*/
+
+#include "snappyLayerDriver.H"
+//#include "motionSmoother.H"
+//#include "pointSet.H"
+//#include "faceSet.H"
+//#include "cellSet.H"
+#include "polyTopoChange.H"
+#include "mapPolyMesh.H"
+#include "addPatchCellLayer.H"
+#include "mapDistributePolyMesh.H"
+//#include "OBJstream.H"
+#include "layerParameters.H"
+#include "externalDisplacementMeshMover.H"
+//#include "profiling.H"
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::snappyLayerDriver::addLayersSinglePass
+(
+    const layerParameters& layerParams,
+    const dictionary& motionDict,
+    const labelList& patchIDs,
+    const label nAllowableErrors,
+    decompositionMethod& decomposer,
+    fvMeshDistribute& distributor
+)
+{
+    fvMesh& mesh = meshRefiner_.mesh();
+
+    // Undistorted edge length
+    const scalar edge0Len = meshRefiner_.meshCutter().level0EdgeLength();
+
+    // faceZones of type internal or baffle (for merging points across)
+    labelList internalOrBaffleFaceZones;
+    {
+        List<surfaceZonesInfo::faceZoneType> fzTypes(2);
+        fzTypes[0] = surfaceZonesInfo::INTERNAL;
+        fzTypes[1] = surfaceZonesInfo::BAFFLE;
+        internalOrBaffleFaceZones = meshRefiner_.getZones(fzTypes);
+    }
+
+    // faceZones of type internal (for checking mesh quality across and
+    // merging baffles)
+    const labelList internalFaceZones
+    (
+        meshRefiner_.getZones
+        (
+            List<surfaceZonesInfo::faceZoneType>
+            (
+                1,
+                surfaceZonesInfo::INTERNAL
+            )
+        )
+    );
+
+    // Create baffles (pairs of faces that share the same points)
+    // Baffles stored as owner and neighbour face that have been created.
+    List<labelPair> baffles;
+    {
+        labelList originatingFaceZone;
+        meshRefiner_.createZoneBaffles
+        (
+            identity(mesh.faceZones().size()),
+            baffles,
+            originatingFaceZone
+        );
+
+        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+        {
+            const_cast<Time&>(mesh.time())++;
+            Info<< "Writing baffled mesh to time "
+                << meshRefiner_.timeName() << endl;
+            meshRefiner_.write
+            (
+                meshRefinement::debugType(debug),
+                meshRefinement::writeType
+                (
+                    meshRefinement::writeLevel()
+                  | meshRefinement::WRITEMESH
+                ),
+                mesh.time().path()/meshRefiner_.timeName()
+            );
+        }
+    }
+
+
+    // Duplicate points on faceZones of type boundary. Should normally already
+    // be done by snapping phase
+    {
+        autoPtr<mapPolyMesh> map = meshRefiner_.dupNonManifoldBoundaryPoints();
+        if (map)
+        {
+            const labelList& reverseFaceMap = map->reverseFaceMap();
+            forAll(baffles, i)
+            {
+                label f0 = reverseFaceMap[baffles[i].first()];
+                label f1 = reverseFaceMap[baffles[i].second()];
+                baffles[i] = labelPair(f0, f1);
+            }
+        }
+    }
+
+
+
+    // Now we have all patches determine the number of layer per patch
+    // This will be layerParams.numLayers() except for faceZones where one
+    // side does get layers and the other not in which case we want to
+    // suppress movement by explicitly setting numLayers 0
+    labelList numLayers(layerParams.numLayers());
+    {
+        labelHashSet layerIDs(patchIDs);
+        forAll(mesh.faceZones(), zonei)
+        {
+            label mpi, spi;
+            surfaceZonesInfo::faceZoneType fzType;
+            bool hasInfo = meshRefiner_.getFaceZoneInfo
+            (
+                mesh.faceZones()[zonei].name(),
+                mpi,
+                spi,
+                fzType
+            );
+            if (hasInfo)
+            {
+                const polyBoundaryMesh& pbm = mesh.boundaryMesh();
+                if (layerIDs.found(mpi) && !layerIDs.found(spi))
+                {
+                    // Only layers on master side. Fix points on slave side
+                    Info<< "On faceZone " << mesh.faceZones()[zonei].name()
+                        << " adding layers to master patch " << pbm[mpi].name()
+                        << " only. Freezing points on slave patch "
+                        << pbm[spi].name() << endl;
+                    numLayers[spi] = 0;
+                }
+                else if (!layerIDs.found(mpi) && layerIDs.found(spi))
+                {
+                    // Only layers on slave side. Fix points on master side
+                    Info<< "On faceZone " << mesh.faceZones()[zonei].name()
+                        << " adding layers to slave patch " << pbm[spi].name()
+                        << " only. Freezing points on master patch "
+                        << pbm[mpi].name() << endl;
+                    numLayers[mpi] = 0;
+                }
+            }
+        }
+    }
+
+
+    // Duplicate points on faceZones that layers are added to
+    labelList pointToMaster;
+    dupFaceZonePoints
+    (
+        patchIDs,  // patch indices
+        numLayers, // number of layers per patch
+        baffles,
+        pointToMaster
+    );
+
+
+    // Add layers to patches
+    // ~~~~~~~~~~~~~~~~~~~~~
+
+    // Now we have
+    // - mesh with optional baffles and duplicated points for faceZones
+    //   where layers are to be added
+    // - pointToMaster    : correspondence for duplicated points
+    // - baffles          : list of pairs of faces
+
+
+    autoPtr<indirectPrimitivePatch> pp
+    (
+        meshRefinement::makePatch
+        (
+            mesh,
+            patchIDs
+        )
+    );
+
+
+    // Global face indices engine
+    const globalIndex globalFaces(mesh.nFaces());
+
+    // Determine extrudePatch.edgeFaces in global numbering (so across
+    // coupled patches). This is used only to string up edges between coupled
+    // faces (all edges between same (global)face indices get extruded).
+    const labelListList edgeGlobalFaces
+    (
+        addPatchCellLayer::globalEdgeFaces
+        (
+            mesh,
+            globalFaces,
+            *pp
+        )
+    );
+
+    // Determine patches for extruded boundary edges. Calculates if any
+    // additional processor patches need to be constructed.
+
+    labelList edgePatchID;
+    labelList edgeZoneID;
+    boolList edgeFlip;
+    labelList inflateFaceID;
+    determineSidePatches
+    (
+        globalFaces,
+        edgeGlobalFaces,
+        *pp,
+
+        edgePatchID,
+        edgeZoneID,
+        edgeFlip,
+        inflateFaceID
+    );
+
+
+    // Point-wise extrusion data
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Displacement for all pp.localPoints. Set to large value so does
+    // not trigger the minThickness truncation (see syncPatchDisplacement below)
+    vectorField patchDisp(pp().nPoints(), vector(GREAT, GREAT, GREAT));
+
+    // Number of layers for all pp.localPoints. Note: only valid if
+    // extrudeStatus = EXTRUDE.
+    labelList patchNLayers(pp().nPoints(), Zero);
+
+    // Whether to add edge for all pp.localPoints.
+    List<extrudeMode> extrudeStatus(pp().nPoints(), EXTRUDE);
+
+    // Ideal number of cells added
+    const label nIdealTotAddedCells = setPointNumLayers
+    (
+        layerParams,
+
+        numLayers,
+        patchIDs,
+        pp(),
+        edgeGlobalFaces,
+
+        patchDisp,
+        patchNLayers,
+        extrudeStatus
+    );
+
+    // Determine (wanted) point-wise overall layer thickness and expansion
+    // ratio
+    scalarField thickness(pp().nPoints());
+    scalarIOField minThickness
+    (
+        IOobject
+        (
+            "minThickness",
+            meshRefiner_.timeName(),
+            mesh,
+            IOobject::NO_READ
+        ),
+        pp().nPoints()
+    );
+    scalarField expansionRatio(pp().nPoints());
+    calculateLayerThickness
+    (
+        *pp,
+        patchIDs,
+        layerParams,
+        meshRefiner_.meshCutter().cellLevel(),
+        patchNLayers,
+        edge0Len,
+
+        thickness,
+        minThickness,
+        expansionRatio
+    );
+
+
+
+    // Per cell 0 or number of layers in the cell column it is part of
+    labelList cellNLayers;
+    // Per face actual overall layer thickness
+    scalarField faceRealThickness;
+    // Per face wanted overall layer thickness
+    scalarField faceWantedThickness(mesh.nFaces(), Zero);
+    {
+        UIndirectList<scalar>(faceWantedThickness, pp->addressing()) =
+            avgPointData(*pp, thickness);
+    }
+
+
+    // Current set of topology changes. (changing mesh clears out
+    // polyTopoChange)
+    polyTopoChange meshMod(mesh.boundaryMesh().size());
+
+    // Play changes into meshMod
+    addLayers
+    (
+        layerParams,
+        layerParams.nLayerIter(),
+
+        // Mesh quality
+        motionDict,
+        layerParams.nRelaxedIter(),
+        nAllowableErrors,
+
+        patchIDs,
+        internalFaceZones,
+        baffles,
+        numLayers,
+        nIdealTotAddedCells,
+
+        // Patch information
+        globalFaces,
+        pp(),
+        edgeGlobalFaces,
+        edgePatchID,
+        edgeZoneID,
+        edgeFlip,
+        inflateFaceID,
+
+        // Per patch point the wanted thickness
+        thickness,
+        minThickness,
+        expansionRatio,
+
+        // Per patch point the obtained thickness
+        patchDisp,
+        patchNLayers,
+        extrudeStatus,
+
+        // Complete mesh changes
+        meshMod,
+
+        // Stats
+        cellNLayers,
+        faceRealThickness
+    );
+
+
+    // At this point we have a (shrunk) mesh and a set of topology changes
+    // which will make a valid mesh with layer. Apply these changes to the
+    // current mesh.
+
+    {
+        // Apply the stored topo changes to the current mesh.
+        autoPtr<mapPolyMesh> mapPtr = meshMod.changeMesh(mesh, false);
+        mapPolyMesh& map = *mapPtr;
+
+        // Hack to remove meshPhi - mapped incorrectly. TBD.
+        mesh.clearOut();
+
+        // Update fields
+        mesh.updateMesh(map);
+
+        // Move mesh (since morphing does not do this)
+        if (map.hasMotionPoints())
+        {
+            mesh.movePoints(map.preMotionPoints());
+        }
+        else
+        {
+            // Delete mesh volumes.
+            mesh.clearOut();
+        }
+
+        // Reset the instance for if in overwrite mode
+        mesh.setInstance(meshRefiner_.timeName());
+
+        meshRefiner_.updateMesh(map, labelList(0));
+
+        // Update numbering of faceWantedThickness
+        meshRefinement::updateList
+        (
+            map.faceMap(),
+            scalar(0),
+            faceWantedThickness
+        );
+
+        // Print data now that we still have patches for the zones
+        //if (meshRefinement::outputLevel() & meshRefinement::OUTPUTLAYERINFO)
+        printLayerData
+        (
+            mesh,
+            patchIDs,
+            cellNLayers,
+            faceWantedThickness,
+            faceRealThickness
+        );
+
+
+        // Dump for debugging
+        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+        {
+            const_cast<Time&>(mesh.time())++;
+            Info<< "Writing mesh with layers but disconnected to time "
+                << meshRefiner_.timeName() << endl;
+            meshRefiner_.write
+            (
+                meshRefinement::debugType(debug),
+                meshRefinement::writeType
+                (
+                    meshRefinement::writeLevel()
+                  | meshRefinement::WRITEMESH
+                ),
+                mesh.time().path()/meshRefiner_.timeName()
+            );
+        }
+
+
+        // Map baffles, pointToMaster
+        mapFaceZonePoints(map, baffles, pointToMaster);
+    }
+
+
+    // Merge baffles
+    mergeFaceZonePoints
+    (
+        pointToMaster, // -1 or index of duplicated point
+        cellNLayers,
+        faceRealThickness,
+        faceWantedThickness
+    );
+
+
+    // Do final balancing
+    // ~~~~~~~~~~~~~~~~~~
+
+    if (Pstream::parRun())
+    {
+        Info<< nl
+            << "Doing final balancing" << nl
+            << "---------------------" << nl
+            << endl;
+
+        if (debug)
+        {
+            const_cast<Time&>(mesh.time())++;
+        }
+
+        // Balance. No restriction on face zones and baffles.
+        autoPtr<mapDistributePolyMesh> map = meshRefiner_.balance
+        (
+            false,
+            false,
+            scalarField(mesh.nCells(), 1.0),
+            decomposer,
+            distributor
+        );
+
+        // Re-distribute flag of layer faces and cells
+        map().distributeCellData(cellNLayers);
+        map().distributeFaceData(faceWantedThickness);
+        map().distributeFaceData(faceRealThickness);
+    }
+
+
+    // Write mesh data
+    // ~~~~~~~~~~~~~~~
+
+    if (!dryRun_)
+    {
+        writeLayerData
+        (
+            mesh,
+            patchIDs,
+            cellNLayers,
+            faceWantedThickness,
+            faceRealThickness
+        );
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.C b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.C
index b8ac1ac7f56..95d164eb82d 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.C
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.C
@@ -343,7 +343,8 @@ Foam::label Foam::snappyRefineDriver::smallFeatureRefine
 Foam::label Foam::snappyRefineDriver::surfaceOnlyRefine
 (
     const refinementParameters& refineParams,
-    const label maxIter
+    const label maxIter,
+    const label leakBlockageIter
 )
 {
     if (dryRun_)
@@ -360,6 +361,7 @@ Foam::label Foam::snappyRefineDriver::surfaceOnlyRefine
 
     addProfiling(surface, "snappyHexMesh::refine::surface");
     const fvMesh& mesh = meshRefiner_.mesh();
+    const refinementSurfaces& surfaces = meshRefiner_.surfaces();
 
     // Determine the maximum refinement level over all surfaces. This
     // determines the minimum number of surface refinement iterations.
@@ -374,6 +376,52 @@ Foam::label Foam::snappyRefineDriver::surfaceOnlyRefine
             << endl;
 
 
+        // Do optional leak closing (by removing cells)
+        if (iter >= leakBlockageIter)
+        {
+            // Block off intersections with unzoned surfaces with specified
+            // leakLevel < iter
+            const labelList unnamedSurfaces
+            (
+                surfaceZonesInfo::getUnnamedSurfaces
+                (
+                    surfaces.surfZones()
+                )
+            );
+
+            DynamicList<label> selectedSurfaces(unnamedSurfaces.size());
+            for (const label surfi : unnamedSurfaces)
+            {
+                const label regioni = surfaces.globalRegion(surfi, 0);
+
+                // Take shortcut: assume all cells on surface are refined to
+                // its refinement level at iteration iter. So just use the
+                // iteration to see if the surface is a candidate.
+                if (iter > surfaces.leakLevel()[regioni])
+                {
+                    selectedSurfaces.append(surfi);
+                }
+            }
+
+            if
+            (
+                selectedSurfaces.size()
+             && refineParams.locationsOutsideMesh().size()
+            )
+            {
+               meshRefiner_.blockLeakFaces
+                (
+                    globalToMasterPatch_,
+                    globalToSlavePatch_,
+                    refineParams.locationsInMesh(),
+                    refineParams.zonesInMesh(),
+                    refineParams.locationsOutsideMesh(),
+                    selectedSurfaces
+                );
+            }
+        }
+
+
         // Determine cells to refine
         // ~~~~~~~~~~~~~~~~~~~~~~~~~
         // Only look at surface intersections (minLevel and surface curvature),
@@ -1762,15 +1810,19 @@ void Foam::snappyRefineDriver::removeInsideCells
     }
 
     // Remove any cells inside limitShells with level -1
-    meshRefiner_.removeLimitShells
-    (
-        nBufferLayers,
-        1,
-        globalToMasterPatch_,
-        globalToSlavePatch_,
-        refineParams.locationsInMesh(),
-        refineParams.zonesInMesh()
-    );
+    if (meshRefiner_.limitShells().shells().size())
+    {
+        meshRefiner_.removeLimitShells
+        (
+            nBufferLayers,
+            1,
+            globalToMasterPatch_,
+            globalToSlavePatch_,
+            refineParams.locationsInMesh(),
+            refineParams.zonesInMesh(),
+            refineParams.locationsOutsideMesh()
+        );
+    }
 
     // Fix any additional (e.g. locationsOutsideMesh). Note: probably not
     // necessary.
@@ -2783,8 +2835,7 @@ void Foam::snappyRefineDriver::baffleAndSplitMesh
             globalToMasterPatch_,
             globalToSlavePatch_,
             refineParams.locationsInMesh(),
-            refineParams.locationsOutsideMesh(),
-            setFormatter_
+            refineParams.locationsOutsideMesh()
         );
     }
 }
@@ -2829,6 +2880,8 @@ void Foam::snappyRefineDriver::zonify
             refineParams.nErodeCellZone(),
             refineParams.locationsInMesh(),
             refineParams.zonesInMesh(),
+            refineParams.locationsOutsideMesh(),
+            setFormatter_,
             zonesToFaceZone
         );
 
@@ -2916,8 +2969,7 @@ void Foam::snappyRefineDriver::splitAndMergeBaffles
         globalToMasterPatch_,
         globalToSlavePatch_,
         refineParams.locationsInMesh(),
-        refineParams.locationsOutsideMesh(),
-        setFormatter_
+        refineParams.locationsOutsideMesh()
     );
 
     if (debug)
@@ -3332,7 +3384,8 @@ void Foam::snappyRefineDriver::doRefine
         surfaceOnlyRefine
         (
             refineParams,
-            20     // maxIter
+            20,         // maxIter
+            labelMax    // no leak detection yet since all in same cell
         );
 
         // Refine cells that contain a gap
@@ -3348,7 +3401,8 @@ void Foam::snappyRefineDriver::doRefine
     surfaceOnlyRefine
     (
         refineParams,
-        100     // maxIter
+        100,    // maxIter
+        0       // Iteration to start leak detection and closure
     );
 
     // Pass1 of automatic gap-level refinement: surface-intersected cells
@@ -3424,17 +3478,31 @@ void Foam::snappyRefineDriver::doRefine
         100    // maxIter
     );
 
-    //// Re-remove cells inbetween two surfaces. The shell refinement/
-    //// directional shell refinement might have caused new small
-    //// gaps to be resolved. This is currently disabled since it finds
-    //// gaps just because it very occasionally walks around already removed
-    //// gaps and still finds 'opposite' surfaces. This probably need additional
-    //// path-length counting to avoid walking huge distances.
-    //surfaceProximityBlock
-    //(
-    //    refineParams,
-    //    1  //100     // maxIter
-    //);
+    // Block gaps (always, ignore surface leakLevel)
+    if (refineParams.locationsOutsideMesh().size())
+    {
+        // For now: only check leaks on meshed surfaces. The problem is that
+        // blockLeakFaces always generates baffles any not just faceZones ...
+        const labelList unnamedSurfaces
+        (
+            surfaceZonesInfo::getUnnamedSurfaces
+            (
+                meshRefiner_.surfaces().surfZones()
+            )
+        );
+        if (unnamedSurfaces.size())
+        {
+            meshRefiner_.blockLeakFaces
+            (
+                globalToMasterPatch_,
+                globalToSlavePatch_,
+                refineParams.locationsInMesh(),
+                refineParams.zonesInMesh(),
+                refineParams.locationsOutsideMesh(),
+                unnamedSurfaces
+            );
+        }
+    }
 
     if
     (
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.H b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.H
index 30827dcf24c..dbfb3dda614 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.H
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappyRefineDriver.H
@@ -125,7 +125,8 @@ class snappyRefineDriver
         label surfaceOnlyRefine
         (
             const refinementParameters& refineParams,
-            const label maxIter
+            const label maxIter,
+            const label leakBlockageIter    // when to start leak closing
         );
 
         //- Refine all cells in small gaps
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.C b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.C
index dac2dee2405..d80cc3432f2 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.C
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.C
@@ -741,6 +741,75 @@ bool Foam::snappySnapDriver::outwardsDisplacement
 }
 
 
+void Foam::snappySnapDriver::freezeExposedPoints
+(
+    const meshRefinement& meshRefiner,
+    const word& fzName,     // faceZone name
+    const word& pzName,     // pointZone name
+    const indirectPrimitivePatch& outside,
+    vectorField& outsideDisp
+)
+{
+    const fvMesh& mesh = meshRefiner.mesh();
+    const pointZoneMesh& pointZones = mesh.pointZones();
+
+    bitSet isFrozenPoint(mesh.nPoints());
+
+    // Add frozen points
+    const label pointZonei = pointZones.findZoneID(pzName);
+    if (pointZonei != -1)
+    {
+        isFrozenPoint.set(pointZones[pointZonei]);
+    }
+
+    // Add (inside) points of frozen faces
+    const faceZoneMesh& faceZones = mesh.faceZones();
+    const label faceZonei = faceZones.findZoneID(fzName);
+    if (faceZonei != -1)
+    {
+        const uindirectPrimitivePatch pp
+        (
+            UIndirectList<face>(mesh.faces(), faceZones[faceZonei]),
+            mesh.points()
+        );
+
+        // Count number of faces per edge
+        const labelList nEdgeFaces(meshRefiner.countEdgeFaces(pp));
+
+        // Freeze all internal points
+        forAll(nEdgeFaces, edgei)
+        {
+            if (nEdgeFaces[edgei] != 1)
+            {
+                const edge& e = pp.edges()[edgei];
+                isFrozenPoint.set(pp.meshPoints()[e[0]]);
+                isFrozenPoint.set(pp.meshPoints()[e[1]]);
+            }
+        }
+    }
+
+    syncTools::syncPointList
+    (
+        mesh,
+        isFrozenPoint,
+        orEqOp<unsigned int>(),
+        0u
+    );
+
+    if (returnReduce(isFrozenPoint.count(), sumOp<label>()))
+    {
+        for (const label pointi : isFrozenPoint)
+        {
+            const auto& iter = outside.meshPointMap().find(pointi);
+            if (iter.found())
+            {
+                outsideDisp[iter()] = Zero;
+            }
+        }
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::snappySnapDriver::snappySnapDriver
@@ -2799,6 +2868,16 @@ void Foam::snappySnapDriver::doSnap
             // Check for displacement being outwards.
             outwardsDisplacement(pp, disp);
 
+            // Freeze points on exposed points/faces
+            freezeExposedPoints
+            (
+                meshRefiner_,
+                "frozenFaces",      // faceZone name
+                "frozenPoints",     // pointZone name
+                pp,
+                disp
+            );
+
             // Set initial distribution of displacement field (on patches)
             // from patchDisp and make displacement consistent with b.c.
             // on displacement pointVectorField.
diff --git a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.H b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.H
index 7d6a24ed140..5f6ed8fc1be 100644
--- a/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.H
+++ b/src/mesh/snappyHexMesh/snappyHexMeshDriver/snappySnapDriver.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -142,6 +143,16 @@ class snappySnapDriver
                 const vectorField&
             );
 
+            //- Freeze points on pointZone or (inside of) faceZone
+            static void freezeExposedPoints
+            (
+                const meshRefinement& meshRefiner,
+                const word& fzName,     // faceZone name
+                const word& pzName,     // pointZone name
+                const indirectPrimitivePatch& outside,
+                vectorField& patchDisp
+            );
+
             //- Detect warpage
             void detectWarpedFaces
             (
diff --git a/src/meshTools/Make/files b/src/meshTools/Make/files
index af715350b76..d63788f99a2 100644
--- a/src/meshTools/Make/files
+++ b/src/meshTools/Make/files
@@ -214,6 +214,7 @@ $(faceSources)/regionToFace/regionToFace.C
 $(faceSources)/searchableSurfaceToFace/searchableSurfaceToFace.C
 $(faceSources)/sphereToFace/sphereToFace.C
 $(faceSources)/zoneToFace/zoneToFace.C
+$(faceSources)/holeToFace/holeToFace.C
 
 pointSources = topoSet/pointSources
 $(pointSources)/topoSetPointSource/topoSetPointSource.C
diff --git a/src/meshTools/polyTopoChange/polyTopoChange.C b/src/meshTools/polyTopoChange/polyTopoChange.C
index 38013b4c3ce..9b0803595b0 100644
--- a/src/meshTools/polyTopoChange/polyTopoChange.C
+++ b/src/meshTools/polyTopoChange/polyTopoChange.C
@@ -888,18 +888,7 @@ void Foam::polyTopoChange::reorderCompactFaces
 }
 
 
-// Compact all and orders points and faces:
-// - points into internal followed by external points
-// - internalfaces upper-triangular
-// - externalfaces after internal ones.
-void Foam::polyTopoChange::compact
-(
-    const bool orderCells,
-    const bool orderPoints,
-    label& nInternalPoints,
-    labelList& patchSizes,
-    labelList& patchStarts
-)
+void Foam::polyTopoChange::shrink()
 {
     points_.shrink();
     pointMap_.shrink();
@@ -915,7 +904,23 @@ void Foam::polyTopoChange::compact
     cellMap_.shrink();
     reverseCellMap_.shrink();
     cellZone_.shrink();
+}
+
 
+// Compact all and orders points and faces:
+// - points into internal followed by external points
+// - internalfaces upper-triangular
+// - externalfaces after internal ones.
+void Foam::polyTopoChange::compact
+(
+    const bool orderCells,
+    const bool orderPoints,
+    label& nInternalPoints,
+    labelList& patchSizes,
+    labelList& patchStarts
+)
+{
+    shrink();
 
     // Compact points
     label nActivePoints = 0;
diff --git a/src/meshTools/polyTopoChange/polyTopoChange.H b/src/meshTools/polyTopoChange/polyTopoChange.H
index cea7508ab45..98c74ebdfb4 100644
--- a/src/meshTools/polyTopoChange/polyTopoChange.H
+++ b/src/meshTools/polyTopoChange/polyTopoChange.H
@@ -511,6 +511,10 @@ public:
                 const label nCells
             );
 
+            //- Shrink storage (does not remove any elements; just compacts
+            //- dynamic lists
+            void shrink();
+
             //- Move all points. Incompatible with other topology changes.
             void movePoints(const pointField& newPoints);
 
diff --git a/src/meshTools/topoSet/faceSources/holeToFace/holeToFace.C b/src/meshTools/topoSet/faceSources/holeToFace/holeToFace.C
new file mode 100644
index 00000000000..e68ac1b83c3
--- /dev/null
+++ b/src/meshTools/topoSet/faceSources/holeToFace/holeToFace.C
@@ -0,0 +1,1359 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020-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 "holeToFace.H"
+#include "transform.H"
+#include "faceSet.H"
+#include "cellSet.H"
+#include "addToRunTimeSelectionTable.H"
+#include "OBJstream.H"
+//#include "fvMesh.H"
+//#include "volFields.H"
+//#include "surfaceFields.H"
+#include "topoDistanceData.H"
+#include "FaceCellWave.H"
+#include "syncTools.H"
+
+#include "edgeTopoDistanceData.H"
+#include "PatchEdgeFaceWave.H"
+#include "indirectPrimitivePatch.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(holeToFace, 0);
+    addToRunTimeSelectionTable(topoSetSource, holeToFace, word);
+    addToRunTimeSelectionTable(topoSetSource, holeToFace, istream);
+    addToRunTimeSelectionTable(topoSetFaceSource, holeToFace, word);
+    addToRunTimeSelectionTable(topoSetFaceSource, holeToFace, istream);
+    addNamedToRunTimeSelectionTable
+    (
+        topoSetFaceSource,
+        holeToFace,
+        word,
+        hole
+    );
+    addNamedToRunTimeSelectionTable
+    (
+        topoSetFaceSource,
+        holeToFace,
+        istream,
+        hole
+    );
+}
+
+
+Foam::topoSetSource::addToUsageTable Foam::holeToFace::usage_
+(
+    holeToFace::typeName,
+    "\n    Usage: holeToFace <faceSet> ((x0 y0 z0) (x1 y1 z1))\n\n"
+    "    Select faces disconnecting the individual regions"
+    " (each indicated by a locations).\n"
+);
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+//Foam::label Foam::holeToFace::globalCount
+//(
+//    const bitSet& isMasterFace,
+//    const bitSet& set
+//)
+//{
+//    if (set.size() != isMasterFace.size())
+//    {
+//        FatalErrorInFunction << "problem" << exit(FatalError);
+//    }
+//
+//    label n = 0;
+//    for (const label facei : set)
+//    {
+//        if (isMasterFace(facei))
+//        {
+//            n++;
+//        }
+//    }
+//    return returnReduce(n, sumOp<label>());
+//}
+
+
+//void Foam::holeToFace::checkFaceSync
+//(
+//    const string& setName,
+//    const bitSet& set
+//) const
+//{
+//    if (set.size() != mesh_.nFaces())
+//    {
+//        FatalErrorInFunction<< "problem" << exit(FatalError);
+//    }
+//    bitSet orSet(set);
+//    syncTools::syncFaceList(mesh_, orSet, orEqOp<unsigned int>());
+//    bitSet andSet(set);
+//    syncTools::syncFaceList(mesh_, andSet, andEqOp<unsigned int>());
+//
+//    forAll(orSet, facei)
+//    {
+//        if (orSet[facei] != andSet[facei] || orSet[facei] != set[facei])
+//        {
+//            FatalErrorInFunction<< "problem for set " << setName
+//                << "face:" << facei
+//                << " at:" << mesh_.faceCentres()[facei]
+//                << " patch:" << mesh_.boundaryMesh().whichPatch(facei)
+//                << " set:" << set[facei]
+//                << " orSet:" << orSet[facei]
+//                << " andSet:" << andSet[facei]
+//                << exit(FatalError);
+//        }
+//    }
+//}
+
+
+//void Foam::holeToFace::checkFaceSync
+//(
+//    const string& fldName,
+//    const labelList& fld
+//) const
+//{
+//    if (fld.size() != mesh_.nFaces())
+//    {
+//        FatalErrorInFunction<< "problem" << exit(FatalError);
+//    }
+//    labelList maxFld(fld);
+//    syncTools::syncFaceList(mesh_, maxFld, maxEqOp<label>());
+//    labelList minFld(fld);
+//    syncTools::syncFaceList(mesh_, minFld, minEqOp<label>());
+//
+//    forAll(maxFld, facei)
+//    {
+//        if (maxFld[facei] != minFld[facei] || maxFld[facei] != fld[facei])
+//        {
+//            FatalErrorInFunction<< "problem for field " << fldName
+//                << "face:" << facei
+//                << " at:" << mesh_.faceCentres()[facei]
+//                << " patch:" << mesh_.boundaryMesh().whichPatch(facei)
+//                << " fld:" << fld[facei]
+//                << " maxFld:" << maxFld[facei]
+//                << " minFld:" << minFld[facei]
+//                << exit(FatalError);
+//        }
+//    }
+//}
+
+
+//void Foam::holeToFace::checkFaceSync
+//(
+//    const string& fldName,
+//    const List<unsigned int>& fld
+//) const
+//{
+//    if (fld.size() != mesh_.nFaces())
+//    {
+//        FatalErrorInFunction<< "problem" << exit(FatalError);
+//    }
+//    List<unsigned int> orFld(fld);
+//    syncTools::syncFaceList(mesh_, orFld, bitOrEqOp<unsigned int>());
+//    List<unsigned int> andFld(fld);
+//    syncTools::syncFaceList(mesh_, andFld, bitAndEqOp<unsigned int>());
+//    forAll(orFld, facei)
+//    {
+//        if (orFld[facei] != andFld[facei] || orFld[facei] != fld[facei])
+//        {
+//            FatalErrorInFunction<< "problem for field " << fldName
+//                << "face:" << facei
+//                << " at:" << mesh_.faceCentres()[facei]
+//                << " patch:" << mesh_.boundaryMesh().whichPatch(facei)
+//                << " fld:" << fld[facei]
+//                << " orFld:" << orFld[facei]
+//                << " andFld:" << andFld[facei]
+//                << exit(FatalError);
+//        }
+//    }
+//}
+
+
+//void Foam::holeToFace::writeCellField
+//(
+//    const word& name,
+//    const labelList& labelFld
+//) const
+//{
+//    Pout<< "Writing field " << name << endl;
+//    if (labelFld.size() != mesh_.nCells())
+//    {
+//        FatalErrorInFunction << exit(FatalError);
+//    }
+//
+//    const fvMesh& fvm = dynamic_cast<const fvMesh&>(mesh_);
+//
+//    volScalarField fld
+//    (
+//        IOobject
+//        (
+//            name,
+//            fvm.time().timeName(),
+//            fvm,
+//            IOobject::NO_READ,
+//            IOobject::NO_WRITE,
+//            false
+//        ),
+//        fvm,
+//        dimensionedScalar("zero", dimless, scalar(0))
+//    );
+//    forAll(labelFld, i)
+//    {
+//        fld[i] = labelFld[i];
+//    }
+//    fld.correctBoundaryConditions();
+//    fld.write();
+//}
+
+
+//void Foam::holeToFace::writeFaceField
+//(
+//    const word& name,
+//    const labelList& labelFld
+//) const
+//{
+//    Pout<< "Writing field " << name << endl;
+//    if (labelFld.size() != mesh_.nFaces())
+//    {
+//        FatalErrorInFunction << exit(FatalError);
+//    }
+//
+//    const fvMesh& fvm = dynamic_cast<const fvMesh&>(mesh_);
+//
+//    surfaceScalarField fld
+//    (
+//        IOobject
+//        (
+//            name,
+//            fvm.time().timeName(),
+//            fvm,
+//            IOobject::NO_READ,
+//            IOobject::NO_WRITE,
+//            false
+//        ),
+//        fvm,
+//        dimensionedScalar("zero", dimless, scalar(0))
+//    );
+//    for (label i = 0; i < mesh_.nInternalFaces(); i++)
+//    {
+//        fld[i] = labelFld[i];
+//    }
+//    surfaceScalarField::Boundary& bfld = fld.boundaryFieldRef();
+//    forAll(bfld, patchi)
+//    {
+//        fvsPatchScalarField& pfld = bfld[patchi];
+//        forAll(pfld, i)
+//        {
+//            pfld[i] = labelFld[pfld.patch().start()+i];
+//        }
+//    }
+//    fld.write();
+//
+//    // Write as faceSet as well
+//    const bitSet isMasterFace(syncTools::getInternalOrMasterFaces(mesh_));
+//
+//    faceSet set(mesh_, name, 100);
+//    label nMasters = 0;
+//    forAll(labelFld, facei)
+//    {
+//        if (labelFld[facei] >= 0)
+//        {
+//            set.insert(facei);
+//            if (isMasterFace(facei))
+//            {
+//                nMasters++;
+//            }
+//        }
+//    }
+//    Pout<< "Writing " << returnReduce(nMasters, sumOp<label>())
+//        << " >= 0 faces to faceSet " << set.name() << endl;
+//    set.write();
+//}
+
+
+void Foam::holeToFace::writeFaces
+(
+    const word& name,
+    const bitSet& faceFld
+) const
+{
+    mkDir(mesh_.time().timePath());
+    OBJstream str(mesh_.time().timePath()/name);
+    Pout<< "Writing " << faceFld.count() << " faces to " << str.name() << endl;
+
+    for (const label facei : faceFld)
+    {
+        str.write(mesh_.faces()[facei], mesh_.points(), false);
+    }
+}
+
+
+void Foam::holeToFace::calculateDistance
+(
+    const labelList& seedFaces,
+    const bitSet& isBlockedCell,
+    const bitSet& isBlockedFace,
+    labelList& cellDist,
+    labelList& faceDist
+) const
+{
+    if (isBlockedCell.size() != mesh_.nCells())
+    {
+        FatalErrorInFunction << "Problem" << exit(FatalError);
+    }
+    if (isBlockedFace.size() != mesh_.nFaces())
+    {
+        FatalErrorInFunction << "Problem" << exit(FatalError);
+    }
+
+    //const bitSet isMasterFace(syncTools::getInternalOrMasterFaces(mesh_));
+
+    // Field on cells and faces.
+    List<topoDistanceData<label>> cellData(mesh_.nCells());
+    List<topoDistanceData<label>> faceData(mesh_.nFaces());
+
+    // Start of changes
+    List<topoDistanceData<label>> seedData
+    (
+        seedFaces.size(),
+        topoDistanceData<label>(0, 123)
+    );
+    //Pout<< "Seeded "
+    //    << globalCount(isMasterFace, bitSet(mesh_.nFaces(), seedFaces))
+    //    << " out of " << returnReduce(mesh_.nFaces(), sumOp<label>()) << endl;
+
+    // Make sure we don't walk through inactive cells
+    //Pout<< "blocking "
+    //    << returnReduce(isBlockedCell.count(), sumOp<label>())
+    //    << " cells" << endl;
+    for (const label celli : isBlockedCell)
+    {
+        cellData[celli] = topoDistanceData<label>(0, 0);
+    }
+    //Pout<< "blocking "
+    //    << globalCount(isMasterFace, isBlockedFace) << " faces" << endl;
+    for (const label facei : isBlockedFace)
+    {
+        faceData[facei] = topoDistanceData<label>(0, 0);
+    }
+
+    // Propagate information inwards
+    FaceCellWave<topoDistanceData<label>> deltaCalc
+    (
+        mesh_,
+        seedFaces,
+        seedData,
+        faceData,
+        cellData,
+        mesh_.globalData().nTotalCells()+1
+    );
+
+    // And extract
+    //bool haveWarned = false;
+    forAll(cellData, celli)
+    {
+        if (!isBlockedCell[celli])
+        {
+            if (!cellData[celli].valid(deltaCalc.data()))
+            {
+                //if (!haveWarned)
+                //{
+                //    WarningInFunction
+                //        << "Did not visit some cells, e.g. cell " << celli
+                //        << " at " << mesh_.cellCentres()[celli] << endl;
+                //    haveWarned = true;
+                //}
+            }
+            else
+            {
+                cellDist[celli] = cellData[celli].distance();
+            }
+        }
+    }
+
+    forAll(faceDist, facei)
+    {
+        if (!isBlockedFace[facei])
+        {
+            if (!faceData[facei].valid(deltaCalc.data()))
+            {
+                //if (!haveWarned)
+                //{
+                //    WarningInFunction
+                //        << "Did not visit some faces, e.g. face " << facei
+                //        << " at " << mesh_.faceCentres()[facei] << endl;
+                //    haveWarned = true;
+                //}
+            }
+            else
+            {
+                faceDist[facei] = faceData[facei].distance();
+            }
+        }
+    }
+}
+
+
+Foam::bitSet Foam::holeToFace::frontFaces
+(
+    const bitSet& isSurfaceFace,
+    const List<unsigned int>& locationFaces,
+    const bitSet& isHoleCell
+) const
+{
+    const labelList& faceOwner = mesh_.faceOwner();
+    const labelList& faceNeighbour = mesh_.faceNeighbour();
+    const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
+
+    bitSet isFrontFace(mesh_.nFaces());
+    for (label facei = 0; facei < mesh_.nInternalFaces(); facei++)
+    {
+        if (!isSurfaceFace[facei])
+        {
+            const label ownHole = isHoleCell[faceOwner[facei]];
+            const label neiHole = isHoleCell[faceNeighbour[facei]];
+
+            if (ownHole != neiHole)
+            {
+                unsigned int masks = locationFaces[facei];
+                if (masks == 0u)
+                {
+                    FatalErrorInFunction << "face:" << facei
+                        << " at:" << mesh_.faceCentres()[facei]
+                        << " not any front" << exit(FatalError);
+                }
+
+                // Count number of bits set
+                const label nSet = BitOps::bit_count(masks);
+
+                if (nSet == 1)
+                {
+                    isFrontFace.set(facei);
+                }
+            }
+        }
+    }
+
+    // Get neighbouring cell data
+    bitSet isHoleNeiCell(mesh_.nBoundaryFaces());
+    {
+        for (const polyPatch& pp : pbm)
+        {
+            label bFacei = pp.start()-mesh_.nInternalFaces();
+            const labelUList& faceCells = pp.faceCells();
+
+            for (const label celli : faceCells)
+            {
+                isHoleNeiCell[bFacei] = isHoleCell[celli];
+                ++bFacei;
+            }
+        }
+        syncTools::swapBoundaryFaceList(mesh_, isHoleNeiCell);
+    }
+
+
+    for (label facei = mesh_.nInternalFaces(); facei < mesh_.nFaces(); facei++)
+    {
+        if (!isSurfaceFace[facei])
+        {
+            const label ownHole = isHoleCell[faceOwner[facei]];
+            const label neiHole = isHoleNeiCell[facei-mesh_.nInternalFaces()];
+
+            if (ownHole != neiHole)
+            {
+                unsigned int masks = locationFaces[facei];
+                if (masks == 0u)
+                {
+                    FatalErrorInFunction << "face:" << facei
+                        << " at:" << mesh_.faceCentres()[facei]
+                        << " not any front" << exit(FatalError);
+                }
+
+                // Count number of bits set
+                const label nSet = BitOps::bit_count(masks);
+
+                if (nSet == 1)
+                {
+                    isFrontFace.set(facei);
+                }
+            }
+        }
+    }
+    return isFrontFace;
+}
+
+
+Foam::bitSet Foam::holeToFace::findClosure
+(
+    const bitSet& isSurfaceFace,                // intersected faces
+    const bitSet& isCandidateHoleCell,          // starting blockage
+    const labelListList& locationCells          // cells per zone
+) const
+{
+    const labelList& faceOwner = mesh_.faceOwner();
+    const labelList& faceNeighbour = mesh_.faceNeighbour();
+
+    if (zonePoints_.size() < 2)
+    {
+        FatalErrorInFunction << "single region only : "
+            << flatOutput(zonePoints_) << exit(FatalError);
+    }
+
+    if (zonePoints_.size() > 31)
+    {
+        FatalErrorInFunction << "only support < 32 locations in mesh."
+            << " Currently : " << flatOutput(zonePoints_) << exit(FatalError);
+    }
+
+
+    //const bitSet isMasterFace(syncTools::getInternalOrMasterFaces(mesh_));
+
+    bitSet isHoleCell(isCandidateHoleCell);
+    for (const labelList& zoneCells : locationCells)
+    {
+        for (const label celli : zoneCells)
+        {
+            if (celli != -1)
+            {
+                isHoleCell.unset(celli);
+            }
+        }
+    }
+
+    bitSet notHoleCell(isHoleCell);
+    notHoleCell.flip();
+
+    if (debug)
+    {
+        Pout<< "holeToFace::findClosure :"
+            << " locationCells:" << flatOutput(locationCells) << nl
+            << "holeToFace::findClosure :"
+            << " initial blocked faces:" << isSurfaceFace.count()
+            << " candidate closure cells:" << isHoleCell.count()
+            << endl;
+    }
+
+    // Distance to surface for every cell/face inside isHoleCell
+    labelList surfaceCellDist(mesh_.nCells(), -1);
+    labelList surfaceNeiCellDist(mesh_.nBoundaryFaces(), -1);
+    labelList surfaceFaceDist(mesh_.nFaces(), -1);
+    {
+        calculateDistance
+        (
+            isSurfaceFace.toc(),    // seed faces
+            notHoleCell,            // no need to walk through non-hole cells
+            bitSet(mesh_.nFaces()), // no blocked faces
+            surfaceCellDist,
+            surfaceFaceDist
+        );
+        syncTools::swapBoundaryCellList
+        (
+            mesh_,
+            surfaceCellDist,
+            surfaceNeiCellDist
+        );
+        //writeCellField("surfaceCellDistance0", surfaceCellDist);
+        //writeFaceField("surfaceFaceDistance0", surfaceFaceDist);
+        //checkFaceSync("surfaceFaceDistance0", surfaceFaceDist);
+    }
+
+    if (debug)
+    {
+        Pout<< "holeToFace::findClosure :"
+            << " calculated topological distance to initial blocked faces."
+            << " max distance:" << gMax(surfaceCellDist)
+            << endl;
+    }
+
+
+    // Find faces reachable from locationCells. If the locationCell is inside
+    // the blockage it will be only the faces of the cell. If it is outside
+    // it does a full walk to find the reachable faces on the outside of
+    // the blockage.
+    // Note: actual distance itself does not matter - only if they have been
+    // visited.
+    List<unsigned int> locationFaces(mesh_.nFaces(), 0u);
+    forAll(locationCells, zonei)
+    {
+        labelList cellDist(mesh_.nCells(), -1);
+        labelList faceDist(mesh_.nFaces(), -1);
+
+        labelList seedFaces;
+
+        const labelList& zoneCells = locationCells[zonei];
+        for (const label celli : zoneCells)
+        {
+            if (celli != -1)
+            {
+                cellDist[celli] = 0;
+                const cell& cFaces = mesh_.cells()[celli];
+                seedFaces.append(cFaces);
+                UIndirectList<label>(faceDist, cFaces) = 0;
+            }
+        }
+
+        // Extra par sync. Is this needed?
+        {
+            bitSet isSeedFace(mesh_.nFaces(), seedFaces);
+            syncTools::syncFaceList
+            (
+                mesh_,
+                isSeedFace,
+                orEqOp<unsigned int>()
+            );
+            seedFaces = isSeedFace.toc();
+        }
+
+
+        calculateDistance
+        (
+            seedFaces,      // seed faces
+            isHoleCell,     // do not walk through blocking cells
+            isSurfaceFace,  // do not walk through surface
+            cellDist,
+            faceDist
+        );
+        //writeCellField("cellDistance" + Foam::name(zonei), cellDist);
+        //writeFaceField("faceDistance" + Foam::name(zonei), faceDist);
+        //checkFaceSync("faceDistance", faceDist);
+
+        // Add all reached faces
+        const unsigned int mask = (1u << zonei);
+        forAll(faceDist, facei)
+        {
+            if (faceDist[facei] >= 0)
+            {
+                locationFaces[facei] |= mask;
+            }
+        }
+    }
+
+    if (debug)
+    {
+        writeFaces("isSurfaceFace.obj", isSurfaceFace);
+    }
+    //if (debug)
+    //{
+    //    bitSet isMultiBitFace(mesh_.nFaces());
+    //    forAll(locationFaces, facei)
+    //    {
+    //        const unsigned int bits = locationFaces[facei];
+    //        const label nSet = BitOps::bit_count(bits);
+    //        if (nSet > 1)
+    //        {
+    //            isMultiBitFace.set(facei);
+    //        }
+    //    }
+    //    writeFaces("isMultiBitFace.obj", isMultiBitFace);
+    //}
+
+
+    //// Check that there are some faces that connect to more than one zone
+    //- NOT CORRECT!!! At this point the walking has only done the distance
+    //                 outside the initial set of blocked faces. We'd have to
+    //                 walk through all faces before we can determine.
+    //bool haveLeak = false;
+    //forAll(locationFaces, facei)
+    //{
+    //    if (!isSurfaceFace[facei])
+    //    {
+    //        const unsigned int bits = locationFaces[facei];
+    //        const label nSet = BitOps::bit_count(bits);
+    //        if (nSet > 1)
+    //        {
+    //            haveLeak = true;
+    //
+    //            if (debug)
+    //            {
+    //                // Collect points
+    //                DynamicList<pointField> connected;
+    //                forAll(zonePoints_, zonei)
+    //                {
+    //                    if (bits & (1u << zonei))
+    //                    {
+    //                        connected.append(zonePoints_[zonei]);
+    //                    }
+    //                }
+    //                Pout<< "holeToFace::findClosure :"
+    //                    << " found initial leak at face "
+    //                    << mesh_.faceCentres()[facei]
+    //                    << " between zones " << flatOutput(connected)
+    //                    << endl;
+    //            }
+    //            break;
+    //        }
+    //    }
+    //}
+    //reduce(haveLeak, orOp<bool>());
+    //
+    //if (!haveLeak)
+    //{
+    //    if (debug)
+    //    {
+    //        Pout<< "holeToFace::findClosure :"
+    //            << " did not find leak between zones "
+    //            << flatOutput(zonePoints_) << endl;
+    //    }
+    //    return bitSet(mesh_.nFaces());
+    //}
+
+
+    // Front (on outside of hole cell but not connecting multiple locations
+    bitSet isFrontFace(frontFaces(isSurfaceFace, locationFaces, isHoleCell));
+
+
+    // Start off eroding the cells furthest away from the surface
+    label surfaceDist = gMax(surfaceCellDist);
+
+    // Work storage
+    //List<unsigned int> newLocationFaces(mesh_.nFaces());
+    //bitSet newHoleCell(mesh_.nCells());
+
+    while (surfaceDist >= 0)
+    {
+        // Erode cells with >= surfaceDist:
+        // - unmark cell as blockage (isHoleCell)
+        // - mark faces of cell as visible from inside/outside
+
+        //newLocationFaces = locationFaces;
+        //newHoleCell = isHoleCell;
+
+        label nChanged = 0;
+        for (const label facei : isFrontFace)
+        {
+            const label own = faceOwner[facei];
+            if (isHoleCell[own])
+            {
+                const label ownDist = surfaceCellDist[own];
+                if (ownDist >= surfaceDist)
+                {
+                    //newHoleCell.unset(own);
+                    isHoleCell.unset(own);
+                    nChanged++;
+
+                    const cell& cFaces = mesh_.cells()[own];
+                    // Set corresponding bits on faces
+                    const unsigned int mask = locationFaces[facei];
+                    for (const label fi : cFaces)
+                    {
+                        //newLocationFaces[fi] |= mask;
+                        locationFaces[fi] |= mask;
+                    }
+                }
+            }
+            else if (mesh_.isInternalFace(facei))
+            {
+                const label nei = faceNeighbour[facei];
+                const label neiDist = surfaceCellDist[nei];
+
+                if (isHoleCell[nei] && neiDist >= surfaceDist)
+                {
+                    //newHoleCell[nei] = false;
+                    isHoleCell[nei] = false;
+                    nChanged++;
+
+                    const cell& cFaces = mesh_.cells()[nei];
+                    // Set corresponding bits on faces
+                    const unsigned int mask = locationFaces[facei];
+                    for (const label fi : cFaces)
+                    {
+                        //newLocationFaces[fi] |= mask;
+                        locationFaces[fi] |= mask;
+                    }
+                }
+            }
+        }
+
+        reduce(nChanged, sumOp<label>());
+
+
+        if (debug)
+        {
+            Pout<< "holeToFace::findClosure :"
+                << " surfaceDist:" << surfaceDist
+                << " front:" << isFrontFace.count()
+                << " nChangedCells:" << nChanged
+                << endl;
+        }
+
+
+        if (nChanged == 0)
+        {
+            // Nothing eroded at this level. Erode cells nearer to surface.
+            --surfaceDist;
+        }
+        else
+        {
+            //locationFaces = newLocationFaces;
+            //isHoleCell = newHoleCell;
+
+            // Sync locationFaces
+            syncTools::syncFaceList
+            (
+                mesh_,
+                locationFaces,
+                bitOrEqOp<unsigned int>()
+            );
+
+            // Calculate new front. Never include faces that are both visible
+            // from outside and inside
+            isFrontFace = frontFaces(isSurfaceFace, locationFaces, isHoleCell);
+        }
+    }
+
+
+    // Debug: dump all the end fronts
+    //{
+    //    forAll(locationCells, zonei)
+    //    {
+    //        const unsigned int mask = (1u << zonei);
+    //
+    //        bitSet isZoneFace(mesh_.nFaces());
+    //        forAll(locationFaces, facei)
+    //        {
+    //            if (locationFaces[facei] & mask)
+    //            {
+    //                isZoneFace.set(facei);
+    //            }
+    //        }
+    //        writeFaces("isZoneFace"+Foam::name(zonei)+".obj", isZoneFace);
+    //    }
+    //}
+
+
+
+    // Find faces that are connected to more than one location
+    bitSet isCommonFace(mesh_.nFaces());
+    forAll(locationFaces, facei)
+    {
+        unsigned int masks = locationFaces[facei];
+        if (masks != 0u)
+        {
+            // Count number of bits set
+            const label nSet = BitOps::bit_count(masks);
+            if (nSet >= 2)
+            {
+                isCommonFace.set(facei);
+            }
+        }
+    }
+
+    // Remove faces that are on the surface
+    for (const label facei : isCommonFace)
+    {
+        if (surfaceFaceDist[facei] == 0)
+        {
+            isCommonFace.unset(facei);
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "holeToFace::findClosure :"
+            << " closure faces:" << isCommonFace.count() << endl;
+    }
+
+    return isCommonFace;
+}
+
+
+Foam::bitSet Foam::holeToFace::erodeSet
+(
+    const bitSet& isSurfaceFace,
+    const bitSet& isSetFace
+) const
+{
+    // Detect cells with lots of faces in the set. WIP. Not parallel consistent.
+
+    const labelList& faceOwner = mesh_.faceOwner();
+    const labelList& faceNeighbour = mesh_.faceNeighbour();
+
+    bitSet isSetCell(mesh_.nCells());
+    for (const label facei : isSetFace)
+    {
+        isSetCell.set(faceOwner[facei]);
+        if (mesh_.isInternalFace(facei))
+        {
+            isSetCell.set(faceNeighbour[facei]);
+        }
+    }
+
+    // Count number of faces per cell. Decide if surface would improve by
+    // moving set
+    bitSet erodedSet(isSetFace);
+    for (const label celli : isSetCell)
+    {
+        const cell& cFaces = mesh_.cells()[celli];
+
+        label nBlockedFaces = 0;
+        label nSurfaceFaces = 0;
+        for (const label facei : cFaces)
+        {
+            if (erodedSet[facei])
+            {
+                nBlockedFaces++;
+            }
+            else if (isSurfaceFace[facei])
+            {
+                nSurfaceFaces++;
+            }
+        }
+
+        if ((nSurfaceFaces + nBlockedFaces) == cFaces.size())
+        {
+            // Single cell already disconnected by surface intersections
+            for (const label facei : cFaces)
+            {
+                erodedSet.unset(facei);
+            }
+        }
+    }
+    syncTools::syncFaceList
+    (
+        mesh_,
+        erodedSet,
+        andEqOp<unsigned int>()
+    );
+    //checkFaceSync("erodedSet", erodedSet);
+
+    for (const label celli : isSetCell)
+    {
+        const cell& cFaces = mesh_.cells()[celli];
+
+        label nBlockedFaces = 0;
+        for (const label facei : cFaces)
+        {
+            if (erodedSet[facei])
+            {
+                nBlockedFaces++;
+            }
+        }
+        if (nBlockedFaces >= cFaces.size()-2)
+        {
+            for (const label facei : cFaces)
+            {
+                erodedSet.flip(facei);
+            }
+        }
+    }
+    syncTools::syncFaceList
+    (
+        mesh_,
+        erodedSet,
+        andEqOp<unsigned int>()
+    );
+
+    if (debug)
+    {
+        Pout<< "holeToFace::erodeSet :"
+            << " starting set:" << isSetFace.count()
+            << " eroded set:" << erodedSet.count() << endl;
+    }
+
+    //checkFaceSync("erodedSet", erodedSet);
+    return erodedSet;
+}
+
+
+void Foam::holeToFace::combine
+(
+    topoSet& set,
+    const bitSet& isSurfaceFace,
+    const bitSet& isHoleCell,
+    const bool add
+) const
+{
+    labelListList locationCells(zonePoints_.size());
+    forAll(zonePoints_, zonei)
+    {
+        const pointField& zoneLocations = zonePoints_[zonei];
+        labelList& zoneCells = locationCells[zonei];
+        zoneCells.setSize(zoneLocations.size());
+        forAll(zoneLocations, i)
+        {
+            const label celli = mesh_.findCell(zoneLocations[i]);
+            zoneCells[i] = celli;
+
+            // Check that cell has at least one unblocked face so front can
+            // 'escape'.
+            if (celli != -1)
+            {
+                const cell& cFaces = mesh_.cells()[celli];
+                bool hasUnblocked = false;
+                for (const label facei : cFaces)
+                {
+                    if (!isSurfaceFace[facei])
+                    {
+                        hasUnblocked = true;
+                        break;
+                    }
+                }
+
+                if (!hasUnblocked)
+                {
+                    FatalErrorInFunction
+                        << "problem : location:" << zoneLocations[i]
+                        << " in zone:" << zonei
+                        << " is found in cell at:" << celli
+                        << mesh_.cellCentres()[celli]
+                        << " which is completely surrounded by blocked faces"
+                        << exit(FatalError);
+                }
+            }
+        }
+    }
+
+    bitSet isClosingFace
+    (
+        findClosure
+        (
+            isSurfaceFace,      // intersected faces
+            isHoleCell,         // starting blockage
+            locationCells       // cells for zonePoints_
+        )
+    );
+
+    if (erode_)
+    {
+        isClosingFace = erodeSet(isSurfaceFace, isClosingFace);
+    }
+
+    if (debug)
+    {
+        writeFaces("isClosingFace.obj", isClosingFace);
+        //checkFaceSync("isClosingFace", isCommonFace);
+    }
+
+    for (const label facei : isClosingFace)
+    {
+        addOrDelete(set, facei, add);
+    }
+}
+
+
+Foam::List<Foam::pointField> Foam::holeToFace::expand(const pointField& pts)
+{
+    List<pointField> allPts(pts.size());
+    forAll(pts, i)
+    {
+        pointField& onePt = allPts[i];
+        onePt.setSize(1, pts[i]);
+    }
+    return allPts;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::holeToFace::holeToFace
+(
+    const polyMesh& mesh,
+    const List<pointField>& zonePoints,
+    const wordList& blockedFaceNames,
+    const wordList& blockedCellNames,
+    const bool erode
+)
+:
+    topoSetFaceSource(mesh),
+    zonePoints_(zonePoints),
+    blockedFaceNames_(blockedFaceNames),
+    blockedCellNames_(blockedCellNames),
+    erode_(erode)
+{}
+
+
+Foam::holeToFace::holeToFace
+(
+    const polyMesh& mesh,
+    const dictionary& dict
+)
+:
+    topoSetFaceSource(mesh),
+    zonePoints_(dict.get<List<pointField>>("points")),
+    blockedFaceNames_(),
+    blockedCellNames_(),
+    erode_(dict.getOrDefault<bool>("erode", false))
+{
+    // Look for 'sets' or 'set'
+    if (!dict.readIfPresent("faceSets", blockedFaceNames_))
+    {
+        blockedFaceNames_.resize(1);
+        if (!dict.readEntry("faceSet", blockedFaceNames_.first()))
+        {
+            blockedFaceNames_.clear();
+        }
+    }
+    if (!dict.readIfPresent("cellSets", blockedCellNames_))
+    {
+        blockedCellNames_.resize(1);
+        if (!dict.readEntry("cellSet", blockedCellNames_.first()))
+        {
+            blockedCellNames_.clear();
+        }
+    }
+}
+
+
+Foam::holeToFace::holeToFace
+(
+    const polyMesh& mesh,
+    Istream& is
+)
+:
+    topoSetFaceSource(mesh),
+    zonePoints_(expand(pointField(is))),
+    blockedFaceNames_(one(), word(checkIs(is))),
+    blockedCellNames_(),
+    erode_(false)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::holeToFace::applyToSet
+(
+    const topoSetSource::setAction action,
+    topoSet& set
+) const
+{
+    // Set of additional blocked (internal or coupled) faces
+    bitSet isBlockedFace(mesh_.nFaces());
+    for (const word& setName : blockedFaceNames_)
+    {
+        const faceSet loadedSet(mesh_, setName);
+        isBlockedFace.set(loadedSet.toc());
+    }
+
+    // Optional initial blocked cells
+    bitSet isCandidateCell(mesh_.nCells());
+    if (blockedFaceNames_.size())
+    {
+        for (const word& setName : blockedCellNames_)
+        {
+            const cellSet loadedSet(mesh_, setName);
+            isCandidateCell.set(loadedSet.toc());
+        }
+    }
+    else
+    {
+        isCandidateCell = true;
+    }
+
+    if (action == topoSetSource::ADD || action == topoSetSource::NEW)
+    {
+        if (verbose_)
+        {
+            Info<< "    Adding all faces to disconnect regions "
+                << flatOutput(zonePoints_) << " ..." << endl;
+        }
+
+        combine(set, isBlockedFace, isCandidateCell, true);
+    }
+    else if (action == topoSetSource::SUBTRACT)
+    {
+        if (verbose_)
+        {
+            Info<< "    Removing all faces to disconnect regions "
+                << flatOutput(zonePoints_) << " ..." << endl;
+        }
+
+        combine(set, isBlockedFace, isCandidateCell, false);
+    }
+}
+
+
+Foam::autoPtr<Foam::mapDistribute> Foam::holeToFace::calcClosure
+(
+    const polyMesh& mesh,
+    const List<pointField>& zonePoints,
+    const labelList& blockedFaces,
+    const globalIndex& globalBlockedFaces,
+    const bool erode,
+
+    labelList& closureFaces,        // local faces to close gap
+    labelList& closureToBlocked
+)
+{
+    if (blockedFaces.size() != globalBlockedFaces.localSize())
+    {
+        FatalErrorInFunction << "problem : blockedFaces:" << blockedFaces.size()
+            << " globalBlockedFaces:" << globalBlockedFaces.localSize()
+            << exit(FatalError);
+    }
+
+
+    // Calculate faces needed to close hole (closureFaces)
+    {
+        const holeToFace faceSetSource
+        (
+            mesh,
+            zonePoints,
+            wordList(0),
+            wordList(0),
+            erode           //false
+        );
+        faceSet closureFaceSet(mesh, "calcClosure", 256);
+
+        const bitSet isBlockedFace(mesh.nFaces(), blockedFaces);
+        const bitSet isActiveCell(mesh.nCells(), true);
+
+        faceSetSource.combine
+        (
+            closureFaceSet,
+            isBlockedFace,
+            isActiveCell,
+            true
+        );
+
+        closureFaces = closureFaceSet.sortedToc();
+    }
+
+
+    if (returnReduce(closureFaces.size(), sumOp<label>()) == 0)
+    {
+        closureToBlocked.clear();
+        return autoPtr<mapDistribute>(nullptr);
+    }
+
+
+    //- Seed edges of closureFaces patch with (global) index of blockedFace
+
+    const indirectPrimitivePatch pp
+    (
+        IndirectList<face>(mesh.faces(), closureFaces),
+        mesh.points()
+    );
+    const edgeList& edges = pp.edges();
+    const labelList& mp = pp.meshPoints();
+    const label nBndEdges = pp.nEdges() - pp.nInternalEdges();
+
+    // For all faces in blockedFaces mark the edge with a face. No special
+    // handling for multiple faces sharing the edge - first one wins
+    EdgeMap<label> edgeMap(pp.nEdges());
+    forAll(blockedFaces, i)
+    {
+        const label globalBlockedi = globalBlockedFaces.toGlobal(i);
+        const label facei = blockedFaces[i];
+        const face& f = mesh.faces()[facei];
+        forAll(f, fp)
+        {
+            label nextFp = f.fcIndex(fp);
+            edgeMap.insert(edge(f[fp], f[nextFp]), globalBlockedi);
+        }
+    }
+    syncTools::syncEdgeMap(mesh, edgeMap, maxEqOp<label>());
+
+
+
+    // Seed
+    DynamicList<label> initialEdges(2*nBndEdges);
+    DynamicList<edgeTopoDistanceData<label, indirectPrimitivePatch>>
+        initialEdgesInfo(2*nBndEdges);
+    forAll(edges, edgei)
+    {
+        const edge& e = edges[edgei];
+        const edge meshE = edge(mp[e[0]], mp[e[1]]);
+
+        auto iter = edgeMap.cfind(meshE);
+        if (iter.found())
+        {
+            // Found edge on patch connected to blocked face. Seed with the
+            // (global) index of that blocked face
+
+            initialEdges.append(edgei);
+            initialEdgesInfo.append
+            (
+                edgeTopoDistanceData<label, indirectPrimitivePatch>
+                (
+                    0,          // distance
+                    iter()      // globalBlockedi
+                )
+            );
+        }
+    }
+
+    // Data on all edges and faces
+    List<edgeTopoDistanceData<label, indirectPrimitivePatch>> allEdgeInfo
+    (
+        pp.nEdges()
+    );
+    List<edgeTopoDistanceData<label, indirectPrimitivePatch>> allFaceInfo
+    (
+        pp.size()
+    );
+
+    // Walk
+    PatchEdgeFaceWave
+    <
+        indirectPrimitivePatch,
+        edgeTopoDistanceData<label, indirectPrimitivePatch>
+    > calc
+    (
+        mesh,
+        pp,
+        initialEdges,
+        initialEdgesInfo,
+        allEdgeInfo,
+        allFaceInfo,
+        returnReduce(pp.nEdges(), sumOp<label>())+1
+    );
+
+
+    // Per closure face the seed face
+    closureToBlocked.setSize(pp.size());
+    closureToBlocked = -1;
+    forAll(allFaceInfo, facei)
+    {
+        if (allFaceInfo[facei].valid(calc.data()))
+        {
+            closureToBlocked[facei] = allFaceInfo[facei].data();
+        }
+    }
+    // Above wave only guarantees unique data on coupled edges, not on
+    // coupled faces (?) so explicitly sync faces
+    {
+        labelList syncFld(mesh.nFaces(), -1);
+        UIndirectList<label>(syncFld, pp.addressing()) = closureToBlocked;
+        syncTools::syncFaceList(mesh, syncFld, maxEqOp<label>());
+        closureToBlocked = UIndirectList<label>(syncFld, pp.addressing());
+    }
+
+    List<Map<label>> compactMap;
+    return autoPtr<mapDistribute>::New
+    (
+        globalBlockedFaces,
+        closureToBlocked,
+        compactMap
+    );
+}
+
+
+// ************************************************************************* //
diff --git a/src/meshTools/topoSet/faceSources/holeToFace/holeToFace.H b/src/meshTools/topoSet/faceSources/holeToFace/holeToFace.H
new file mode 100644
index 00000000000..98ef41b60fa
--- /dev/null
+++ b/src/meshTools/topoSet/faceSources/holeToFace/holeToFace.H
@@ -0,0 +1,220 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::holeToFace
+
+Description
+    A topoSetFaceSource to select a set of faces that closes a hole i.e.
+    disconnects zones (specified by locations) from one another.
+
+    Algorithm roughly according to
+    "A 3D-Hole Closing Algorithm", Zouina Aktouf et al
+
+    \heading Dictionary parameters
+    \table
+        Property    | Description                       | Required  | Default
+        points      | Per zone the list of points       | yes   |
+        faceSet     | Optional blocked faces            | no    | <empty>
+        cellSet     | Optional subset of cells to operate in | no    | <empty>
+        erode       | Perform some cleanup on set       | no    | no
+    \endtable
+
+    Limited to max 31 zones.
+
+SourceFiles
+    holeToFace.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef holeToFace_H
+#define holeToFace_H
+
+#include "topoSetFaceSource.H"
+#include "bitSet.H"
+#include "mapDistribute.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class holeToFace Declaration
+\*---------------------------------------------------------------------------*/
+
+class holeToFace
+:
+    public topoSetFaceSource
+{
+    // Private data
+
+        //- Add usage string
+        static addToUsageTable usage_;
+
+        //- Per 'zone' the set of points
+        List<pointField> zonePoints_;
+
+        //- Names of faceSets specifying blockage
+        wordList blockedFaceNames_;
+
+        //- Names of cellSets specifying blockage
+        wordList blockedCellNames_;
+
+        //- Erode set
+        bool erode_;
+
+
+    // Private Member Functions
+
+        // Helper functions
+
+            //- Expand pointField into list of pointFields
+            static List<pointField> expand(const pointField& pts);
+
+            //- Write .obj file with selected faces
+            void writeFaces
+            (
+                const word& name,
+                const bitSet& faceFld
+            ) const;
+
+
+        //- Calculate topological distance from seedFaces. Do not cross
+        //  blocked cells/faces
+        void calculateDistance
+        (
+            const labelList& seedFaces,
+            const bitSet& isBlockedCell,
+            const bitSet& isBlockedFace,
+            labelList& cellDist,
+            labelList& faceDist
+        ) const;
+
+        //- Calculate faces on outside of hole cells. Ignore blocked faces
+        //  (isSurfaceFace) and faces reachable from more than one location
+        bitSet frontFaces
+        (
+            const bitSet& isSurfaceFace,
+            const List<unsigned int>& locationFaces,
+            const bitSet& isHoleCell
+        ) const;
+
+        //- Overall driver to find minimum set of faces to close the path
+        //  between zones
+        bitSet findClosure
+        (
+            const bitSet& isSurfaceFace,                // intersected faces
+            const bitSet& isCandidateHoleCell,          // starting blockage
+            const labelListList& locationCells          // cells per zone
+        ) const;
+
+        //- WIP. Remove excess faces. Not parallel consistent.
+        bitSet erodeSet
+        (
+            const bitSet& isSurfaceFace,
+            const bitSet& isSetFace
+        ) const;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("holeToFace");
+
+    // Constructors
+
+        //- Construct from components
+        holeToFace
+        (
+            const polyMesh& mesh,
+            const List<pointField>& zonePoints,
+            const wordList& blockedFaceNames,
+            const wordList& blockedCellNames,
+            const bool erode
+        );
+
+        //- Construct from dictionary
+        holeToFace(const polyMesh& mesh, const dictionary& dict);
+
+        //- Construct from Istream
+        holeToFace(const polyMesh& mesh, Istream& is);
+
+
+    //- Destructor
+    virtual ~holeToFace() = default;
+
+
+    // Member Functions
+
+        virtual void applyToSet
+        (
+            const topoSetSource::setAction action,
+            topoSet&
+        ) const;
+
+        //- Optional direct use to generate a faceSet
+        void combine
+        (
+            topoSet& set,
+            const bitSet& isBlockedFace,
+            const bitSet& isActiveCell,
+            const bool add
+        ) const;
+
+        //- Optional direct use to generate the set of faces and the method to
+        //  get data from nearby blocked faces. Gets provided with the
+        //  - set of points per 'zone'
+        //  - set of blocked faces
+        //  - global numbering for these blocked faces
+        //  Returns
+        //  - the set of faces to disconnect zones from one
+        //    another (closureFaces)
+        //  - a nullptr or the maps from closureFaces to nearest blocked face
+        //    (mapDistribute and closureToBlocked). Note: closureToBlocked
+        //     (index into the map) can contain -1 if no nearby valid blocked
+        //    face could be found (from an edge-face-edge walk)
+        static autoPtr<mapDistribute> calcClosure
+        (
+            const polyMesh& mesh,
+            const List<pointField>& zonePoints,
+            const labelList& blockedFaces,
+            const globalIndex& globalBlockedFaces,
+            const bool erode,
+            labelList& closureFaces,
+            labelList& closureToBlocked
+        );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/Allrun.pre b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/Allrun.pre
new file mode 100755
index 00000000000..33c0095d655
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/Allrun.pre
@@ -0,0 +1,25 @@
+#!/bin/sh
+cd "${0%/*}" || exit                                # Run from this directory
+. ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions        # Tutorial run functions
+#------------------------------------------------------------------------------
+
+touch case.foam
+
+runApplication blockMesh
+
+# Generate f0Zone faceZone
+runApplication topoSet
+
+runApplication extrudeMesh
+
+runApplication checkMesh
+
+#- Extrude some internal faces into owner
+
+runApplication -s flip topoSet -dict system/topoSetDict.flip
+
+runApplication -s flip extrudeMesh -dict system/extrudeMeshDict.flip
+
+runApplication -s flip checkMesh
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/README b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/README
new file mode 100644
index 00000000000..f7b8c883296
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/README
@@ -0,0 +1,9 @@
+Unit testcase demonstrating extruding from internal faces
+(using a faceZone instead of a faceSet)
+
+- pass 1 :  extrude on bottom left cell both internal faces and a boundary face.
+            These faces will all be extruded away from cell 0 (i.e. no flip in
+            the faceZone)
+
+- pass 2 :  extrude on top right cell both internal and boundary faces.
+            The internal faces will all have flip in the faceZone)
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/constant/transportProperties b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/constant/transportProperties
new file mode 100644
index 00000000000..e0356a427a1
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/constant/transportProperties
@@ -0,0 +1,21 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "constant";
+    object      transportProperties;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+nu              0.01;
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/blockMeshDict b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/blockMeshDict
new file mode 100644
index 00000000000..36f1425873d
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/blockMeshDict
@@ -0,0 +1,71 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      blockMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+scale   1;
+
+vertices
+(
+    (0 0 0)
+    (2 0 0)
+    (2 2 0)
+    (0 2 0)
+    (0 0 1)
+    (2 0 1)
+    (2 2 1)
+    (0 2 1)
+);
+
+blocks
+(
+    hex (0 1 2 3 4 5 6 7) (2 2 1) simpleGrading (1 1 1)
+);
+
+edges
+(
+);
+
+boundary
+(
+    movingWall
+    {
+        type wall;
+        faces
+        (
+            (3 7 6 2)
+        );
+    }
+    fixedWalls
+    {
+        type wall;
+        faces
+        (
+            (0 4 7 3)
+            (2 6 5 1)
+            (1 5 4 0)
+        );
+    }
+    frontAndBack
+    {
+        type wall;
+        faces
+        (
+            (0 3 2 1)
+            (4 5 6 7)
+        );
+    }
+);
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/controlDict b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/controlDict
new file mode 100644
index 00000000000..d8b0b77182e
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/controlDict
@@ -0,0 +1,49 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "system";
+    object      controlDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+application     icoFoam;
+
+startFrom       startTime;
+
+startTime       0;
+
+stopAt          endTime;
+
+endTime         0.5;
+
+deltaT          0.005;
+
+writeControl    timeStep;
+
+writeInterval   20;
+
+purgeWrite      0;
+
+writeFormat     ascii;
+
+writePrecision  6;
+
+writeCompression off;
+
+timeFormat      general;
+
+timePrecision   6;
+
+runTimeModifiable true;
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/decomposeParDict b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/decomposeParDict
new file mode 100644
index 00000000000..e2cd8b7b451
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/decomposeParDict
@@ -0,0 +1,27 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      decomposeParDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+numberOfSubdomains  9;
+
+method  hierarchical;
+
+coeffs
+{
+    n   (3 3 1);
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict
new file mode 100644
index 00000000000..d928cc57886
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict
@@ -0,0 +1,30 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      extrudeMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+constructFrom mesh;
+sourceCase    "<case>";
+
+//sourcePatches ();
+sourceFaceZones (f0Zone);
+exposedPatchName front;
+
+extrudeModel  linearNormal;
+thickness     0.05;
+
+flipNormals false;
+mergeFaces false;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict.flip b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict.flip
new file mode 100644
index 00000000000..d928cc57886
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/extrudeMeshDict.flip
@@ -0,0 +1,30 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      extrudeMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+constructFrom mesh;
+sourceCase    "<case>";
+
+//sourcePatches ();
+sourceFaceZones (f0Zone);
+exposedPatchName front;
+
+extrudeModel  linearNormal;
+thickness     0.05;
+
+flipNormals false;
+mergeFaces false;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSchemes b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSchemes
new file mode 100644
index 00000000000..fe0555ce86c
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSchemes
@@ -0,0 +1,51 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "system";
+    object      fvSchemes;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+ddtSchemes
+{
+    default         Euler;
+}
+
+gradSchemes
+{
+    default         Gauss linear;
+    grad(p)         Gauss linear;
+}
+
+divSchemes
+{
+    default         none;
+    div(phi,U)      Gauss linear;
+}
+
+laplacianSchemes
+{
+    default         Gauss linear orthogonal;
+}
+
+interpolationSchemes
+{
+    default         linear;
+}
+
+snGradSchemes
+{
+    default         orthogonal;
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSolution b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSolution
new file mode 100644
index 00000000000..a3b55dd1039
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/fvSolution
@@ -0,0 +1,52 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "system";
+    object      fvSolution;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+solvers
+{
+    p
+    {
+        solver          PCG;
+        preconditioner  DIC;
+        tolerance       1e-06;
+        relTol          0.05;
+    }
+
+    pFinal
+    {
+        $p;
+        relTol          0;
+    }
+
+    U
+    {
+        solver          smoothSolver;
+        smoother        symGaussSeidel;
+        tolerance       1e-05;
+        relTol          0;
+    }
+}
+
+PISO
+{
+    nCorrectors     2;
+    nNonOrthogonalCorrectors 0;
+    pRefCell        0;
+    pRefValue       0;
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict
new file mode 100644
index 00000000000..568885f770a
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict
@@ -0,0 +1,82 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      topoSetDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+actions
+(
+    {
+        name    f0;
+        type    faceSet;
+        action  new;
+        source  boxToFace;
+        sourceInfo
+        {
+            box (-100 -100 -100) (1.01 1.01 1.01);
+        }
+    }
+    {
+        name    f0;
+        type    faceSet;
+        action  delete;
+        source  boundaryToFace;
+        sourceInfo
+        {
+        }
+    }
+
+    ////- Remove internal faces; do boundary faces only
+    //{
+    //    name    f0;
+    //    type    faceSet;
+    //    action  clear;
+    //}
+
+    //- Add single external face
+    {
+        name    f0;
+        type    faceSet;
+        action  add;
+        source  boxToFace;
+        sourceInfo
+        {
+            // Minz face
+            box (-100 -100 -100) (1.01 1.01 0.01);
+        }
+    }
+
+    {
+        name    c0;
+        type    cellSet;
+        action  new;
+        source  nearestToCell;
+        sourceInfo
+        {
+            points  ((0.5 0.5 0.5));
+        }
+    }
+    {
+        name    f0Zone;
+        type    faceZoneSet;
+        action  new;
+        source  setsToFaceZone;
+        sourceInfo
+        {
+            faceSet f0;
+            cellSet c0;
+        }
+    }
+);
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict.flip b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict.flip
new file mode 100644
index 00000000000..b0f1cda8fb0
--- /dev/null
+++ b/tutorials/mesh/extrudeMesh/faceZoneExtrusion/system/topoSetDict.flip
@@ -0,0 +1,50 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      topoSetDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+actions
+(
+    {
+        name    c0;
+        type    cellSet;
+        action  new;
+        source  nearestToCell;
+        sourceInfo
+        {
+            points  ((1.5 1.5 0.5));
+        }
+    }
+    {
+        name    f0;
+        type    faceSet;
+        action  new;
+        source  cellToFace;
+        set     c0;
+        option  all;
+    }
+    {
+        name    f0Zone;
+        type    faceZoneSet;
+        action  new;
+        source  setsToFaceZone;
+        sourceInfo
+        {
+            faceSet f0;
+            cellSet c0;
+        }
+    }
+);
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/Allrun b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/Allrun
index f9023e1ce6c..9df5c86d892 100755
--- a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/Allrun
+++ b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/Allrun
@@ -3,6 +3,8 @@ cd "${0%/*}" || exit                                # Run from this directory
 . ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions        # Tutorial run functions
 #------------------------------------------------------------------------------
 
+paraFoam -vtk -touch
+
 restore0Dir
 
 runApplication blockMesh
diff --git a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/decomposeParDict b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/decomposeParDict
index 88ca418e76f..1d2799ae759 100644
--- a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/decomposeParDict
+++ b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/decomposeParDict
@@ -14,25 +14,9 @@ FoamFile
 }
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-numberOfSubdomains  2;
-
-//- Use the volScalarField named here as a weight for each cell in the
-//  decomposition.  For example, use a particle population field to decompose
-//  for a balanced number of particles in a lagrangian simulation.
-// weightField dsmcRhoNMean;
-
-method          scotch;
-
-constraints
-{
-    //- Keep owner and neighbour of baffles on same processor
-    //  (i.e. keep it detectable as a baffle).
-    //  Baffles are two boundary face sharing the same points.
-    baffles
-    {
-        type    preserveBaffles;
-    }
-}
+numberOfSubdomains  3;
 
+// For testing only: use random decomposition.
+method              random;
 
 // ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/fvSolution b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/fvSolution
index cd310baa4aa..1d5d21be268 100644
--- a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/fvSolution
+++ b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/fvSolution
@@ -16,7 +16,7 @@ FoamFile
 
 solvers
 {
-    "(p|Phi)"
+    "(p|Phi|cellDisplacement)"
     {
         solver          PCG;
         preconditioner  DIC;
diff --git a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/snappyHexMeshDict b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/snappyHexMeshDict
index b8c3ce8cb35..14cf63af838 100644
--- a/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/snappyHexMeshDict
+++ b/tutorials/mesh/snappyHexMesh/addLayersToFaceZone/system/snappyHexMeshDict
@@ -174,16 +174,15 @@ addLayersControls
     // size of the refined cell outside layer (true) or absolute sizes (false).
     relativeSizes false;
 
-    // Expansion factor for layer mesh
-    expansionRatio 1.0;
-
-    firstLayerThickness 0.2e-3;
+    // Layers defined by overall thickness and expansion ratio
+    thickness           0.7e-3;
+    firstLayerThickness 0.1e-3;
 
     // Minimum overall thickness of total layers. If for any reason layer
     // cannot be above minThickness do not add layer.
     // If relativeSizes this is relative to undistorted size of cell
     // outside layer..
-    minThickness 0.1e-3;
+    minThickness 0.0001e-3;
 
 
     // Per final patch (so not geometry!) the layer information
@@ -191,19 +190,19 @@ addLayersControls
     {
         maxY
         {
-            nSurfaceLayers 2;
+            nSurfaceLayers 3;
         }
         minY
         {
-            nSurfaceLayers 2;
+            nSurfaceLayers 3;
         }
         A
         {
-            nSurfaceLayers 2;
+            nSurfaceLayers 3;
         }
         A_slave
         {
-            nSurfaceLayers 2;
+            nSurfaceLayers 3;
         }
     }
 
@@ -257,14 +256,29 @@ addLayersControls
         // Number of smoothing iterations of interior mesh movement direction
         nSmoothNormals 3;
 
-
-    // Mesh shrinking
-
         // Optional: at non-patched sides allow mesh to slip if extrusion
         // direction makes angle larger than slipFeatureAngle. Default is
         // 0.5*featureAngle.
         slipFeatureAngle 30;
 
+
+    //// Motion solver instead of default medial axis
+    //
+    //    //- Use displacementMotionSolver to shrink mesh
+    //    meshShrinker displacementMotionSolver;
+    //
+    //    //- Use laplacian for shrinking
+    //    solver displacementLaplacian;
+    //
+    //    displacementLaplacianCoeffs
+    //    {
+    //        diffusivity quadratic inverseDistance ("m.*");
+    //    }
+
+
+
+    // Mesh shrinking
+
         // Maximum number of snapping relaxation iterations. Should stop
         // before upon reaching a correct mesh.
         nRelaxIter 5;
@@ -276,18 +290,10 @@ addLayersControls
         // exit if it reaches this number of iterations; possibly with an
         // illegal mesh.
         nLayerIter 50;
-//
-//        // Max number of iterations after which relaxed meshQuality controls
-//        // get used. Up to nRelaxedIter it uses the settings in
-//        // meshQualityControls,
-//        // after nRelaxedIter it uses the values in
-//        // meshQualityControls::relaxed.
-//        nRelaxedIter 20;
-//
-//        // Additional reporting: if there are just a few faces where there
-//        // are mesh errors (after adding the layers) print their face centres.
-//        // This helps in tracking down problematic mesh areas.
-//        //additionalReporting true;
+
+        // Overall number of outer layer of iterations. Default is 1 -> all
+        // layers are added in one pass.
+        nOuterIter 3;
 }
 
 // Generic mesh quality settings. At any undoable phase these determine
@@ -313,5 +319,15 @@ meshQualityControls
 // Note: the write tolerance needs to be higher than this.
 mergeTolerance 1e-6;
 
+debugFlags
+(
+    //mesh
+);
+
+// Write flags
+writeFlags
+(
+    //layerFields     // write volScalarField for layer coverage
+);
 
 // ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/controlDict b/tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/controlDict
new file mode 100644
index 00000000000..43a296491d0
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/controlDict
@@ -0,0 +1,54 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1906                                 |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    location    "system";
+    object      controlDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+//DebugSwitches
+//{
+//    holeToFace  1;
+//}
+
+application     icoFoam;
+
+startFrom       startTime;
+
+startTime       0;
+
+stopAt          endTime;
+
+endTime         100;
+
+deltaT          1;
+
+writeControl    timeStep;
+
+writeInterval   1;
+
+purgeWrite      0;
+
+writeFormat     ascii;
+
+writePrecision  6;
+
+writeCompression off;
+
+timeFormat      general;
+
+timePrecision   6;
+
+runTimeModifiable true;
+
+
+// ************************************************************************* //
diff --git a/tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/snappyHexMeshDict b/tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/snappyHexMeshDict
new file mode 100644
index 00000000000..51d69f15797
--- /dev/null
+++ b/tutorials/mesh/snappyHexMesh/sphere_gapClosure/system/snappyHexMeshDict
@@ -0,0 +1,311 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v1906                                 |
+|   \\  /    A nd           | Web:      www.OpenFOAM.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      snappyHexMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Which of the steps to run
+castellatedMesh true;
+snap            true;
+addLayers       false;
+
+
+// Geometry. Definition of all surfaces. All surfaces are of class
+// searchableSurface.
+// Surfaces are used
+// - to specify refinement for any mesh cell intersecting it
+// - to specify refinement for any mesh cell inside/outside/near
+// - to 'snap' the mesh boundary to the surface
+geometry
+{
+    dummy
+    {
+        // Dummy surface just to see if indexing is correct
+        type box;
+        min (0 0 0);
+        max (1 1 1);
+    }
+    sphere_with_hole.stl
+    {
+        type triSurfaceMesh;
+        name sphere;
+    }
+};
+
+
+
+// Settings for the castellatedMesh generation.
+castellatedMeshControls
+{
+
+    // Refinement parameters
+    // ~~~~~~~~~~~~~~~~~~~~~
+
+    // If local number of cells is >= maxLocalCells on any processor
+    // switches from from refinement followed by balancing
+    // (current method) to (weighted) balancing before refinement.
+    maxLocalCells 100000;
+
+    // Overall cell limit (approximately). Refinement will stop immediately
+    // upon reaching this number so a refinement level might not complete.
+    // Note that this is the number of cells before removing the part which
+    // is not 'visible' from the keepPoint. The final number of cells might
+    // actually be a lot less.
+    maxGlobalCells 2000000;
+
+    // The surface refinement loop might spend lots of iterations refining just a
+    // few cells. This setting will cause refinement to stop if <= minimumRefine
+    // are selected for refinement. Note: it will at least do one iteration
+    // (unless the number of cells to refine is 0)
+    minRefinementCells 1;
+
+    // Allow a certain level of imbalance during refining
+    // (since balancing is quite expensive)
+    // Expressed as fraction of perfect balance (= overall number of cells /
+    // nProcs). 0=balance always.
+    maxLoadUnbalance 0.10;
+
+
+    // Number of buffer layers between different levels.
+    // 1 means normal 2:1 refinement restriction, larger means slower
+    // refinement.
+    nCellsBetweenLevels 1;
+
+
+
+    // Explicit feature edge refinement
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Specifies a level for any cell intersected by its edges.
+    // This is a featureEdgeMesh, read from constant/triSurface for now.
+    features
+    (
+    );
+
+
+
+    // Surface based refinement
+    // ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Specifies two levels for every surface. The first is the minimum level,
+    // every cell intersecting a surface gets refined up to the minimum level.
+    // The second level is the maximum level. Cells that 'see' multiple
+    // intersections where the intersections make an
+    // angle > resolveFeatureAngle get refined up to the maximum level.
+
+    refinementSurfaces
+    {
+        "sphere.*"
+        {
+            // Surface-wise min and max refinement level
+            level (5 5);
+
+            // Optional level at which to start early checking for leaks
+            //leakLevel 2;
+            //faceZone sphere_with_hole;
+            //cellZone sphere_with_hole;
+            //cellZoneInside  insidePoint;
+            //insidePoint     (1.5 1.5 1.5);
+        }
+    }
+
+    // Resolve sharp angles
+    resolveFeatureAngle 30;
+
+
+    // Region-wise refinement
+    // ~~~~~~~~~~~~~~~~~~~~~~
+
+    // Specifies refinement level for cells in relation to a surface. One of
+    // three modes
+    // - distance. 'levels' specifies per distance to the surface the
+    //   wanted refinement level. The distances need to be specified in
+    //   descending order.
+    // - inside. 'levels' is only one entry and only the level is used. All
+    //   cells inside the surface get refined up to the level. The surface
+    //   needs to be closed for this to be possible.
+    // - outside. Same but cells outside.
+
+    refinementRegions
+    {
+    }
+
+
+    // Mesh selection
+    // ~~~~~~~~~~~~~~
+
+    // After refinement patches get added for all refinementSurfaces and
+    // all cells intersecting the surfaces get put into these patches. The
+    // section reachable from the locationInMesh is kept.
+    // NOTE: This point should never be on a face, always inside a cell, even
+    // after refinement.
+    locationInMesh (1.5 1.5 1.5);
+
+    locationsOutsideMesh ((3.8 3.8 3.8));
+
+    // Whether any faceZones (as specified in the refinementSurfaces)
+    // are only on the boundary of corresponding cellZones or also allow
+    // free-standing zone faces. Not used if there are no faceZones.
+    allowFreeStandingZoneFaces false;
+}
+
+
+// Settings for the snapping.
+snapControls
+{
+    //- Number of patch smoothing iterations before finding correspondence
+    //  to surface
+    nSmoothPatch 3;
+
+    //- Relative distance for points to be attracted by surface feature point
+    //  or edge. True distance is this factor times local
+    //  maximum edge length.
+    tolerance 1.0;
+
+    //- Number of mesh displacement relaxation iterations.
+    nSolveIter 10;
+
+    //- Maximum number of snapping relaxation iterations. Should stop
+    //  before upon reaching a correct mesh.
+    nRelaxIter 5;
+
+    // Feature snapping
+
+        //- Number of feature edge snapping iterations.
+        //  Leave out altogether to disable.
+        nFeatureSnapIter 10;
+
+        //- Detect (geometric only) features by sampling the surface
+        //  (default=false).
+        implicitFeatureSnap false;
+
+        //- Use castellatedMeshControls::features (default = true)
+        explicitFeatureSnap false;
+
+        //- Detect points on multiple surfaces (only for explicitFeatureSnap)
+        multiRegionFeatureSnap false;
+}
+
+
+
+// Settings for the layer addition.
+addLayersControls
+{
+    // Are the thickness parameters below relative to the undistorted
+    // size of the refined cell outside layer (true) or absolute sizes (false).
+    relativeSizes true;
+
+    // Per final patch (so not geometry!) the layer information
+    layers
+    {
+    }
+
+    // Expansion factor for layer mesh
+    expansionRatio 1.0;
+
+    // Wanted thickness of final added cell layer. If multiple layers
+    // is the thickness of the layer furthest away from the wall.
+    // Relative to undistorted size of cell outside layer.
+    // See relativeSizes parameter.
+    finalLayerThickness 0.3;
+
+    // Minimum thickness of cell layer. If for any reason layer
+    // cannot be above minThickness do not add layer.
+    // Relative to undistorted size of cell outside layer.
+    minThickness 0.1;
+
+    // If points get not extruded do nGrow layers of connected faces that are
+    // also not grown. This helps convergence of the layer addition process
+    // close to features.
+    // Note: changed(corrected) w.r.t 1.7.x! (didn't do anything in 1.7.x)
+    nGrow 0;
+
+    // Advanced settings
+
+    // When not to extrude surface. 0 is flat surface, 90 is when two faces
+    // are perpendicular
+    featureAngle 60;
+
+    // At non-patched sides allow mesh to slip if extrusion direction makes
+    // angle larger than slipFeatureAngle.
+    slipFeatureAngle 30;
+
+    // Maximum number of snapping relaxation iterations. Should stop
+    // before upon reaching a correct mesh.
+    nRelaxIter 3;
+
+    // Number of smoothing iterations of surface normals
+    nSmoothSurfaceNormals 1;
+
+    // Number of smoothing iterations of interior mesh movement direction
+    nSmoothNormals 3;
+
+    // Smooth layer thickness over surface patches
+    nSmoothThickness 10;
+
+    // Stop layer growth on highly warped cells
+    maxFaceThicknessRatio 0.5;
+
+    // Reduce layer growth where ratio thickness to medial
+    // distance is large
+    maxThicknessToMedialRatio 0.3;
+
+    // Angle used to pick up medial axis points
+    // Note: changed(corrected) w.r.t 1.7.x! 90 degrees corresponds to 130
+    // in 1.7.x.
+    minMedianAxisAngle 90;
+
+
+    // Create buffer region for new layer terminations
+    nBufferCellsNoExtrude 0;
+
+
+    // Overall max number of layer addition iterations. The mesher will exit
+    // if it reaches this number of iterations; possibly with an illegal
+    // mesh.
+    nLayerIter 50;
+}
+
+
+
+// Generic mesh quality settings. At any undoable phase these determine
+// where to undo.
+meshQualityControls
+{
+    #includeEtc "caseDicts/meshQualityDict"
+
+    // Advanced
+
+    //- Number of error distribution iterations
+    nSmoothScale 4;
+    //- Amount to scale back displacement at error points
+    errorReduction 0.75;
+}
+
+
+// Advanced
+
+// Format to use for lines (e.g. leak path)
+setFormat ensight;
+
+//debugFlags
+//(
+//    mesh
+//);
+
+// Merge tolerance. Is fraction of overall bounding box of initial mesh.
+// Note: the write tolerance needs to be higher than this.
+mergeTolerance 1e-6;
+
+
+// ************************************************************************* //
-- 
GitLab