diff --git a/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C b/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C
index a7ec45eef47ab4c8c654330a2889d9525c39759b..573f2926e2132893ad5cfa4ccd854f6f22689cfd 100644
--- a/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C
+++ b/applications/utilities/mesh/generation/extrude/extrudeMesh/extrudeMesh.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -493,21 +493,30 @@ int main(int argc, char *argv[])
         //    new patchID to neighbour processor)
         //  - number of new patches (nPatches)
 
-        labelList sidePatchID;
+        labelList edgePatchID;
+        labelList edgeZoneID;
+        boolList edgeFlip;
+        labelList inflateFaceID;
         label nPatches;
         Map<label> nbrProcToPatch;
         Map<label> patchToNbrProc;
-        addPatchCellLayer::calcSidePatch
+        addPatchCellLayer::calcExtrudeInfo
         (
+            true,   // for internal edges get zone info from any face
+
             mesh,
             globalFaces,
             edgeGlobalFaces,
             extrudePatch,
 
-            sidePatchID,
+            edgePatchID,
             nPatches,
             nbrProcToPatch,
-            patchToNbrProc
+            patchToNbrProc,
+
+            edgeZoneID,
+            edgeFlip,
+            inflateFaceID
         );
 
 
@@ -659,7 +668,12 @@ int main(int argc, char *argv[])
 
             ratio,              // expansion ratio
             extrudePatch,       // patch faces to extrude
-            sidePatchID,        // if boundary edge: patch to use
+
+            edgePatchID,        // if boundary edge: patch for extruded face
+            edgeZoneID,         // optional zone for extruded face
+            edgeFlip,
+            inflateFaceID,      // mesh face that zone/patch info is from
+
             exposedPatchID,     // if new mesh: patches for exposed faces
             nFaceLayers,
             nPointLayers,
diff --git a/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMesh.C b/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMesh.C
index c6906a6c920d1a351bd454fcaabc4ca07ca0e3c3..67a9c3a9444a3803f8c43dd270b2b7330219b32a 100644
--- a/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMesh.C
+++ b/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMesh.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -1121,6 +1121,38 @@ int main(int argc, char *argv[])
     );
 
 
+    // Refinement parameters
+    const refinementParameters refineParams(refineDict);
+
+    // Snap parameters
+    const snapParameters snapParams(snapDict);
+
+
+
+    // Add all the cellZones and faceZones
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // 1. cellZones relating to surface (faceZones added later)
+
+    const labelList namedSurfaces
+    (
+        surfaceZonesInfo::getNamedSurfaces(surfaces.surfZones())
+    );
+
+    labelList surfaceToCellZone = surfaceZonesInfo::addCellZonesToMesh
+    (
+        surfaces.surfZones(),
+        namedSurfaces,
+        mesh
+    );
+
+
+    // 2. cellZones relating to locations
+
+    refineParams.addCellZonesToMesh(mesh);
+
+
+
     // Add all the surface regions as patches
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -1128,6 +1160,8 @@ int main(int argc, char *argv[])
     //  (faceZone surfaces)
     labelList globalToMasterPatch;
     labelList globalToSlavePatch;
+
+
     {
         Info<< nl
             << "Adding patches for surface regions" << nl
@@ -1148,6 +1182,7 @@ int main(int argc, char *argv[])
 
         const labelList& surfaceGeometry = surfaces.surfaces();
         const PtrList<dictionary>& surfacePatchInfo = surfaces.patchInfo();
+        const polyBoundaryMesh& pbm = mesh.boundaryMesh();
 
         forAll(surfaceGeometry, surfI)
         {
@@ -1157,7 +1192,9 @@ int main(int argc, char *argv[])
 
             Info<< surfaces.names()[surfI] << ':' << nl << nl;
 
-            if (surfaces.surfZones()[surfI].faceZoneName().empty())
+            const word& fzName = surfaces.surfZones()[surfI].faceZoneName();
+
+            if (fzName.empty())
             {
                 // 'Normal' surface
                 forAll(regNames, i)
@@ -1188,7 +1225,7 @@ int main(int argc, char *argv[])
 
                     Info<< setf(ios_base::left)
                         << setw(6) << patchI
-                        << setw(20) << mesh.boundaryMesh()[patchI].type()
+                        << setw(20) << pbm[patchI].type()
                         << setw(30) << regNames[i] << nl;
 
                     globalToMasterPatch[globalRegionI] = patchI;
@@ -1228,7 +1265,7 @@ int main(int argc, char *argv[])
 
                         Info<< setf(ios_base::left)
                             << setw(6) << patchI
-                            << setw(20) << mesh.boundaryMesh()[patchI].type()
+                            << setw(20) << pbm[patchI].type()
                             << setw(30) << regNames[i] << nl;
 
                         globalToMasterPatch[globalRegionI] = patchI;
@@ -1260,12 +1297,27 @@ int main(int argc, char *argv[])
 
                         Info<< setf(ios_base::left)
                             << setw(6) << patchI
-                            << setw(20) << mesh.boundaryMesh()[patchI].type()
+                            << setw(20) << pbm[patchI].type()
                             << setw(30) << slaveName << nl;
 
                         globalToSlavePatch[globalRegionI] = patchI;
                     }
                 }
+
+                // For now: have single faceZone per surface. Use first
+                // region in surface for patch for zoneing
+                if (regNames.size())
+                {
+                    label globalRegionI = surfaces.globalRegion(surfI, 0);
+
+                    meshRefiner.addFaceZone
+                    (
+                        fzName,
+                        pbm[globalToMasterPatch[globalRegionI]].name(),
+                        pbm[globalToSlavePatch[globalRegionI]].name(),
+                        surfaces.surfZones()[surfI].faceType()
+                    );
+                }
             }
 
             Info<< nl;
@@ -1275,10 +1327,85 @@ int main(int argc, char *argv[])
     }
 
 
+
+    // Add all information for all the remaining faceZones
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    HashTable<Pair<word> > faceZoneToPatches;
+    forAll(mesh.faceZones(), zoneI)
+    {
+        const word& fzName = mesh.faceZones()[zoneI].name();
+
+        label mpI, spI;
+        surfaceZonesInfo::faceZoneType fzType;
+        bool hasInfo = meshRefiner.getFaceZoneInfo(fzName, mpI, spI, fzType);
+
+        if (!hasInfo)
+        {
+            // faceZone does not originate from a surface but presumably
+            // from a cellZone pair instead
+            string::size_type i = fzName.find("_to_");
+            if (i != string::npos)
+            {
+                word cz0 = fzName.substr(0, i);
+                word cz1 = fzName.substr(i+4, fzName.size()-i+4);
+                word slaveName(cz1 + "_to_" + cz0);
+                faceZoneToPatches.insert(fzName, Pair<word>(fzName, slaveName));
+            }
+            else
+            {
+                // Add as fzName + fzName_slave
+                const word slaveName = fzName + "_slave";
+                faceZoneToPatches.insert(fzName, Pair<word>(fzName, slaveName));
+            }
+        }
+    }
+
+    if (faceZoneToPatches.size())
+    {
+        autoRefineDriver::addFaceZones
+        (
+            meshRefiner,
+            refineParams,
+            faceZoneToPatches
+        );
+    }
+
+
+
+    // Re-do intersections on meshed boundaries since they use an extrapolated
+    // other side
+    {
+        const labelList adaptPatchIDs(meshRefiner.meshedPatches());
+
+        const polyBoundaryMesh& pbm = mesh.boundaryMesh();
+
+        label nFaces = 0;
+        forAll(adaptPatchIDs, i)
+        {
+            nFaces += pbm[adaptPatchIDs[i]].size();
+        }
+
+        labelList faceLabels(nFaces);
+        nFaces = 0;
+        forAll(adaptPatchIDs, i)
+        {
+            const polyPatch& pp = pbm[adaptPatchIDs[i]];
+            forAll(pp, i)
+            {
+                faceLabels[nFaces++] = pp.start()+i;
+            }
+        }
+        meshRefiner.updateIntersections(faceLabels);
+    }
+
+
+
     // Parallel
     // ~~~~~~~~
 
-    // Decomposition
+    // Construct decomposition engine. Note: cannot use decompositionModel
+    // MeshObject since we're clearing out the mesh inside the mesh generation.
     autoPtr<decompositionMethod> decomposerPtr
     (
         decompositionMethod::New
@@ -1312,14 +1439,17 @@ int main(int argc, char *argv[])
     const Switch wantSnap(meshDict.lookup("snap"));
     const Switch wantLayers(meshDict.lookup("addLayers"));
 
-    // Refinement parameters
-    const refinementParameters refineParams(refineDict);
-
-    // Snap parameters
-    const snapParameters snapParams(snapDict);
+    const Switch mergePatchFaces
+    (
+        meshDict.lookupOrDefault("mergePatchFaces", true)
+    );
 
-    // Layer addition parameters
-    const layerParameters layerParams(layerDict, mesh.boundaryMesh());
+    if (!mergePatchFaces)
+    {
+        Info<< "Not merging patch-faces of cell to preserve"
+            << " (split)hex cell shape."
+            << nl << endl;
+    }
 
 
     if (wantRefine)
@@ -1348,6 +1478,7 @@ int main(int argc, char *argv[])
             refineParams,
             snapParams,
             refineParams.handleSnapProblems(),
+            mergePatchFaces,        // merge co-planar faces
             motionDict
         );
 
@@ -1387,6 +1518,7 @@ int main(int argc, char *argv[])
         (
             snapDict,
             motionDict,
+            mergePatchFaces,
             curvature,
             planarAngle,
             snapParams
@@ -1408,6 +1540,9 @@ int main(int argc, char *argv[])
     {
         cpuTime timer;
 
+        // Layer addition parameters
+        const layerParameters layerParams(layerDict, mesh.boundaryMesh());
+
         autoLayerDriver layerDriver
         (
             meshRefiner,
@@ -1433,6 +1568,7 @@ int main(int argc, char *argv[])
             layerDict,
             motionDict,
             layerParams,
+            mergePatchFaces,
             preBalance,
             decomposer,
             distributor
diff --git a/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMeshDict b/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMeshDict
index 577e116b64d067d8f83484b677a850faa782d10a..9b759b5ca4f6628dd9e11117d62697a0b12e0aef 100644
--- a/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMeshDict
+++ b/applications/utilities/mesh/generation/snappyHexMesh/snappyHexMeshDict
@@ -71,6 +71,11 @@ geometry
     }
 };
 
+
+// Optional: avoid patch-face merging. Allows mesh to be used for
+//           refinement/unrefinement
+//mergePatchFaces    off; // default on
+
 // Settings for the castellatedMesh generation.
 castellatedMeshControls
 {
@@ -177,7 +182,7 @@ castellatedMeshControls
             //  how to select the cells that are in the cellZone
             //  (inside / outside / specified insidePoint)
             //  The orientation of the faceZone is
-            //  - if on cellZone(s) : point out of (maximum) cellZone
+            //  - if on cellZone(s) : point out of (minimum) cellZone
             //  - if freestanding   : oriented according to surface
 
             //faceZone sphere;
@@ -249,16 +254,70 @@ castellatedMeshControls
 
     // 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.
+    // section reachable from the location(s)InMesh is kept.
     // NOTE: This point should never be on a face, always inside a cell, even
     // after refinement.
-    locationInMesh (5 0.28 0.43);
+    //
+    // There are two different ways of specifying the regions to keep:
+    // 1. a single locationInMesh. All the 'zoned' surfaces are marked as such
+    //    in the refinementSurfaces with the faceZone and cellZone keywords.
+    //
+    // or
+    //
+    // 2. multiple locationsInMesh, with per location the name of the cellZone.
+    //    This uses walking to determine zones and automatically creates
+    //    faceZones on the outside of cellZones.
+
+
+        // Ad 1. Specify a single location and how to treat faces inbetween
+        //       cellZones
+        locationInMesh (5 0.28 0.43);
+
+        // 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 true;
+
+
+
+        // 2. Specify multiple locations with optional cellZones for the
+        //    regions. faceZones are automatically constructed from the
+        //    names of the cellZones: <cellZoneA> _to_ <cellZoneB>
+        //    where the cellZoneA is the lowest numbered cellZone (non-cellZone
+        //    cells are in a special region called "none" which is always
+        //    last).
+
+        locationsInMesh
+        (
+            ((-0.09 -0.039 -0.049)  bottomAir)  // cellZone 0
+            ((-0.09 0.009 -0.049)   topAir)     // cellZone 1
+            ((-0.09 0.001 -0.049)   leftSolid)  // cellZone 2
+            ((0.02  0.001 -0.049)   rightSolid) // cellZone 3
+            ((-0.001 -0.039 0.0015) heater)     // cellZone 4
+        );
+
+        // Per synthesised faceZone name the faceType and type of baffles to
+        // create
+        faceZoneControls
+        {
+            bottomAir_to_heater
+            {
+                // Optional specification of patch type (default is wall). No
+                // constraint types (cyclic, symmetry) etc. are allowed.
+                patchInfo
+                {
+                    type patch;
+                    inGroups (patchPatches);
+                }
+                faceType baffle;
+            }
+        }
+
 
-    // 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 true;
 
+    // Optional locations that should not be reachable from
+    // location(s)InMesh
+    locationsOutsideMesh ((100 100 100));
 
     // Optional: do not remove cells likely to give snapping problems
     // handleSnapProblems false;
@@ -266,6 +325,10 @@ castellatedMeshControls
     // Optional: switch off topological test for cells to-be-squashed
     //           and use geometric test instead
     //useTopologicalSnapDetection false;
+
+    // Optional: do not refine surface cells with opposite faces of
+    //           differing refinement levels
+    //interfaceRefine false;
 }
 
 // Settings for the snapping.
@@ -275,6 +338,11 @@ snapControls
     // to surface
     nSmoothPatch 3;
 
+    // Optional: number of smoothing iterations for internal points on
+    // refinement interfaces. This will reduce non-orthogonality on
+    // refinement interfaces.
+    //nSmoothInternal $nSmoothPatch;
+
     // Maximum relative distance for points to be attracted by surface.
     // True distance is this factor times local maximum edge length.
     // Note: changed(corrected) w.r.t 17x! (17x used 2* tolerance)
@@ -287,6 +355,11 @@ snapControls
     // before upon reaching a correct mesh.
     nRelaxIter 5;
 
+    // (wip) disable snapping to opposite near surfaces (revert to 22x
+    //  behaviour)
+    // detectNearSurfacesSnap false;
+
+
     // Feature snapping
 
         // Number of feature edge snapping iterations.
@@ -305,8 +378,28 @@ snapControls
         multiRegionFeatureSnap false;
 
 
-    // wip: disable snapping to opposite near surfaces (revert to 22x behaviour)
-    // detectNearSurfacesSnap false;
+        //- When to run face splitting (never at first iteration, always
+        //  at last iteration). Is interval. Default -1 (disabled)
+        //nFaceSplitInterval 5;
+
+
+        // (wip) Optional for explicit feature snapping:
+        //- Detect baffle edges. Default is true.
+        //detectBaffles false;
+        //- Erase attraction close to feature point. Default is false.
+        //releasePoints true;
+        //- Walk along feature edges, adding missing ones. Default is true.
+        //stringFeatures false;
+        //- If diagonal attraction also attract other face points. Default is
+        //  false
+        //avoidDiagonal true;
+        //- When splitting what concave faces to leave intact. Default is 45
+        //  degrees.
+        //concaveAngle 30;
+        //- When splitting the minimum area ratio of faces. If face split
+        //  causes ratio of area less than this do not split. Default is 0.3
+        //minAreaRatio 0.3;
+
 }
 
 // Settings for the layer addition.
@@ -350,10 +443,10 @@ addLayersControls
     // cannot be above minThickness do not add layer.
     // If relativeSizes this is relative to undistorted size of cell
     // outside layer..
-    minThickness 0.25;
+    minThickness 0.1;
 
 
-    // Per final patch (so not geometry!) the layer information
+    // Per final patch or faceZone (so not geometry!) the layer information
     // Note: This behaviour changed after 21x. Any non-mentioned patches
     //       now slide unless:
     //          - nSurfaceLayers is explicitly mentioned to be 0.
@@ -397,6 +490,10 @@ addLayersControls
         // are perpendicular
         featureAngle 130;
 
+        // When to merge patch faces. Default is featureAngle. Useful when
+        // featureAngle is large.
+        //mergePatchFacesAngle 45;
+
         // Stop layer growth on highly warped cells
         maxFaceThicknessRatio 0.5;
 
@@ -433,8 +530,10 @@ addLayersControls
         // default is 0.
         //nSmoothDisplacement 90;
 
-        // (wip)Optional: do not extrude a point if none of the surrounding points is
-        // not extruded. Default is false.
+        // (wip)Optional: do not extrude any point where
+        //   (false) : all surrounding faces are not fully extruded
+        //   (true)  : all surrounding points are not extruded
+        // Default is false.
         //detectExtrusionIsland true;
 
 
@@ -449,7 +548,8 @@ addLayersControls
         // before upon reaching a correct mesh.
         nRelaxIter 5;
 
-        // Create buffer region for new layer terminations
+        // Create buffer region for new layer terminations, i.e. gradually
+        // step down number of layers. Set to <0 to terminate layer in one go.
         nBufferCellsNoExtrude 0;
 
         // Overall max number of layer addition iterations. The mesher will
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C
index 58d39bc3cf75c31cf9e56a45b30ec9c65d34f712..4d518bcece5a8a2c03865d6f66bc3b52cc97dfe1 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -34,6 +34,8 @@ License
 #include "polyModifyFace.H"
 #include "polyAddCell.H"
 #include "globalIndex.H"
+#include "PatchTools.H"
+#include "dummyTransform.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -221,13 +223,15 @@ Foam::labelPair Foam::addPatchCellLayer::getEdgeString
 }
 
 
-// Adds a side face i.e. extrudes a patch edge.
 Foam::label Foam::addPatchCellLayer::addSideFace
 (
     const indirectPrimitivePatch& pp,
     const labelListList& addedCells,    // per pp face the new extruded cell
     const face& newFace,
     const label newPatchID,
+    const label zoneI,
+    const bool edgeFlip,
+    const label inflateFaceI,
 
     const label ownFaceI,               // pp face that provides owner
     const label nbrFaceI,
@@ -238,68 +242,14 @@ Foam::label Foam::addPatchCellLayer::addSideFace
     polyTopoChange& meshMod
 ) const
 {
-    // Face or edge to 'inflate' from
-    label inflateEdgeI = -1;
-    label inflateFaceI = -1;
-
-    // Check mesh faces using edge
-    if (addToMesh_)
-    {
-        forAll(meshFaces, i)
-        {
-            if (mesh_.isInternalFace(meshFaces[i]))
-            {
-                // meshEdge uses internal faces so ok to inflate from it
-                inflateEdgeI = meshEdgeI;
-                break;
-            }
-        }
-    }
-
-    // Zone info comes from any side patch face. Otherwise -1 since we
-    // don't know what to put it in - inherit from the extruded faces?
-    label zoneI = -1;   //mesh_.faceZones().whichZone(meshFaceI);
-    bool flip = false;
-
     label addedFaceI = -1;
 
+
     // Is patch edge external edge of indirectPrimitivePatch?
     if (nbrFaceI == -1)
     {
         // External edge so external face.
 
-        const polyBoundaryMesh& patches = mesh_.boundaryMesh();
-
-        // Loop over all faces connected to edge to inflate and
-        // see if we can find a face that is otherPatchID
-
-        // Get my mesh face and its zone.
-        label meshFaceI = pp.addressing()[ownFaceI];
-
-        forAll(meshFaces, k)
-        {
-            label faceI = meshFaces[k];
-
-            if
-            (
-                (faceI != meshFaceI)
-             && (patches.whichPatch(faceI) == newPatchID)
-            )
-            {
-                // Found the patch face. Use it to inflate from
-                inflateEdgeI = -1;
-                inflateFaceI = faceI;
-
-                zoneI = mesh_.faceZones().whichZone(faceI);
-                if (zoneI != -1)
-                {
-                    label index = mesh_.faceZones()[zoneI].whichFace(faceI);
-                    flip = mesh_.faceZones()[zoneI].flipMap()[index];
-                }
-                break;
-            }
-        }
-
         // Determine if different number of layer on owner and neighbour side
         // (relevant only for coupled faces). See section for internal edge
         // below.
@@ -333,16 +283,16 @@ Foam::label Foam::addPatchCellLayer::addSideFace
         (
             polyAddFace
             (
-                newFace,                    // face
-                addedCells[ownFaceI][layerOwn],   // owner
-                -1,                         // neighbour
-                -1,                         // master point
-                inflateEdgeI,               // master edge
-                inflateFaceI,               // master face
-                false,                      // flux flip
-                newPatchID,                 // patch for face
-                zoneI,                      // zone for face
-                flip                        // face zone flip
+                newFace,                        // face
+                addedCells[ownFaceI][layerOwn], // owner
+                -1,                             // neighbour
+                -1,                             // master point
+                -1,                             // master edge
+                inflateFaceI,                   // master face
+                false,                          // flux flip
+                newPatchID,                     // patch for face
+                zoneI,                          // zone for face
+                edgeFlip                        // face zone flip
             )
         );
     }
@@ -395,20 +345,37 @@ Foam::label Foam::addPatchCellLayer::addSideFace
             layerOwn = layerI;
         }
 
+
+        // Check mesh internal faces using edge to initialise
+        label inflateEdgeI = -1;
+        if (addToMesh_)
+        {
+            forAll(meshFaces, i)
+            {
+                if (mesh_.isInternalFace(meshFaces[i]))
+                {
+                    // meshEdge uses internal faces so ok to inflate from it
+                    inflateEdgeI = meshEdgeI;
+                    break;
+                }
+            }
+        }
+
+
         addedFaceI = meshMod.setAction
         (
             polyAddFace
             (
-                newFace,                    // face
-                addedCells[ownFaceI][layerOwn],   // owner
-                addedCells[nbrFaceI][layerNbr],   // neighbour
-                -1,                         // master point
-                inflateEdgeI,               // master edge
-                -1,                         // master face
-                false,                      // flux flip
-                -1,                         // patch for face
-                zoneI,                      // zone for face
-                flip                        // face zone flip
+                newFace,                        // face
+                addedCells[ownFaceI][layerOwn], // owner
+                addedCells[nbrFaceI][layerNbr], // neighbour
+                -1,                             // master point
+                inflateEdgeI,                   // master edge
+                -1,                             // master face
+                false,                          // flux flip
+                -1,                             // patch for face
+                zoneI,                          // zone for face
+                edgeFlip                        // face zone flip
             )
         );
 
@@ -467,6 +434,134 @@ void Foam::addPatchCellLayer::setFaceProps
 }
 
 
+void Foam::addPatchCellLayer::setFaceProps
+(
+    const polyMesh& mesh,
+    const indirectPrimitivePatch& pp,
+    const label ppEdgeI,
+    const label faceI,
+
+    label& patchI,
+    label& zoneI,
+    bool& zoneFlip,
+    label& inflateFaceI
+)
+{
+    setFaceProps
+    (
+        mesh,
+        faceI,
+
+        patchI,
+        zoneI,
+        zoneFlip
+    );
+
+    if (patchI != -1 || zoneI != -1)
+    {
+        inflateFaceI = faceI;
+    }
+
+    if (zoneI != -1)
+    {
+        // Correct flip for patch edge ordering
+        const edge& ppEdge = pp.edges()[ppEdgeI];
+        const edge patchEdge
+        (
+            pp.meshPoints()[ppEdge[0]],
+            pp.meshPoints()[ppEdge[1]]
+        );
+
+        const face& f = mesh.faces()[faceI];
+        bool found = false;
+        forAll(f, fp)
+        {
+            const edge e(f[fp], f.nextLabel(fp));
+            int stat = edge::compare(e, patchEdge);
+            if (stat == 1)
+            {
+                found = true;
+                break;
+            }
+            else if (stat == -1)
+            {
+                found = true;
+                zoneFlip = !zoneFlip;
+                break;
+            }
+        }
+
+        if (!found)
+        {
+            FatalErrorIn("addPatchCellLayer::setFaceProps(..)")
+                << "Problem: cannot find patch edge " << ppEdgeI
+                << " with mesh vertices " << patchEdge
+                << " at " << patchEdge.line(mesh.points())
+                << " is not in face " << faceI << " with mesh vertices "
+                << f
+                << exit(FatalError);
+        }
+    }
+}
+
+
+void Foam::addPatchCellLayer::findZoneFace
+(
+    const bool useInternalFaces,
+    const bool useBoundaryFaces,
+
+    const polyMesh& mesh,
+    const indirectPrimitivePatch& pp,
+    const label ppEdgeI,
+    const UIndirectList<label>& excludeFaces,
+    const labelList& meshFaces,
+
+    label& inflateFaceI,
+    label& patchI,
+    label& zoneI,
+    bool& zoneFlip
+)
+{
+    inflateFaceI = -1;
+    patchI = -1;
+    zoneI = -1;
+    zoneFlip = false;
+
+    forAll(meshFaces, k)
+    {
+        label faceI = meshFaces[k];
+
+        if
+        (
+            (findIndex(excludeFaces, faceI) == -1)
+         && (
+                (mesh.isInternalFace(faceI) && useInternalFaces)
+             || (!mesh.isInternalFace(faceI) && useBoundaryFaces)
+            )
+        )
+        {
+            setFaceProps
+            (
+                mesh,
+                pp,
+                ppEdgeI,
+                faceI,
+
+                patchI,
+                zoneI,
+                zoneFlip,
+                inflateFaceI
+            );
+
+            if (zoneI != -1 || patchI != -1)
+            {
+                break;
+            }
+        }
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 // Construct from mesh
@@ -564,38 +659,61 @@ Foam::labelListList Foam::addPatchCellLayer::globalEdgeFaces
 }
 
 
-void Foam::addPatchCellLayer::calcSidePatch
+void Foam::addPatchCellLayer::calcExtrudeInfo
 (
+    const bool zoneFromAnyFace,
+
     const polyMesh& mesh,
     const globalIndex& globalFaces,
     const labelListList& globalEdgeFaces,
     const indirectPrimitivePatch& pp,
 
-    labelList& sidePatchID,
+    labelList& edgePatchID,
     label& nPatches,
     Map<label>& nbrProcToPatch,
-    Map<label>& patchToNbrProc
+    Map<label>& patchToNbrProc,
+    labelList& edgeZoneID,
+    boolList& edgeFlip,
+    labelList& inflateFaceID
 )
 {
     const polyBoundaryMesh& patches = mesh.boundaryMesh();
+    const globalMeshData& gd = mesh.globalData();
 
     // Precalculate mesh edges for pp.edges.
     const labelList meshEdges(pp.meshEdges(mesh.edges(), mesh.pointEdges()));
 
-    sidePatchID.setSize(pp.nEdges());
-    sidePatchID = -1;
+    edgePatchID.setSize(pp.nEdges());
+    edgePatchID = -1;
+    edgeZoneID.setSize(pp.nEdges());
+    edgeZoneID = -1;
+    edgeFlip.setSize(pp.nEdges());
+    edgeFlip = false;
+
+
+    // Determine properties for faces extruded from edges
+    // - edge inbetween two different processors:
+    //      - extrude as patch face on correct processor
+    // - edge at end of patch (so edgeFaces.size() == 1):
+    //      - use mesh face that is a boundary face
+    // - edge internal to patch (so edgeFaces.size() == 2):
+
+
 
     // These also get determined but not (yet) exported:
     // - whether face is created from other face or edge
-    // - what zone&orientation face should have
 
-    labelList inflateEdgeI(pp.nEdges(), -1);
-    labelList inflateFaceI(pp.nEdges(), -1);
-    labelList sideZoneID(pp.nEdges(), -1);
-    boolList sideFlip(pp.nEdges(), false);
+    inflateFaceID.setSize(pp.nEdges(), -1);
 
     nPatches = patches.size();
 
+
+
+    // Pass1:
+    // For all edges inbetween two processors: see if matches to existing
+    // processor patch or create interprocessor-patch if necessary.
+    // Sets edgePatchID[edgeI] but none of the other quantities
+
     forAll(globalEdgeFaces, edgeI)
     {
         const labelList& eGlobalFaces = globalEdgeFaces[edgeI];
@@ -606,8 +724,7 @@ void Foam::addPatchCellLayer::calcSidePatch
         )
         {
             // Locally but not globally a boundary edge. Hence a coupled
-            // edge. Find the patch to use if on different
-            // processors.
+            // edge. Find the patch to use if on different processors.
 
             label f0 = eGlobalFaces[0];
             label f1 = eGlobalFaces[1];
@@ -625,18 +742,25 @@ void Foam::addPatchCellLayer::calcSidePatch
 
             if (otherProcI != -1)
             {
-                sidePatchID[edgeI] = findProcPatch(mesh, otherProcI);
-                if (sidePatchID[edgeI] == -1)
+                if (findIndex(gd[Pstream::myProcNo()], otherProcI) != -1)
+                {
+                    // There is already a processorPolyPatch to otherProcI.
+                    // Use it. Note that we can only index procPatchMap
+                    // if the processor actually is a neighbour processor
+                    // so that is why we first check.
+                    edgePatchID[edgeI] = gd.procPatchMap()[otherProcI];
+                }
+                else
                 {
                     // Cannot find a patch to processor. See if already
                     // marked for addition
                     if (nbrProcToPatch.found(otherProcI))
                     {
-                        sidePatchID[edgeI] = nbrProcToPatch[otherProcI];
+                        edgePatchID[edgeI] = nbrProcToPatch[otherProcI];
                     }
                     else
                     {
-                        sidePatchID[edgeI] = nPatches;
+                        edgePatchID[edgeI] = nPatches;
                         nbrProcToPatch.insert(otherProcI, nPatches);
                         patchToNbrProc.insert(nPatches, otherProcI);
                         nPatches++;
@@ -647,9 +771,8 @@ void Foam::addPatchCellLayer::calcSidePatch
     }
 
 
-
-    // Determine face properties for all other boundary edges
-    // ------------------------------------------------------
+    // Pass2: determine face properties for all other edges
+    // ----------------------------------------------------
 
     const labelListList& edgeFaces = pp.edgeFaces();
 
@@ -657,13 +780,10 @@ void Foam::addPatchCellLayer::calcSidePatch
 
     forAll(edgeFaces, edgeI)
     {
-        if (edgeFaces[edgeI].size() == 1 && sidePatchID[edgeI] == -1)
+        if (edgePatchID[edgeI] == -1)
         {
-            // Proper, uncoupled patch edge.
+            UIndirectList<label> ppFaces(pp.addressing(), edgeFaces[edgeI]);
 
-            label myFaceI = pp.addressing()[edgeFaces[edgeI][0]];
-
-            // Pick up any boundary face on this edge and use its properties
             label meshEdgeI = meshEdges[edgeI];
             const labelList& meshFaces = mesh.edgeFaces
             (
@@ -671,43 +791,71 @@ void Foam::addPatchCellLayer::calcSidePatch
                 dynMeshEdgeFaces
             );
 
-            forAll(meshFaces, k)
+            if (edgeFaces[edgeI].size() == 2)
             {
-                label faceI = meshFaces[k];
+                // Internal edge. Look at any face (internal or boundary) to
+                // determine extrusion properties. First one that has zone
+                // info wins
 
-                if (faceI != myFaceI && !mesh.isInternalFace(faceI))
-                {
-                    setFaceProps
-                    (
-                        mesh,
-                        faceI,
+                label dummyPatchI = -1;
+                findZoneFace
+                (
+                    true,               // useInternalFaces,
+                    zoneFromAnyFace,    // useBoundaryFaces,
 
-                        sidePatchID[edgeI],
-                        sideZoneID[edgeI],
-                        sideFlip[edgeI]
-                    );
-                    inflateFaceI[edgeI] = faceI;
-                    inflateEdgeI[edgeI] = -1;
+                    mesh,
+                    pp,
+                    edgeI,
 
-                    break;
-                }
+
+                    ppFaces,            // excludeFaces,
+                    meshFaces,          // meshFaces,
+
+                    inflateFaceID[edgeI],
+                    dummyPatchI,        // do not use patch info
+                    edgeZoneID[edgeI],
+                    edgeFlip[edgeI]
+                );
+            }
+            else
+            {
+                // Proper, uncoupled patch edge
+
+                findZoneFace
+                (
+                    false,      // useInternalFaces,
+                    true,       // useBoundaryFaces,
+
+                    mesh,
+                    pp,
+                    edgeI,
+
+
+                    ppFaces,    // excludeFaces,
+                    meshFaces,  // meshFaces,
+
+                    inflateFaceID[edgeI],
+                    edgePatchID[edgeI],
+                    edgeZoneID[edgeI],
+                    edgeFlip[edgeI]
+                );
             }
         }
     }
 
 
 
-    // Now hopefully every boundary edge has a side patch. Check
+    // Now hopefully every boundary edge has a edge patch. Check
     if (debug)
     {
         forAll(edgeFaces, edgeI)
         {
-            if (edgeFaces[edgeI].size() == 1 && sidePatchID[edgeI] == -1)
+            if (edgeFaces[edgeI].size() == 1 && edgePatchID[edgeI] == -1)
             {
                 const edge& e = pp.edges()[edgeI];
-                //FatalErrorIn("addPatchCellLayer::calcSidePatch(..)")
-                WarningIn("addPatchCellLayer::calcSidePatch(..)")
-                    << "Have no sidePatchID for edge " << edgeI << " points "
+                //FatalErrorIn("addPatchCellLayer::calcExtrudeInfo(..)")
+                WarningIn("addPatchCellLayer::calcExtrudeInfo(..)")
+                    << "Have no edgePatchID for edge " << edgeI << " points "
                     << pp.points()[pp.meshPoints()[e[0]]]
                     << pp.points()[pp.meshPoints()[e[1]]]
                     //<< abort(FatalError);
@@ -718,15 +866,15 @@ void Foam::addPatchCellLayer::calcSidePatch
 
 
 
-    // Now we have sidepatch see if we have patchface or edge to inflate
-    // from.
+    // Pass3: for any faces set in pass1 see if we can find a processor face
+    //        to inherit from (we only have a patch, not a patch face)
     forAll(edgeFaces, edgeI)
     {
         if
         (
             edgeFaces[edgeI].size() == 1
-         && sidePatchID[edgeI] != -1
-         && inflateFaceI[edgeI] == -1
+         && edgePatchID[edgeI] != -1
+         && inflateFaceID[edgeI] == -1
         )
         {
             // 1. Do we have a boundary face to inflate from
@@ -745,35 +893,116 @@ void Foam::addPatchCellLayer::calcSidePatch
             {
                 label faceI = meshFaces[k];
 
-                if (faceI != myFaceI)
+                if (faceI != myFaceI && !mesh.isInternalFace(faceI))
                 {
-                    if (mesh.isInternalFace(faceI))
+                    if (patches.whichPatch(faceI) == edgePatchID[edgeI])
                     {
-                        inflateEdgeI[edgeI] = meshEdgeI;
-                    }
-                    else
-                    {
-                        if (patches.whichPatch(faceI) == sidePatchID[edgeI])
-                        {
-                            setFaceProps
-                            (
-                                mesh,
-                                faceI,
-
-                                sidePatchID[edgeI],
-                                sideZoneID[edgeI],
-                                sideFlip[edgeI]
-                            );
-                            inflateFaceI[edgeI] = faceI;
-                            inflateEdgeI[edgeI] = -1;
+                        setFaceProps
+                        (
+                            mesh,
+                            pp,
+                            edgeI,
+                            faceI,
 
-                            break;
-                        }
+                            edgePatchID[edgeI],
+                            edgeZoneID[edgeI],
+                            edgeFlip[edgeI],
+                            inflateFaceID[edgeI]
+                        );
+                        break;
                     }
                 }
             }
         }
     }
+
+
+
+    // Sync all data:
+    // - edgePatchID : might be local in case of processor patch. Do not
+    //   sync for now
+    // - inflateFaceID: local. Do not sync
+    // - edgeZoneID : global. sync.
+    // - edgeFlip : global. sync.
+
+    if (Pstream::parRun())
+    {
+        const globalMeshData& gd = mesh.globalData();
+        const indirectPrimitivePatch& cpp = gd.coupledPatch();
+
+        labelList patchEdges;
+        labelList coupledEdges;
+        PackedBoolList sameEdgeOrientation;
+        PatchTools::matchEdges
+        (
+            pp,
+            cpp,
+            patchEdges,
+            coupledEdges,
+            sameEdgeOrientation
+        );
+
+        // Convert data on pp edges to data on coupled patch
+        labelList cppEdgeZoneID(cpp.nEdges(), -1);
+        boolList cppEdgeFlip(cpp.nEdges(), false);
+        forAll(coupledEdges, i)
+        {
+            label cppEdgeI = coupledEdges[i];
+            label ppEdgeI = patchEdges[i];
+
+            cppEdgeZoneID[cppEdgeI] = edgeZoneID[ppEdgeI];
+            if (sameEdgeOrientation[i])
+            {
+                cppEdgeFlip[cppEdgeI] = edgeFlip[ppEdgeI];
+            }
+            else
+            {
+                cppEdgeFlip[cppEdgeI] = !edgeFlip[ppEdgeI];
+            }
+        }
+
+        // Sync
+        const globalIndexAndTransform& git = gd.globalTransforms();
+        const mapDistribute& edgeMap = gd.globalEdgeSlavesMap();
+
+        globalMeshData::syncData
+        (
+            cppEdgeZoneID,
+            gd.globalEdgeSlaves(),
+            gd.globalEdgeTransformedSlaves(),
+            edgeMap,
+            git,
+            maxEqOp<label>(),
+            dummyTransform()
+        );
+        globalMeshData::syncData
+        (
+            cppEdgeFlip,
+            gd.globalEdgeSlaves(),
+            gd.globalEdgeTransformedSlaves(),
+            edgeMap,
+            git,
+            andEqOp<bool>(),
+            dummyTransform()
+        );
+
+        // Convert data on coupled edges to pp edges
+        forAll(coupledEdges, i)
+        {
+            label cppEdgeI = coupledEdges[i];
+            label ppEdgeI = patchEdges[i];
+
+            edgeZoneID[ppEdgeI] = cppEdgeZoneID[cppEdgeI];
+            if (sameEdgeOrientation[i])
+            {
+                edgeFlip[ppEdgeI] = cppEdgeFlip[cppEdgeI];
+            }
+            else
+            {
+                edgeFlip[ppEdgeI] = !cppEdgeFlip[cppEdgeI];
+            }
+        }
+    }
 }
 
 
@@ -783,7 +1012,12 @@ void Foam::addPatchCellLayer::setRefinement
     const labelListList& globalEdgeFaces,
     const scalarField& expansionRatio,
     const indirectPrimitivePatch& pp,
-    const labelList& sidePatchID,
+
+    const labelList& edgePatchID,
+    const labelList& edgeZoneID,
+    const boolList& edgeFlip,
+    const labelList& inflateFaceID,
+
     const labelList& exposedPatchID,
     const labelList& nFaceLayers,
     const labelList& nPointLayers,
@@ -865,7 +1099,10 @@ void Foam::addPatchCellLayer::setRefinement
                     << e.line(pp.localPoints())
                     << " which is non-manifold (has "
                     << globalEdgeFaces[edgeI].size()
-                    << " faces using it)"
+                    << " local or coupled faces using it)"
+                    << " of which "
+                    << pp.edgeFaces()[edgeI].size()
+                    << " local"
                     << abort(FatalError);
             }
         }
@@ -1741,13 +1978,25 @@ void Foam::addPatchCellLayer::setRefinement
                             ef
                         );
 
+                        // Because we walk in order of patch face and in order
+                        // of face edges so face orientation will be opposite
+                        // that of the patch edge
+                        bool zoneFlip = false;
+                        if (edgeZoneID[startEdgeI] != -1)
+                        {
+                            zoneFlip = !edgeFlip[startEdgeI];
+                        }
+
                         addSideFace
                         (
                             pp,
                             addedCells,
 
                             newFace,                // vertices of new face
-                            sidePatchID[startEdgeI],// -1 or patch for face
+                            edgePatchID[startEdgeI],// -1 or patch for face
+                            edgeZoneID[startEdgeI],
+                            zoneFlip,
+                            inflateFaceID[startEdgeI],
 
                             patchFaceI,
                             nbrFaceI,
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H
index 158af5e1d8ec77574720b2b300dddc0175f0cfb0..5413ea77fd671a680dce94b6af6232b5c63ba42b 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/addPatchCellLayer.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -220,6 +220,9 @@ class addPatchCellLayer
 
             const face& newFace,
             const label newPatchID,
+            const label newZoneI,
+            const bool newFlip,
+            const label inflateFaceI,
 
             const label ownFaceI,
             const label nbrFaceI,
@@ -243,6 +246,39 @@ class addPatchCellLayer
             bool&
         );
 
+        //- Extract properties from mesh face in pp edge ordering
+        static void setFaceProps
+        (
+            const polyMesh& mesh,
+            const indirectPrimitivePatch& pp,
+            const label ppEdgeI,
+            const label faceI,
+
+            label& patchI,
+            label& zoneI,
+            bool& zoneFlip,
+            label& inflateFaceI
+        );
+
+        //- Find internal or boundary face to get extrude properties
+        //  from. zoneFlip consistent with ppEdge ordering
+        static void findZoneFace
+        (
+            const bool useInternalFaces,
+            const bool useBoundaryFaces,
+
+            const polyMesh& mesh,
+            const indirectPrimitivePatch& pp,
+            const label ppEdgeI,
+            const UIndirectList<label>& excludeFaces,
+            const labelList& meshFaces,
+
+            label& inflateFaceI,
+            label& patchI,
+            label& zoneI,
+            bool& zoneFlip
+        );
+
         //- Disallow default bitwise copy construct
         addPatchCellLayer(const addPatchCellLayer&);
 
@@ -303,22 +339,37 @@ public:
                 const indirectPrimitivePatch& pp
             );
 
-            //- Boundary edges get extruded into boundary faces. Determine patch
-            //  for these faces. This might be a to-be-created processor patch
-            //  (patchI >= mesh.boundaryMesh().size()) in which case the
-            //  nbrProcToPatch, patchToNbrProc give the correspondence. nPatches
-            //  is the new number of patches.
-            static void calcSidePatch
+            //- Determine extrude information per patch edge:
+            //  - zoneID, zoneFlip :
+            //      picks one of the faces that connects to
+            //      the edge. For boundary edge only looks
+            //      at boundary faces. For internal edge it looks at internal
+            //      faces only (zoneFromAnyFace = false) or at any face
+            //      (zoneFromAnyFace = true). zoneFlip is consistent with
+            //      ordering of pp edge.
+            //      Face selected gets stored in inflateFaceID
+            //  - patchID :
+            //      get patch from any boundary face connected to the
+            //      edge. The patch might be a to-be-created processor patch
+            //      (patchI >= mesh.boundaryMesh().size()) in which case the
+            //      nbrProcToPatch, patchToNbrProc give the correspondence.
+            //      nPatches is the new number of patches.
+            static void calcExtrudeInfo
             (
+                const bool zoneFromAnyFace,
+
                 const polyMesh&,
                 const globalIndex& globalFaces,
                 const labelListList& globalEdgeFaces,
                 const indirectPrimitivePatch& pp,
 
-                labelList& sidePatchID,
+                labelList& edgePatchID,     // if extruding a patch edge
                 label& nPatches,
                 Map<label>& nbrProcToPatch,
-                Map<label>& patchToNbrProc
+                Map<label>& patchToNbrProc,
+                labelList& edgeZoneID,
+                boolList& edgeFlip,
+                labelList& inflateFaceID
             );
 
             //- Play commands into polyTopoChange to create layers on top
@@ -347,7 +398,12 @@ public:
                 const labelListList& globalEdgeFaces,
                 const scalarField& expansionRatio,
                 const indirectPrimitivePatch& pp,
+
                 const labelList& sidePatchID,
+                const labelList& sideZoneID,
+                const boolList& sideFlip,
+                const labelList& inflateFaceID,
+
                 const labelList& exposedPatchID,
                 const labelList& nFaceLayers,
                 const labelList& nPointLayers,
@@ -374,7 +430,12 @@ public:
                     globalEdgeFaces,
                     scalarField(pp.nPoints(), 1.0),     // expansion ration
                     pp,
+
                     sidePatchID,
+                    labelList(pp.nEdges(), -1),         // zoneID
+                    boolList(pp.nEdges(), false),       // zoneFlip
+                    labelList(pp.nEdges(), -1),         // inflateFaceID
+
                     labelList(0),
                     labelList(pp.size(), nLayers),      // nFaceLayers
                     labelList(pp.nPoints(), nLayers),   // nPointLayers
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.C b/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.C
index 263afe869fe46d96e28591fe3c74f6ade9d44bdc..38dd6908e4086c675d91997864f1b7167332be0c 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.C
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -795,7 +795,7 @@ Foam::label Foam::hexRef8::findLevel
 
 
 // Gets cell level such that the face has four points <= level.
-Foam::label Foam::hexRef8::getAnchorLevel(const label faceI) const
+Foam::label Foam::hexRef8::faceLevel(const label faceI) const
 {
     const face& f = mesh_.faces()[faceI];
 
@@ -3519,7 +3519,7 @@ Foam::labelListList Foam::hexRef8::setRefinement
 
     for (label faceI = 0; faceI < mesh_.nFaces(); faceI++)
     {
-        faceAnchorLevel[faceI] = getAnchorLevel(faceI);
+        faceAnchorLevel[faceI] = faceLevel(faceI);
     }
 
     // -1  : no need to split face
diff --git a/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.H b/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.H
index 58dfc8559bdf4314f228557ecbaec0c3bf5773ef..883681c789f95b42b2547be20771c4d77bf730de 100644
--- a/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.H
+++ b/src/dynamicMesh/polyTopoChange/polyTopoChange/hexRef8.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -411,7 +411,7 @@ public:
         // Refinement
 
             //- Gets level such that the face has four points <= level.
-            label getAnchorLevel(const label faceI) const;
+            label faceLevel(const label faceI) const;
 
             //- Given valid mesh and current cell level and proposed
             //  cells to refine calculate any clashes (due to 2:1) and return
diff --git a/src/mesh/autoMesh/Make/files b/src/mesh/autoMesh/Make/files
index 2bc9cd80a17ee6186e46e895250e16ba14cfcf35..081d70afbd52185b77011cfa13f68f8f07fea44e 100644
--- a/src/mesh/autoMesh/Make/files
+++ b/src/mesh/autoMesh/Make/files
@@ -2,7 +2,6 @@ autoHexMesh             = autoHexMesh
 autoHexMeshDriver       = $(autoHexMesh)/autoHexMeshDriver
 
 $(autoHexMeshDriver)/autoLayerDriver.C
-/* $(autoHexMeshDriver)/autoLayerDriverShrink.C */
 $(autoHexMeshDriver)/autoSnapDriver.C
 $(autoHexMeshDriver)/autoSnapDriverFeature.C
 $(autoHexMeshDriver)/autoRefineDriver.C
@@ -10,7 +9,6 @@ $(autoHexMeshDriver)/autoRefineDriver.C
 $(autoHexMeshDriver)/layerParameters/layerParameters.C
 $(autoHexMeshDriver)/refinementParameters/refinementParameters.C
 $(autoHexMeshDriver)/snapParameters/snapParameters.C
-$(autoHexMeshDriver)/pointData/pointData.C
 
 $(autoHexMesh)/meshRefinement/meshRefinementBaffles.C
 $(autoHexMesh)/meshRefinement/meshRefinement.C
@@ -30,7 +28,10 @@ meshMover = $(autoHexMesh)/externalDisplacementMeshMover
 $(meshMover)/displacementMeshMoverMotionSolver.C
 $(meshMover)/externalDisplacementMeshMover.C
 $(meshMover)/medialAxisMeshMover.C
+$(meshMover)/displacementMotionSolverMeshMover.C
+/* $(meshMover)/pointSmoothingMeshMover.C */
 $(meshMover)/zeroFixedValue/zeroFixedValuePointPatchFields.C
+$(meshMover)/fieldSmoother/fieldSmoother.C
 
 LIB = $(FOAM_LIBBIN)/libautoMesh
 
diff --git a/src/mesh/autoMesh/Make/options b/src/mesh/autoMesh/Make/options
index d37a26a9dd460742d9203623e4ddf6f69a4c99de..88577d6c825c3afabd897d9744e2fd8a43f8b621 100644
--- a/src/mesh/autoMesh/Make/options
+++ b/src/mesh/autoMesh/Make/options
@@ -18,4 +18,5 @@ LIB_LIBS = \
     -ledgeMesh \
     -lsurfMesh \
     -ltriSurface \
+    -lfvMotionSolvers \
     -ldistributed
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.C
index 4999a16d3ca2e9cccaa80578038ef16ff72c7e0f..a7e30cb36cd0b2064fabd98d9ce01995765858e1 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.C
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -55,9 +55,7 @@ Description
 #include "cyclicSlipPointPatchFields.H"
 #include "fixedValueFvPatchFields.H"
 #include "localPointRegion.H"
-
 #include "externalDisplacementMeshMover.H"
-#include "medialAxisMeshMover.H"
 #include "scalarIOField.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -973,34 +971,42 @@ void Foam::autoLayerDriver::determineSidePatches
     const labelListList& edgeGlobalFaces,
     const indirectPrimitivePatch& pp,
 
-    labelList& sidePatchID
+    labelList& edgePatchID,
+    labelList& edgeZoneID,
+    boolList& edgeFlip,
+    labelList& inflateFaceID
 )
 {
     // Sometimes edges-to-be-extruded are on more than 2 processors.
     // Work out which 2 hold the faces to be extruded and thus which procpatch
-    // the side-face should be in. As an additional complication this might
+    // the edge-face should be in. As an additional complication this might
     // mean that 2 procesors that were only edge-connected now suddenly need
     // to become face-connected i.e. have a processor patch between them.
 
     fvMesh& mesh = meshRefiner_.mesh();
 
-    // Determine sidePatchID. Any additional processor boundary gets added to
+    // Determine edgePatchID. Any additional processor boundary gets added to
     // patchToNbrProc,nbrProcToPatch and nPatches gets set to the new number
     // of patches.
     label nPatches;
     Map<label> nbrProcToPatch;
     Map<label> patchToNbrProc;
-    addPatchCellLayer::calcSidePatch
+    addPatchCellLayer::calcExtrudeInfo
     (
+        true,           // zoneFromAnyFace
+
         mesh,
         globalFaces,
         edgeGlobalFaces,
         pp,
 
-        sidePatchID,
+        edgePatchID,
         nPatches,
         nbrProcToPatch,
-        patchToNbrProc
+        patchToNbrProc,
+        edgeZoneID,
+        edgeFlip,
+        inflateFaceID
     );
 
     label nOldPatches = mesh.boundaryMesh().size();
@@ -1012,7 +1018,7 @@ void Foam::autoLayerDriver::determineSidePatches
     if (nAdded > 0)
     {
         // We might not add patches in same order as in patchToNbrProc
-        // so prepare to renumber sidePatchID
+        // so prepare to renumber edgePatchID
         Map<label> wantedToAddedPatch;
 
         for (label patchI = nOldPatches; patchI < nPatches; patchI++)
@@ -1046,14 +1052,14 @@ void Foam::autoLayerDriver::determineSidePatches
             wantedToAddedPatch.insert(patchI, procPatchI);
         }
 
-        // Renumber sidePatchID
-        forAll(sidePatchID, i)
+        // Renumber edgePatchID
+        forAll(edgePatchID, i)
         {
-            label patchI = sidePatchID[i];
+            label patchI = edgePatchID[i];
             Map<label>::const_iterator fnd = wantedToAddedPatch.find(patchI);
             if (fnd != wantedToAddedPatch.end())
             {
-                sidePatchID[i] = fnd();
+                edgePatchID[i] = fnd();
             }
         }
 
@@ -2427,6 +2433,67 @@ Foam::label Foam::autoLayerDriver::countExtrusion
 }
 
 
+Foam::List<Foam::labelPair> Foam::autoLayerDriver::getBafflesOnAddedMesh
+(
+    const polyMesh& mesh,
+    const labelList& newToOldFaces,
+    const List<labelPair>& baffles
+)
+{
+    // The problem is that the baffle faces are now inside the
+    // mesh (addPatchCellLayer modifies original boundary faces and
+    // adds new ones. So 2 pass:
+    // - find the boundary face for all faces originating from baffle
+    // - use the boundary face for the new baffles
+
+    Map<label> baffleSet(4*baffles.size());
+    forAll(baffles, baffleI)
+    {
+        baffleSet.insert(baffles[baffleI][0], baffleI);
+        baffleSet.insert(baffles[baffleI][1], baffleI);
+    }
+
+
+    List<labelPair> newBaffles(baffles.size(), labelPair(-1, -1));
+    for
+    (
+        label faceI = mesh.nInternalFaces();
+        faceI < mesh.nFaces();
+        faceI++
+    )
+    {
+        label oldFaceI = newToOldFaces[faceI];
+
+        Map<label>::const_iterator faceFnd = baffleSet.find(oldFaceI);
+        if (faceFnd != baffleSet.end())
+        {
+            label baffleI = faceFnd();
+            labelPair& p = newBaffles[baffleI];
+            if (p[0] == -1)
+            {
+                p[0] = faceI;
+            }
+            else if (p[1] == -1)
+            {
+                p[1] = faceI;
+            }
+            else
+            {
+                FatalErrorIn("addLayers(..)")
+                    << "Problem:" << faceI << " at:"
+                    << mesh.faceCentres()[faceI]
+                    << " is on same baffle as " << p[0]
+                    << " at:" << mesh.faceCentres()[p[0]]
+                    << " and " << p[1]
+                    << " at:" << mesh.faceCentres()[p[1]]
+                    << exit(FatalError);
+            }
+        }
+    }
+    return newBaffles;
+}
+
+
 // Collect layer faces and layer cells into mesh fields for ease of handling
 void Foam::autoLayerDriver::getLayerCellsFaces
 (
@@ -2457,6 +2524,7 @@ void Foam::autoLayerDriver::getLayerCellsFaces
 
         if (layer.size())
         {
+            // Leave out original internal face
             forAll(added, i)
             {
                 cellNLayers[added[i]] = layer.size()-1;
@@ -2577,70 +2645,84 @@ void Foam::autoLayerDriver::printLayerData
 }
 
 
-bool Foam::autoLayerDriver::writeLayerData
+bool Foam::autoLayerDriver::writeLayerSets
 (
     const fvMesh& mesh,
-    const labelList& patchIDs,
     const labelList& cellNLayers,
-    const scalarField& faceWantedThickness,
     const scalarField& faceRealThickness
 ) const
 {
     bool allOk = true;
-
-    if (meshRefinement::writeLevel() & meshRefinement::WRITELAYERSETS)
     {
+        label nAdded = 0;
+        forAll(cellNLayers, cellI)
         {
-            label nAdded = 0;
-            forAll(cellNLayers, cellI)
+            if (cellNLayers[cellI] > 0)
             {
-                if (cellNLayers[cellI] > 0)
-                {
-                    nAdded++;
-                }
+                nAdded++;
             }
-            cellSet addedCellSet(mesh, "addedCells", nAdded);
-            forAll(cellNLayers, cellI)
+        }
+        cellSet addedCellSet(mesh, "addedCells", nAdded);
+        forAll(cellNLayers, cellI)
+        {
+            if (cellNLayers[cellI] > 0)
             {
-                if (cellNLayers[cellI] > 0)
-                {
-                    addedCellSet.insert(cellI);
-                }
+                addedCellSet.insert(cellI);
             }
-            addedCellSet.instance() = meshRefiner_.timeName();
-            Info<< "Writing "
-                << returnReduce(addedCellSet.size(), sumOp<label>())
-                << " added cells to cellSet "
-                << addedCellSet.name() << endl;
-            bool ok = addedCellSet.write();
-            allOk = allOk & ok;
         }
+        addedCellSet.instance() = meshRefiner_.timeName();
+        Info<< "Writing "
+            << returnReduce(addedCellSet.size(), sumOp<label>())
+            << " added cells to cellSet "
+            << addedCellSet.name() << endl;
+        bool ok = addedCellSet.write();
+        allOk = allOk && ok;
+    }
+    {
+        label nAdded = 0;
+        for (label faceI = 0; faceI < mesh.nInternalFaces(); faceI++)
         {
-            label nAdded = 0;
-            for (label faceI = 0; faceI < mesh.nInternalFaces(); faceI++)
+            if (faceRealThickness[faceI] > 0)
             {
-                if (faceRealThickness[faceI] > 0)
-                {
-                    nAdded++;
-                }
+                nAdded++;
             }
+        }
 
-            faceSet layerFacesSet(mesh, "layerFaces", nAdded);
-            for (label faceI = 0; faceI < mesh.nInternalFaces(); faceI++)
+        faceSet layerFacesSet(mesh, "layerFaces", nAdded);
+        for (label faceI = 0; faceI < mesh.nInternalFaces(); faceI++)
+        {
+            if (faceRealThickness[faceI] > 0)
             {
-                if (faceRealThickness[faceI] > 0)
-                {
-                    layerFacesSet.insert(faceI);
-                }
+                layerFacesSet.insert(faceI);
             }
-            layerFacesSet.instance() = meshRefiner_.timeName();
-            Info<< "Writing "
-                << returnReduce(layerFacesSet.size(), sumOp<label>())
-                << " faces inside added layer to faceSet "
-                << layerFacesSet.name() << endl;
-            bool ok = layerFacesSet.write();
-            allOk = allOk & ok;
         }
+        layerFacesSet.instance() = meshRefiner_.timeName();
+        Info<< "Writing "
+            << returnReduce(layerFacesSet.size(), sumOp<label>())
+            << " faces inside added layer to faceSet "
+            << layerFacesSet.name() << endl;
+        bool ok = layerFacesSet.write();
+        allOk = allOk && ok;
+    }
+    return allOk;
+}
+
+
+bool Foam::autoLayerDriver::writeLayerData
+(
+    const fvMesh& mesh,
+    const labelList& patchIDs,
+    const labelList& cellNLayers,
+    const scalarField& faceWantedThickness,
+    const scalarField& faceRealThickness
+) const
+{
+    bool allOk = true;
+
+    if (meshRefinement::writeLevel() & meshRefinement::WRITELAYERSETS)
+    {
+        bool ok = writeLayerSets(mesh, cellNLayers, faceRealThickness);
+        allOk = allOk && ok;
     }
 
     if (meshRefinement::writeLevel() & meshRefinement::WRITELAYERFIELDS)
@@ -2663,6 +2745,10 @@ bool Foam::autoLayerDriver::writeLayerData
                 dimensionedScalar("zero", dimless, 0),
                 fixedValueFvPatchScalarField::typeName
             );
+            forAll(fld, cellI)
+            {
+                fld[cellI] = cellNLayers[cellI];
+            }
             const polyBoundaryMesh& pbm = mesh.boundaryMesh();
             forAll(patchIDs, i)
             {
@@ -2679,7 +2765,7 @@ bool Foam::autoLayerDriver::writeLayerData
             Info<< indent << fld.name() << "    : actual number of layers"
                 << endl;
             bool ok = fld.write();
-            allOk = allOk & ok;
+            allOk = allOk && ok;
         }
         {
             volScalarField fld
@@ -2709,7 +2795,7 @@ bool Foam::autoLayerDriver::writeLayerData
             Info<< indent << fld.name() << "         : overall layer thickness"
                 << endl;
             bool ok = fld.write();
-            allOk = allOk & ok;
+            allOk = allOk && ok;
         }
         {
             volScalarField fld
@@ -2757,23 +2843,11 @@ bool Foam::autoLayerDriver::writeLayerData
                 << " : overall layer thickness (fraction"
                 << " of desired thickness)" << endl;
             bool ok = fld.write();
-            allOk = allOk & ok;
+            allOk = allOk && ok;
         }
         Info<< decrIndent<< endl;
     }
 
-    //if (meshRefinement::outputLevel() & meshRefinement::OUTPUTLAYERINFO)
-    {
-        printLayerData
-        (
-            mesh,
-            patchIDs,
-            cellNLayers,
-            faceWantedThickness,
-            faceRealThickness
-        );
-    }
-
     return allOk;
 }
 
@@ -2803,7 +2877,7 @@ void Foam::autoLayerDriver::mergePatchFacesUndo
 {
     // Clip to 30 degrees. Not helpful!
     //scalar planarAngle = min(30.0, layerParams.featureAngle());
-    scalar planarAngle = layerParams.featureAngle();
+    scalar planarAngle = layerParams.mergePatchFacesAngle();
     scalar minCos = Foam::cos(degToRad(planarAngle));
 
     scalar concaveCos = Foam::cos(degToRad(layerParams.concaveAngle()));
@@ -2814,8 +2888,7 @@ void Foam::autoLayerDriver::mergePatchFacesUndo
         << "    - which are on the same patch" << nl
         << "    - which make an angle < " << planarAngle
         << " degrees"
-        << nl
-        << "      (cos:" << minCos << ')' << nl
+        << " (cos:" << minCos << ')' << nl
         << "    - as long as the resulting face doesn't become concave"
         << " by more than "
         << layerParams.concaveAngle() << " degrees" << nl
@@ -2859,34 +2932,322 @@ void Foam::autoLayerDriver::addLayers
 {
     fvMesh& mesh = meshRefiner_.mesh();
 
+
+    // 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;
-    meshRefiner_.createZoneBaffles
-    (
-        globalToMasterPatch_,
-        globalToSlavePatch_,
-        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.valid())
+        {
+            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);
+            }
+        }
+    }
+
+
 
-    if (debug&meshRefinement::MESH)
+    // 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());
     {
-        const_cast<Time&>(mesh.time())++;
-        Info<< "Writing baffled mesh to time "
-            << meshRefiner_.timeName() << endl;
-        meshRefiner_.write
+        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 pointToDuplicate;
+
+    {
+        // Check outside of baffles for non-manifoldness
+        PackedBoolList duplicatePoint(mesh.nPoints());
+        {
+            // 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.
+            vectorField patchDisp(pp().nPoints(), vector::one);
+            labelList patchNLayers(pp().nPoints(), 0);
+            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
+
+                patchDisp,
+                patchNLayers,
+                extrudeStatus,
+                nIdealTotAddedCells
+            );
+            // Make sure displacement is equal on both sides of coupled patches.
+            syncPatchDisplacement
+            (
+                pp,
+                scalarField(patchDisp.size(), 0.0), //minThickness,
+                patchDisp,
+                patchNLayers,
+                extrudeStatus
+            );
+
+            forAll(extrudeStatus, patchPointI)
+            {
+                if (extrudeStatus[patchPointI] != NOEXTRUDE)
+                {
+                    duplicatePoint[pp().meshPoints()[patchPointI]] = 1;
+                }
+            }
+        }
+
+
+        // Duplicate points only if all points agree
+        syncTools::syncPointList
         (
-            meshRefinement::debugType(debug),
-            meshRefinement::writeType
+            mesh,
+            duplicatePoint,
+            andEqOp<unsigned int>(),    // combine op
+            0u                          // null value
+        );
+        label n = duplicatePoint.count();
+        labelList candidatePoints(n);
+        n = 0;
+        forAll(duplicatePoint, pointI)
+        {
+            if (duplicatePoint[pointI])
+            {
+                candidatePoints[n++] = pointI;
+            }
+        }
+
+        // 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());
+
+            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 && !layerIDs.found(mpI) && !layerIDs.found(spI))
+                {
+                    nonDupZones.append(zoneI);
+                }
+            }
+            nonDupBaffles = meshRefinement::subsetBaffles
             (
-                meshRefinement::writeLevel()
-              | meshRefinement::WRITEMESH
-            ),
-            mesh.time().path()/meshRefiner_.timeName()
+                mesh,
+                nonDupZones,
+                localPointRegion::findDuplicateFacePairs(mesh)
+            );
+        }
+
+
+        const localPointRegion regionSide(mesh, nonDupBaffles, candidatePoints);
+
+        autoPtr<mapPolyMesh> map = meshRefiner_.dupNonManifoldPoints
+        (
+            regionSide
         );
+
+        if (map.valid())
+        {
+            // Store point duplication
+            pointToDuplicate.setSize(mesh.nPoints(), -1);
+
+            const labelList& pointMap = map().pointMap();
+            const labelList& reversePointMap = map().reversePointMap();
+
+            forAll(pointMap, pointI)
+            {
+                label oldPointI = pointMap[pointI];
+                label newMasterPointI = reversePointMap[oldPointI];
+
+                if (newMasterPointI != pointI)
+                {
+                    // Found slave. Mark both master and slave
+                    pointToDuplicate[pointI] = newMasterPointI;
+                    pointToDuplicate[newMasterPointI] = newMasterPointI;
+                }
+            }
+
+            // 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);
+                }
+            }
+
+
+            if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+            {
+                const_cast<Time&>(mesh.time())++;
+                Info<< "Writing point-duplicate mesh to time "
+                    << meshRefiner_.timeName() << endl;
+
+                meshRefiner_.write
+                (
+                    meshRefinement::debugType(debug),
+                    meshRefinement::writeType
+                    (
+                        meshRefinement::writeLevel()
+                      | meshRefinement::WRITEMESH
+                    ),
+                    mesh.time().path()/meshRefiner_.timeName()
+                );
+
+                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]];
+
+                    if (newMasterI != pointI)
+                    {
+                        str.write(linePointRef(p[pointI], p[newMasterI]));
+                    }
+                }
+            }
+        }
     }
 
 
+    // Add layers to patches
+    // ~~~~~~~~~~~~~~~~~~~~~
+
+    // Now we have
+    // - mesh with optional baffles and duplicated points for faceZones
+    //   where layers are to be added
+    // - pointToDuplicate : correspondence for duplicated points
+    // - baffles          : list of pairs of faces
+
+
     autoPtr<indirectPrimitivePatch> pp
     (
         meshRefinement::makePatch
@@ -2916,14 +3277,20 @@ void Foam::autoLayerDriver::addLayers
     // Determine patches for extruded boundary edges. Calculates if any
     // additional processor patches need to be constructed.
 
-    labelList sidePatchID;
+    labelList edgePatchID;
+    labelList edgeZoneID;
+    boolList edgeFlip;
+    labelList inflateFaceID;
     determineSidePatches
     (
         globalFaces,
         edgeGlobalFaces,
         pp,
 
-        sidePatchID
+        edgePatchID,
+        edgeZoneID,
+        edgeFlip,
+        inflateFaceID
     );
 
 
@@ -2945,12 +3312,12 @@ void Foam::autoLayerDriver::addLayers
 
 
     {
-        // Get number of layer per point from number of layers per patch
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        // Get number of layers per point from number of layers per patch
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
         setNumLayers
         (
-            layerParams.numLayers(),    // per patch the num layers
+            numLayers,                  // per patch the num layers
             patchIDs,                   // patches that are being moved
             pp,                         // indirectpatch for all faces moving
 
@@ -3077,41 +3444,6 @@ void Foam::autoLayerDriver::addLayers
 
 
 
-    // Overall displacement field
-    pointVectorField displacement
-    (
-        makeLayerDisplacementField
-        (
-            pointMesh::New(mesh),
-            layerParams.numLayers()
-        )
-    );
-
-    // Allocate run-time selectable mesh mover
-    autoPtr<externalDisplacementMeshMover> medialAxisMoverPtr;
-    {
-        // 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());
-
-        // Take over patchDisp as boundary conditions on displacement
-        // pointVectorField
-        medialAxisMoverPtr = externalDisplacementMeshMover::New
-        (
-            layerParams.meshShrinker(),
-            combinedDict,
-            baffles,
-            displacement
-        );
-    }
-
-
-    // Saved old points
-    pointField oldPoints(mesh.points());
-
     // Current set of topology changes. (changing mesh clears out
     // polyTopoChange)
     polyTopoChange savedMeshMod(mesh.boundaryMesh().size());
@@ -3127,422 +3459,752 @@ void Foam::autoLayerDriver::addLayers
     }
 
 
-    for (label iteration = 0; iteration < layerParams.nLayerIter(); iteration++)
     {
-        Info<< nl
-            << "Layer addition iteration " << iteration << nl
-            << "--------------------------" << endl;
-
-
-        // Unset the extrusion at the pp.
-        const dictionary& meshQualityDict =
+        // Overall displacement field
+        pointVectorField displacement
         (
-            iteration < layerParams.nRelaxedIter()
-          ? motionDict
-          : motionDict.subDict("relaxed")
+            makeLayerDisplacementField
+            (
+                pointMesh::New(mesh),
+                numLayers
+            )
         );
 
-        if (iteration >= layerParams.nRelaxedIter())
+        // Allocate run-time selectable mesh mover
+        autoPtr<externalDisplacementMeshMover> medialAxisMoverPtr;
         {
-            Info<< "Switched to relaxed meshQuality constraints." << endl;
-        }
-
-
+            // 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());
 
-        // Make sure displacement is equal on both sides of coupled patches.
-        syncPatchDisplacement
-        (
-            pp,
-            minThickness,
-            patchDisp,
-            patchNLayers,
-            extrudeStatus
-        );
+            const List<labelPair> internalBaffles
+            (
+                meshRefinement::subsetBaffles
+                (
+                    mesh,
+                    internalFaceZones,
+                    localPointRegion::findDuplicateFacePairs(mesh)
+                )
+            );
 
-        // Displacement acc. to pointnormals
-        getPatchDisplacement
-        (
-            pp,
-            thickness,
-            minThickness,
-            patchDisp,
-            patchNLayers,
-            extrudeStatus
-        );
+            // Take over patchDisp as boundary conditions on displacement
+            // pointVectorField
+            medialAxisMoverPtr = externalDisplacementMeshMover::New
+            (
+                layerParams.meshShrinker(),
+                combinedDict,
+                internalBaffles,
+                displacement
+            );
+        }
 
-        // Shrink mesh by displacement value first.
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+        // Saved old points
+        const pointField oldPoints(mesh.points());
+
+        for
+        (
+            label iteration = 0;
+            iteration < layerParams.nLayerIter();
+            iteration++
+        )
         {
-            pointField oldPatchPos(pp().localPoints());
+            Info<< nl
+                << "Layer addition iteration " << iteration << nl
+                << "--------------------------" << endl;
+
 
-            // Take over patchDisp into pointDisplacement field and
-            // adjust both for multi-patch constraints
-            motionSmootherAlgo::setDisplacement
+            // Unset the extrusion at the pp.
+            const dictionary& meshQualityDict =
             (
-                patchIDs,
-                pp,
-                patchDisp,
-                displacement
+                iteration < layerParams.nRelaxedIter()
+              ? motionDict
+              : motionDict.subDict("relaxed")
             );
 
+            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());
 
-            labelList checkFaces(identity(mesh.nFaces()));
-            medialAxisMoverPtr().move
+            // Make sure displacement is equal on both sides of coupled patches.
+            syncPatchDisplacement
             (
-                combinedDict,
-                nAllowableErrors,
-                checkFaces
+                pp,
+                minThickness,
+                patchDisp,
+                patchNLayers,
+                extrudeStatus
             );
 
-            pp().movePoints(mesh.points());
+            // Displacement acc. to pointnormals
+            getPatchDisplacement
+            (
+                pp,
+                thickness,
+                minThickness,
+                patchDisp,
+                patchNLayers,
+                extrudeStatus
+            );
 
-            // Update patchDisp (since not all might have been honoured)
-            patchDisp = oldPatchPos - pp().localPoints();
-        }
+            // Shrink mesh by displacement value first.
+            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-        // 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
-        );
+            {
+                const pointField oldPatchPos(pp().localPoints());
 
+                // We have patchDisp which is the outwards pointing
+                // extrusion distance. Convert into an inwards pointing
+                // shrink distance
+                patchDisp = -patchDisp;
 
-        // Dump to .obj file for debugging.
-        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
-        {
-            dumpDisplacement
+                // Take over patchDisp into pointDisplacement field and
+                // adjust both for multi-patch constraints
+                motionSmootherAlgo::setDisplacement
+                (
+                    patchIDs,
+                    pp,
+                    patchDisp,
+                    displacement
+                );
+
+
+                // 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());
+
+                labelList checkFaces(identity(mesh.nFaces()));
+                medialAxisMoverPtr().move
+                (
+                    combinedDict,
+                    nAllowableErrors,
+                    checkFaces
+                );
+
+                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
             (
-                mesh.time().path()/"layer_" + meshRefiner_.timeName(),
-                pp(),
+                globalFaces,
+                edgeGlobalFaces,
+                pp,
+                minThickness,
+                dummySet,
                 patchDisp,
+                patchNLayers,
                 extrudeStatus
             );
 
-            const_cast<Time&>(mesh.time())++;
-            Info<< "Writing shrunk mesh to time "
-                << meshRefiner_.timeName() << endl;
 
-            // See comment in autoSnapDriver why we should not remove meshPhi
-            // using mesh.clearOut().
+            // Dump to .obj file for debugging.
+            if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
+            {
+                dumpDisplacement
+                (
+                    mesh.time().path()/"layer_" + meshRefiner_.timeName(),
+                    pp(),
+                    patchDisp,
+                    extrudeStatus
+                );
+
+                const_cast<Time&>(mesh.time())++;
+                Info<< "Writing shrunk mesh to time "
+                    << meshRefiner_.timeName() << endl;
 
-            meshRefiner_.write
+                // See comment in autoSnapDriver why we should not remove
+                // meshPhi using mesh.clearOut().
+
+                meshRefiner_.write
+                (
+                    meshRefinement::debugType(debug),
+                    meshRefinement::writeType
+                    (
+                        meshRefinement::writeLevel()
+                      | meshRefinement::WRITEMESH
+                    ),
+                    mesh.time().path()/meshRefiner_.timeName()
+                );
+            }
+
+
+            // Mesh topo change engine. Insert current mesh.
+            polyTopoChange meshMod(mesh);
+
+            // Grow layer of cells on to patch. Handles zero sized displacement.
+            addPatchCellLayer addLayer(mesh);
+
+            // Determine per point/per face number of layers to extrude. Also
+            // handles the slow termination of layers when going switching
+            // layers
+
+            labelList nPatchPointLayers(pp().nPoints(), -1);
+            labelList nPatchFaceLayers(pp().size(), -1);
+            setupLayerInfoTruncation
             (
-                meshRefinement::debugType(debug),
-                meshRefinement::writeType
+                pp,
+                patchNLayers,
+                extrudeStatus,
+                layerParams.nBufferCellsNoExtrude(),
+                nPatchPointLayers,
+                nPatchFaceLayers
+            );
+
+            // Calculate displacement for final layer for addPatchLayer.
+            // (layer of cells next to the original mesh)
+            vectorField finalDisp(patchNLayers.size(), vector::zero);
+
+            forAll(nPatchPointLayers, i)
+            {
+                scalar ratio = layerParams.finalLayerThicknessRatio
                 (
-                    meshRefinement::writeLevel()
-                  | meshRefinement::WRITEMESH
-                ),
-                mesh.time().path()/meshRefiner_.timeName()
+                    nPatchPointLayers[i],
+                    expansionRatio[i]
+                );
+                finalDisp[i] = ratio*patchDisp[i];
+            }
+
+
+            const scalarField invExpansionRatio(1.0/expansionRatio);
+
+            // Add topo regardless of whether extrudeStatus is extruderemove.
+            // Not add layer if patchDisp is zero.
+            addLayer.setRefinement
+            (
+                globalFaces,
+                edgeGlobalFaces,
+
+                invExpansionRatio,
+                pp(),
+
+                edgePatchID,    // boundary patch for extruded boundary edges
+                edgeZoneID,     // zone for extruded edges
+                edgeFlip,
+                inflateFaceID,
+
+
+                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)
+            {
+                const_cast<Time&>(mesh.time())++;
+            }
 
-        // Mesh topo change engine
-        polyTopoChange meshMod(mesh);
+            // Store mesh changes for if mesh is correct.
+            savedMeshMod = meshMod;
 
-        // Grow layer of cells on to patch. Handles zero sized displacement.
-        addPatchCellLayer addLayer(mesh);
 
-        // Determine per point/per face number of layers to extrude. Also
-        // handles the slow termination of layers when going switching layers
+            // With the stored topo changes we create a new mesh so we can
+            // undo if neccesary.
 
-        labelList nPatchPointLayers(pp().nPoints(), -1);
-        labelList nPatchFaceLayers(pp().size(), -1);
-        setupLayerInfoTruncation
-        (
-            pp,
-            patchNLayers,
-            extrudeStatus,
-            layerParams.nBufferCellsNoExtrude(),
-            nPatchPointLayers,
-            nPatchFaceLayers
-        );
+            autoPtr<fvMesh> newMeshPtr;
+            autoPtr<mapPolyMesh> map = meshMod.makeMesh
+            (
+                newMeshPtr,
+                IOobject
+                (
+                    //mesh.name()+"_layer",
+                    mesh.name(),
+                    static_cast<polyMesh&>(mesh).instance(),
+                    mesh.time(),  // register with runTime
+                    IOobject::NO_READ,
+                    static_cast<polyMesh&>(mesh).writeOpt()
+                ),              // io params from original mesh but new name
+                mesh,           // original mesh
+                true            // parallel sync
+            );
+            fvMesh& newMesh = newMeshPtr();
 
-        // Calculate displacement for final layer for addPatchLayer.
-        // (layer of cells next to the original mesh)
-        vectorField finalDisp(patchNLayers.size(), vector::zero);
+            //?neccesary? Update fields
+            newMesh.updateMesh(map);
 
-        forAll(nPatchPointLayers, i)
-        {
-            scalar ratio = layerParams.finalLayerThicknessRatio
+            newMesh.setInstance(meshRefiner_.timeName());
+
+            // Update numbering on addLayer:
+            // - cell/point labels to be newMesh.
+            // - patchFaces to remain in oldMesh order.
+            addLayer.updateMesh
             (
-                nPatchPointLayers[i],
-                expansionRatio[i]
+                map,
+                identity(pp().size()),
+                identity(pp().nPoints())
             );
-            finalDisp[i] = ratio*patchDisp[i];
-        }
 
+            // Collect layer faces and cells for outside loop.
+            getLayerCellsFaces
+            (
+                newMesh,
+                addLayer,
+                avgPointData(pp, mag(patchDisp))(), // current thickness
 
-        const scalarField invExpansionRatio(1.0/expansionRatio);
+                cellNLayers,
+                faceRealThickness
+            );
 
-        // Add topo regardless of whether extrudeStatus is extruderemove.
-        // Not add layer if patchDisp is zero.
-        addLayer.setRefinement
-        (
-            globalFaces,
-            edgeGlobalFaces,
 
-            invExpansionRatio,
-            pp(),
-            sidePatchID,        // boundary patch for extruded boundary edges
-            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
-        );
+            // Count number of added cells
+            label nAddedCells = 0;
+            forAll(cellNLayers, cellI)
+            {
+                if (cellNLayers[cellI] > 0)
+                {
+                    nAddedCells++;
+                }
+            }
 
-        if (debug)
-        {
-            const_cast<Time&>(mesh.time())++;
-        }
 
-        // 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 neccesary.
+            //- 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
+                (
+                    label faceI = newMesh.nInternalFaces();
+                    faceI < newMesh.nFaces();
+                    faceI++
+                )
+                {
+                    label newMeshFaceI = map().faceMap()[faceI];
+                    if (newMeshFaceI != -1)
+                    {
+                        meshToNewMesh[newMeshFaceI] = faceI;
+                    }
+                }
 
-        autoPtr<fvMesh> newMeshPtr;
-        autoPtr<mapPolyMesh> map = meshMod.makeMesh
-        (
-            newMeshPtr,
-            IOobject
+                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);
+
+                internalBaffles = meshRefinement::subsetBaffles
+                (
+                    newMesh,
+                    internalFaceZones,
+                    newMeshBaffles
+                );
+
+                Info<< "Detected "
+                    << returnReduce(internalBaffles.size(), sumOp<label>())
+                    << " baffles across faceZones of type internal" << nl
+                    << endl;
+            }
+
+            label nTotChanged = checkAndUnmark
             (
-                //mesh.name()+"_layer",
-                mesh.name(),
-                static_cast<polyMesh&>(mesh).instance(),
-                mesh.time(),  // register with runTime
-                IOobject::NO_READ,
-                static_cast<polyMesh&>(mesh).writeOpt()
-            ),              // io params from original mesh but new name
-            mesh,           // original mesh
-            true            // parallel sync
-        );
-        fvMesh& newMesh = newMeshPtr();
+                addLayer,
+                meshQualityDict,
+                layerParams.additionalReporting(),
+                internalBaffles,
+                pp(),
+                newMesh,
 
-        //?neccesary? Update fields
-        newMesh.updateMesh(map);
+                patchDisp,
+                patchNLayers,
+                extrudeStatus
+            );
 
-        newMesh.setInstance(meshRefiner_.timeName());
+            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;
+            }
 
-        // 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())
-        );
+            // Reset mesh points and start again
+            mesh.movePoints(oldPoints);
+            pp().movePoints(mesh.points());
+            medialAxisMoverPtr().movePoints(mesh.points());
 
-        // Update numbering of baffles
-        List<labelPair> newMeshBaffles(baffles.size());
-        forAll(baffles, i)
+            // Grow out region of non-extrusion
+            for (label i = 0; i < layerParams.nGrow(); i++)
+            {
+                growNoExtrusion
+                (
+                    pp,
+                    patchDisp,
+                    patchNLayers,
+                    extrudeStatus
+                );
+            }
+
+            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.
+
+    {
+        // Apply the stored topo changes to the current mesh.
+        autoPtr<mapPolyMesh> map = savedMeshMod.changeMesh(mesh, false);
+
+        // 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
         {
-            const labelPair& p = baffles[i];
-            newMeshBaffles[i][0] = map().reverseFaceMap()[p[0]];
-            newMeshBaffles[i][1] = map().reverseFaceMap()[p[1]];
+            // Delete mesh volumes.
+            mesh.clearOut();
         }
 
-        // Collect layer faces and cells for outside loop.
-        getLayerCellsFaces
+        // Reset the instance for if in overwrite mode
+        mesh.setInstance(meshRefiner_.timeName());
+
+        meshRefiner_.updateMesh(map, labelList(0));
+
+        // Update numbering of faceWantedThickness
+        meshRefinement::updateList
         (
-            newMesh,
-            addLayer,
-            avgPointData(pp, mag(patchDisp))(), // current thickness
+            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
         );
 
 
-        // Count number of added cells
-        label nAddedCells = 0;
-        forAll(cellNLayers, cellI)
+        // Dump for debugging
+        if (debug&meshRefinement::MESH || debug&meshRefinement::LAYERINFO)
         {
-            if (cellNLayers[cellI] > 0)
-            {
-                nAddedCells++;
-            }
+            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()
+            );
         }
 
 
-        if (debug&meshRefinement::MESH)
-        {
-            Info<< "Writing layer mesh to time " << meshRefiner_.timeName()
-                << endl;
-            newMesh.write();
 
-            cellSet addedCellSet(newMesh, "addedCells", nAddedCells);
-            forAll(cellNLayers, cellI)
+        // Update numbering of baffles
+        {
+            // From old mesh face to corresponding newMesh boundary face.
+            // (we cannot just use faceMap or reverseFaceMap here since
+            //  multiple faces originate from the old face)
+            labelList oldMeshToNewMesh(map().nOldFaces(), -1);
+            for
+            (
+                label faceI = mesh.nInternalFaces();
+                faceI < mesh.nFaces();
+                faceI++
+            )
             {
-                if (cellNLayers[cellI] > 0)
+                label oldFaceI = map().faceMap()[faceI];
+
+                if (oldFaceI != -1)
                 {
-                    addedCellSet.insert(cellI);
+                    oldMeshToNewMesh[oldFaceI] = faceI;
                 }
             }
-            addedCellSet.instance() = meshRefiner_.timeName();
-            Info<< "Writing "
-                << returnReduce(addedCellSet.size(), sumOp<label>())
-                << " added cells to cellSet " << addedCellSet.name() << endl;
-            addedCellSet.write();
 
-            faceSet layerFacesSet(newMesh, "layerFaces", newMesh.nFaces()/100);
-            for (label faceI = 0; faceI < newMesh.nInternalFaces(); faceI++)
+            label newI = 0;
+            forAll(baffles, i)
             {
-                if (faceRealThickness[faceI] > 0)
+                const labelPair& p = baffles[i];
+
+                labelPair newB(oldMeshToNewMesh[p[0]], oldMeshToNewMesh[p[1]]);
+                if (newB[0] != -1 && newB[1] != -1)
                 {
-                    layerFacesSet.insert(faceI);
+                    baffles[newI++] = newB;
                 }
             }
-            layerFacesSet.instance() = meshRefiner_.timeName();
-            Info<< "Writing "
-                << returnReduce(layerFacesSet.size(), sumOp<label>())
-                << " faces inside added layer to faceSet "
-                << layerFacesSet.name() << endl;
-            layerFacesSet.write();
+            baffles.setSize(newI);
         }
 
 
-        label nTotChanged = checkAndUnmark
-        (
-            addLayer,
-            meshQualityDict,
-            layerParams.additionalReporting(),
-            newMeshBaffles,
-            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)
+        // Update numbering of pointToDuplicate
+        if (returnReduce(pointToDuplicate.size(), sumOp<label>()))
         {
-            break;
-        }
+            // The problem is that pointToDuplicate is valid for the old
+            // boundary points which are now internal. We need to find the
+            // corresponding new boundary point.
 
-        // Reset mesh points and start again
-        mesh.movePoints(oldPoints);
-        pp().movePoints(mesh.points());
 
-        // Grow out region of non-extrusion
-        for (label i = 0; i < layerParams.nGrow(); i++)
-        {
-            growNoExtrusion
+            List<labelPair> mergePointBaffles
             (
-                pp,
-                patchDisp,
-                patchNLayers,
-                extrudeStatus
+                meshRefinement::subsetBaffles
+                (
+                    mesh,
+                    internalOrBaffleFaceZones,
+                    baffles
+                )
             );
-        }
+            Info<< "Detected "
+                << returnReduce(mergePointBaffles.size(), sumOp<label>())
+                << " baffles to merge points across" << nl << endl;
 
-        Info<< endl;
-    }
 
+            label nPointPairs = 0;
+            forAll(pointToDuplicate, oldPointI)
+            {
+                label otherOldPointI = pointToDuplicate[oldPointI];
+                if (otherOldPointI != -1)
+                {
+                    nPointPairs++;
+                }
+            }
 
-    // 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.
+            const labelList& pointMap = map().pointMap();
+
+            // 1. Construct map from old (possibly) internal point to
+            //    new boundary point
+            Map<label> oldPointToBoundaryPoint(2*nPointPairs);
+
+            forAll(mergePointBaffles, i)
+            {
+                const labelPair& baffle = mergePointBaffles[i];
+                forAll(baffle, j)
+                {
+                    const face& f = mesh.faces()[baffle[j]];
+                    forAll(f, fp)
+                    {
+                        label pointI = f[fp];
+                        label oldPointI = pointMap[pointI];
+                        if (pointToDuplicate[oldPointI] != -1)
+                        {
+                            oldPointToBoundaryPoint.insert(oldPointI, pointI);
+                        }
+                    }
+                }
+            }
 
-    // Apply the stored topo changes to the current mesh.
-    autoPtr<mapPolyMesh> map = savedMeshMod.changeMesh(mesh, false);
 
-    // Hack to remove meshPhi - mapped incorrectly. TBD.
-    mesh.clearOut();
+            // 2. Pick up old internal point
 
-    // Update fields
-    mesh.updateMesh(map);
+            labelList oldPointToDuplicate(pointToDuplicate.xfer());
+            pointToDuplicate.setSize(mesh.nPoints(), -1);
 
-    // Move mesh (since morphing does not do this)
-    if (map().hasMotionPoints())
-    {
-        mesh.movePoints(map().preMotionPoints());
+            forAll(mergePointBaffles, i)
+            {
+                const labelPair& baffle = mergePointBaffles[i];
+                forAll(baffle, j)
+                {
+                    const face& f = mesh.faces()[baffle[j]];
+                    forAll(f, fp)
+                    {
+                        label pointI = f[fp];
+                        label oldPointI = pointMap[pointI];
+                        label oldDupI = oldPointToDuplicate[oldPointI];
+                        if (oldDupI != -1)
+                        {
+                            label newPointI = oldPointToBoundaryPoint[oldDupI];
+                            pointToDuplicate[pointI] = newPointI;
+                        }
+                    }
+                }
+            }
+
+
+            // Check
+            forAll(pointToDuplicate, pointI)
+            {
+                label dupI = pointToDuplicate[pointI];
+                if (dupI != -1)
+                {
+                    const point& pt = mesh.points()[pointI];
+                    const point& dupPt = mesh.points()[dupI];
+                    if (mag(pt-dupPt) > meshRefiner_.mergeDistance())
+                    {
+                        WarningIn("autoLayerDriver::addLayers(..)")
+                            << "Trying to merge points "
+                            << pointI << " at:" << pt
+                            << "and " << dupI << " at:" << dupPt
+                            << " distance " << mag(pt-dupPt)
+                            << endl;
+                    }
+                }
+            }
+        }
     }
-    else
+
+    // Count duplicate points
+    label nPointPairs = 0;
+    forAll(pointToDuplicate, pointI)
     {
-        // Delete mesh volumes.
-        mesh.clearOut();
+        label otherPointI = pointToDuplicate[pointI];
+        if (otherPointI != -1)
+        {
+            nPointPairs++;
+        }
     }
+    reduce(nPointPairs, sumOp<label>());
+    if (nPointPairs > 0)
+    {
+        // Merge any duplicated points
+        Info<< "Merging " << nPointPairs << " duplicated points ..." << endl;
 
-    // Reset the instance for if in overwrite mode
-    mesh.setInstance(meshRefiner_.timeName());
+        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(pointToDuplicate, pointI)
+            {
+                label otherPointI = pointToDuplicate[pointI];
+                if (otherPointI != -1)
+                {
+                    const point& pt = mesh.points()[pointI];
+                    const point& otherPt = mesh.points()[otherPointI];
+                    str.write(linePointRef(pt, otherPt));
+                }
+            }
+        }
 
-    meshRefiner_.updateMesh(map, labelList(0));
 
-    // Update numbering of faceWantedThickness
-    meshRefinement::updateList(map().faceMap(), scalar(0), faceWantedThickness);
+        autoPtr<mapPolyMesh> map = meshRefiner_.mergePoints(pointToDuplicate);
+        if (map.valid())
+        {
+            inplaceReorder(map().reverseCellMap(), cellNLayers);
 
-    // Update numbering on baffles
-    forAll(baffles, i)
-    {
-        labelPair& p = baffles[i];
-        p[0] = map().reverseFaceMap()[p[0]];
-        p[1] = map().reverseFaceMap()[p[1]];
-    }
+            const labelList& reverseFaceMap = map().reverseFaceMap();
+            inplaceReorder(reverseFaceMap, faceWantedThickness);
+            inplaceReorder(reverseFaceMap, faceRealThickness);
 
+            Info<< "Merged points in = "
+                << mesh.time().cpuTimeIncrement() << " s\n" << nl << endl;
+        }
+    }
 
-    label nBaffles = returnReduce(baffles.size(), sumOp<label>());
-    if (nBaffles > 0)
+    if (mesh.faceZones().size() > 0)
     {
         // Merge any baffles
-        Info<< "Converting " << nBaffles
-            << " baffles back into zoned faces ..."
+        Info<< "Converting baffles back into zoned faces ..."
             << endl;
 
-        autoPtr<mapPolyMesh> map = meshRefiner_.mergeBaffles(baffles);
+        autoPtr<mapPolyMesh> map = meshRefiner_.mergeZoneBaffles
+        (
+            true,   // internal zones
+            false   // baffle zones
+        );
+        if (map.valid())
+        {
+            inplaceReorder(map().reverseCellMap(), cellNLayers);
+
+            const labelList& faceMap = map().faceMap();
 
-        inplaceReorder(map().reverseCellMap(), cellNLayers);
-        inplaceReorder(map().reverseFaceMap(), faceWantedThickness);
-        inplaceReorder(map().reverseFaceMap(), faceRealThickness);
+            // Make sure to keep the max since on two patches only one has
+            // layers.
+            scalarField newFaceRealThickness(mesh.nFaces(), 0.0);
+            scalarField newFaceWantedThickness(mesh.nFaces(), 0.0);
+            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);
+        }
 
         Info<< "Converted baffles in = "
             << meshRefiner_.mesh().time().cpuTimeIncrement()
             << " s\n" << nl << endl;
     }
 
-
     // Do final balancing
     // ~~~~~~~~~~~~~~~~~~
 
@@ -3594,6 +4256,7 @@ void Foam::autoLayerDriver::doLayers
     const dictionary& shrinkDict,
     const dictionary& motionDict,
     const layerParameters& layerParams,
+    const bool mergePatchFaces,
     const bool preBalance,
     decompositionMethod& decomposer,
     fvMeshDistribute& distributor
@@ -3606,10 +4269,15 @@ void Foam::autoLayerDriver::doLayers
         << "----------------------------------" << nl
         << endl;
 
+
     Info<< "Using mesh parameters " << motionDict << nl << endl;
 
     // Merge coplanar boundary faces
-    mergePatchFacesUndo(layerParams, motionDict);
+    if (mergePatchFaces)
+    {
+        mergePatchFacesUndo(layerParams, motionDict);
+    }
+
 
     // Per patch the number of layers (-1 or 0 if no layer)
     const labelList& numLayers = layerParams.numLayers();
@@ -3636,6 +4304,26 @@ void Foam::autoLayerDriver::doLayers
             }
         }
     }
+
+    // Add contributions from faceZones that get layers
+    const faceZoneMesh& fZones = mesh.faceZones();
+    forAll(fZones, zoneI)
+    {
+        label mpI, spI;
+        surfaceZonesInfo::faceZoneType fzType;
+        meshRefiner_.getFaceZoneInfo(fZones[zoneI].name(), mpI, spI, fzType);
+
+        if (numLayers[mpI] > 0)
+        {
+            nFacesWithLayers += fZones[zoneI].size();
+        }
+        if (numLayers[spI] > 0)
+        {
+            nFacesWithLayers += fZones[zoneI].size();
+        }
+    }
+
+
     patchIDs.shrink();
 
     if (returnReduce(nFacesWithLayers, sumOp<label>()) == 0)
@@ -3683,12 +4371,45 @@ void Foam::autoLayerDriver::doLayers
                 }
             }
 
+            // Add contributions from faceZones that get layers
+            const faceZoneMesh& fZones = mesh.faceZones();
+            forAll(fZones, zoneI)
+            {
+                const faceZone& fZone = fZones[zoneI];
+                const word& fzName = fZone.name();
+
+                label mpI, spI;
+                surfaceZonesInfo::faceZoneType fzType;
+                meshRefiner_.getFaceZoneInfo(fzName, mpI, spI, fzType);
+
+                if (numLayers[mpI] > 0)
+                {
+                    // Get the owner side for unflipped faces, neighbour side
+                    // for flipped ones
+                    const labelList& cellIDs = fZone.slaveCells();
+                    forAll(cellIDs, i)
+                    {
+                        cellWeights[cellIDs[i]] += numLayers[mpI];
+                    }
+                }
+                if (numLayers[spI] > 0)
+                {
+                    const labelList& cellIDs = fZone.masterCells();
+                    forAll(cellIDs, i)
+                    {
+                        cellWeights[cellIDs[i]] += numLayers[mpI];
+                    }
+                }
+            }
+
+
+
             // Balance mesh (and meshRefinement). Restrict faceZones to
             // be on internal faces only since they will be converted into
             // baffles.
             autoPtr<mapDistributePolyMesh> map = meshRefiner_.balance
             (
-                true,   //false,    // keepZoneFaces
+                true,           // keepZoneFaces
                 false,
                 cellWeights,
                 decomposer,
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.H b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.H
index 7aa5050a2291dc9916279c86432c7c63549d2841..55db5ed9d04a9f747e4db4a0c6c0ba0d1764ebc5 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.H
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoLayerDriver.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -47,7 +47,6 @@ class removePoints;
 class pointSet;
 class motionSmoother;
 class addPatchCellLayer;
-class pointData;
 class faceSet;
 class layerParameters;
 
@@ -108,7 +107,6 @@ private:
         const labelList globalToSlavePatch_;
 
 
-
     // Private Member Functions
 
         // Layers
@@ -231,14 +229,17 @@ private:
                     List<extrudeMode>& extrudeStatus
                 ) const;
 
-                //- See what patches boundaryedges should be extruded into
+                //- See what zones and patches edges should be extruded into
                 void determineSidePatches
                 (
                     const globalIndex& globalFaces,
                     const labelListList& edgeGlobalFaces,
                     const indirectPrimitivePatch& pp,
 
-                    labelList& sidePatchID
+                    labelList& edgePatchID,
+                    labelList& edgeZoneID,
+                    boolList& edgeFlip,
+                    labelList& inflateFaceID
                 );
 
                 //- Calculate pointwise wanted and minimum thickness.
@@ -370,6 +371,14 @@ private:
                     const List<extrudeMode>& extrudeStatus
                 );
 
+                //- After adding to mesh get the new baffles
+                static List<labelPair> getBafflesOnAddedMesh
+                (
+                    const polyMesh& mesh,
+                    const labelList& newToOldFaces,
+                    const List<labelPair>& baffles
+                );
+
                 //- Collect layer faces and layer cells into bools
                 //  for ease of handling
                 static void getLayerCellsFaces
@@ -393,6 +402,15 @@ private:
                 ) const;
 
                 //- Write cellSet,faceSet for layers
+                bool writeLayerSets
+                (
+                    const fvMesh& mesh,
+                    const labelList& cellNLayers,
+                    const scalarField& faceRealThickness
+                ) const;
+
+                //- Write volFields,cellSet,faceSet for layers depending
+                //  on write level
                 bool writeLayerData
                 (
                     const fvMesh& mesh,
@@ -464,13 +482,6 @@ private:
                     pointVectorField& normals
                 ) const;
 
-                bool isMaxEdge
-                (
-                    const List<pointData>&,
-                    const label edgeI,
-                    const scalar minCos
-                ) const;
-
                 //- Stop layer growth where mesh wraps around edge with a
                 //  large feature angle
                 void handleFeatureAngleLayerTerminations
@@ -595,6 +606,7 @@ public:
                 const dictionary& shrinkDict,
                 const dictionary& motionDict,
                 const layerParameters& layerParams,
+                const bool mergePatchFaces,         // merging patch faces
                 const bool preBalance,              // balance before adding?
                 decompositionMethod& decomposer,
                 fvMeshDistribute& distributor
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.C
index 8c63239f397c6ece0c5baedcb2c9bf433c303337..be908b76bbc9588fb48a586579613ac1539a62bd 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.C
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -37,6 +37,8 @@ License
 #include "unitConversion.H"
 #include "snapParameters.H"
 #include "localPointRegion.H"
+#include "IOmanip.H"
+#include "labelVector.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -94,7 +96,7 @@ Foam::label Foam::autoRefineDriver::featureEdgeRefine
             (
                 meshRefiner_.refineCandidates
                 (
-                    refineParams.keepPoints(),
+                    refineParams.locationsInMesh(),
                     refineParams.curvature(),
                     refineParams.planarAngle(),
 
@@ -207,7 +209,7 @@ Foam::label Foam::autoRefineDriver::surfaceOnlyRefine
         (
             meshRefiner_.refineCandidates
             (
-                refineParams.keepPoints(),
+                refineParams.locationsInMesh(),
                 refineParams.curvature(),
                 refineParams.planarAngle(),
 
@@ -341,7 +343,7 @@ Foam::label Foam::autoRefineDriver::gapOnlyRefine
         (
             meshRefiner_.refineCandidates
             (
-                refineParams.keepPoints(),
+                refineParams.locationsInMesh(),
                 refineParams.curvature(),
                 refineParams.planarAngle(),
 
@@ -669,6 +671,348 @@ Foam::label Foam::autoRefineDriver::danglingCellRefine
 }
 
 
+// Detect cells with opposing intersected faces of differing refinement
+// level and refine them.
+Foam::label Foam::autoRefineDriver::refinementInterfaceRefine
+(
+    const refinementParameters& refineParams,
+    const label maxIter
+)
+{
+    const fvMesh& mesh = meshRefiner_.mesh();
+
+    label iter = 0;
+
+    if (refineParams.interfaceRefine())
+    {
+        for (;iter < maxIter; iter++)
+        {
+            Info<< nl
+                << "Refinement transition refinement iteration " << iter << nl
+                << "--------------------------------------------" << nl
+                << endl;
+
+            const labelList& surfaceIndex = meshRefiner_.surfaceIndex();
+            const hexRef8& cutter = meshRefiner_.meshCutter();
+            const vectorField& fA = mesh.faceAreas();
+            const labelList& faceOwner = mesh.faceOwner();
+
+
+            // Determine cells to refine
+            // ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+            const cellList& cells = mesh.cells();
+
+            labelList candidateCells;
+            {
+                // Pass1: pick up cells with differing face level
+
+                cellSet transitionCells
+                (
+                    mesh,
+                    "transitionCells",
+                    cells.size()/100
+                );
+
+                forAll(cells, cellI)
+                {
+                    const cell& cFaces = cells[cellI];
+                    label cLevel = cutter.cellLevel()[cellI];
+
+                    forAll(cFaces, cFaceI)
+                    {
+                        label faceI = cFaces[cFaceI];
+
+                        if (surfaceIndex[faceI] != -1)
+                        {
+                            label fLevel = cutter.faceLevel(faceI);
+                            if (fLevel != cLevel)
+                            {
+                                transitionCells.insert(cellI);
+                            }
+                        }
+                    }
+                }
+
+
+                cellSet candidateCellSet
+                (
+                    mesh,
+                    "candidateCells",
+                    cells.size()/1000
+                );
+
+                // Pass2: check for oppositeness
+
+                //forAllConstIter(cellSet, transitionCells, iter)
+                //{
+                //    label cellI = iter.key();
+                //    const cell& cFaces = cells[cellI];
+                //    const point& cc = cellCentres[cellI];
+                //    const scalar rCVol = pow(cellVolumes[cellI], -5.0/3.0);
+                //
+                //    // Determine principal axes of cell
+                //    symmTensor R(symmTensor::zero);
+                //
+                //    forAll(cFaces, i)
+                //    {
+                //        label faceI = cFaces[i];
+                //
+                //        const point& fc = faceCentres[faceI];
+                //
+                //        // Calculate face-pyramid volume
+                //        scalar pyrVol = 1.0/3.0 * fA[faceI] & (fc-cc);
+                //
+                //        if (faceOwner[faceI] != cellI)
+                //        {
+                //            pyrVol = -pyrVol;
+                //        }
+                //
+                //        // Calculate face-pyramid centre
+                //        vector pc = (3.0/4.0)*fc + (1.0/4.0)*cc;
+                //
+                //        R += pyrVol*sqr(pc-cc)*rCVol;
+                //    }
+                //
+                //    //- MEJ: Problem: truncation errors cause complex evs
+                //    vector lambdas(eigenValues(R));
+                //    const tensor axes(eigenVectors(R, lambdas));
+                //
+                //
+                //    // Check if this cell has
+                //    // - opposing sides intersected
+                //    // - which are of different refinement level
+                //    // - plus the inbetween face
+                //
+                //    labelVector plusFaceLevel(labelVector(-1, -1, -1));
+                //    labelVector minFaceLevel(labelVector(-1, -1, -1));
+                //
+                //    forAll(cFaces, cFaceI)
+                //    {
+                //        label faceI = cFaces[cFaceI];
+                //
+                //        if (surfaceIndex[faceI] != -1)
+                //        {
+                //            label fLevel = cutter.faceLevel(faceI);
+                //
+                //            // Get outwards pointing normal
+                //            vector n = fA[faceI]/mag(fA[faceI]);
+                //            if (faceOwner[faceI] != cellI)
+                //            {
+                //                n = -n;
+                //            }
+                //
+                //            // What is major direction and sign
+                //            direction cmpt = vector::X;
+                //            scalar maxComp = (n&axes.x());
+                //
+                //            scalar yComp = (n&axes.y());
+                //            scalar zComp = (n&axes.z());
+                //
+                //            if (mag(yComp) > mag(maxComp))
+                //            {
+                //                maxComp = yComp;
+                //                cmpt = vector::Y;
+                //            }
+                //
+                //            if (mag(zComp) > mag(maxComp))
+                //            {
+                //                maxComp = zComp;
+                //                cmpt = vector::Z;
+                //            }
+                //
+                //            if (maxComp > 0)
+                //            {
+                //                plusFaceLevel[cmpt] = max
+                //                (
+                //                    plusFaceLevel[cmpt],
+                //                    fLevel
+                //                );
+                //            }
+                //            else
+                //            {
+                //                minFaceLevel[cmpt] = max
+                //                (
+                //                    minFaceLevel[cmpt],
+                //                    fLevel
+                //                );
+                //            }
+                //        }
+                //    }
+                //
+                //    // Check if we picked up any opposite differing level
+                //    for (direction dir = 0; dir < vector::nComponents; dir++)
+                //    {
+                //        if
+                //        (
+                //            plusFaceLevel[dir] != -1
+                //         && minFaceLevel[dir] != -1
+                //         && plusFaceLevel[dir] != minFaceLevel[dir]
+                //        )
+                //        {
+                //            candidateCellSet.insert(cellI);
+                //        }
+                //    }
+                //}
+
+                const scalar oppositeCos = Foam::cos(Foam::degToRad(135));
+
+                forAllConstIter(cellSet, transitionCells, iter)
+                {
+                    label cellI = iter.key();
+                    const cell& cFaces = cells[cellI];
+                    label cLevel = cutter.cellLevel()[cellI];
+
+                    // Detect opposite intersection
+                    bool foundOpposite = false;
+
+                    forAll(cFaces, cFaceI)
+                    {
+                        label faceI = cFaces[cFaceI];
+
+                        if
+                        (
+                            surfaceIndex[faceI] != -1
+                         && cutter.faceLevel(faceI) > cLevel
+                        )
+                        {
+                            // Get outwards pointing normal
+                            vector n = fA[faceI]/mag(fA[faceI]);
+                            if (faceOwner[faceI] != cellI)
+                            {
+                                n = -n;
+                            }
+
+                            // Check for any opposite intersection
+                            forAll(cFaces, cFaceI2)
+                            {
+                                label face2I = cFaces[cFaceI2];
+
+                                if
+                                (
+                                    face2I != faceI
+                                 && surfaceIndex[face2I] != -1
+                                )
+                                {
+                                    // Get outwards pointing normal
+                                    vector n2 = fA[face2I]/mag(fA[face2I]);
+                                    if (faceOwner[face2I] != cellI)
+                                    {
+                                        n2 = -n2;
+                                    }
+
+
+                                    if ((n&n2) < oppositeCos)
+                                    {
+                                        foundOpposite = true;
+                                        break;
+                                    }
+                                }
+                            }
+
+                            if (foundOpposite)
+                            {
+                                break;
+                            }
+                        }
+                    }
+
+
+                    if (foundOpposite)
+                    {
+                        candidateCellSet.insert(cellI);
+                    }
+                }
+
+                if (debug&meshRefinement::MESH)
+                {
+                    Pout<< "Dumping " << candidateCellSet.size()
+                        << " cells to cellSet candidateCellSet." << endl;
+                    candidateCellSet.instance() = meshRefiner_.timeName();
+                    candidateCellSet.write();
+                }
+                candidateCells = candidateCellSet.toc();
+            }
+
+
+
+            labelList cellsToRefine
+            (
+                meshRefiner_.meshCutter().consistentRefinement
+                (
+                    candidateCells,
+                    true
+                )
+            );
+            Info<< "Determined cells to refine in = "
+                << mesh.time().cpuTimeIncrement() << " s" << endl;
+
+
+            label nCellsToRefine = cellsToRefine.size();
+            reduce(nCellsToRefine, sumOp<label>());
+
+            Info<< "Selected for refinement : " << nCellsToRefine
+                << " cells (out of " << mesh.globalData().nTotalCells()
+                << ')' << endl;
+
+            // Stop when no cells to refine. After a few iterations check if too
+            // few cells
+            if
+            (
+                nCellsToRefine == 0
+             || (
+                    iter >= 1
+                 && nCellsToRefine <= refineParams.minRefineCells()
+                )
+            )
+            {
+                Info<< "Stopping refining since too few cells selected."
+                    << nl << endl;
+                break;
+            }
+
+
+            if (debug)
+            {
+                const_cast<Time&>(mesh.time())++;
+            }
+
+
+            if
+            (
+                returnReduce
+                (
+                    (mesh.nCells() >= refineParams.maxLocalCells()),
+                    orOp<bool>()
+                )
+            )
+            {
+                meshRefiner_.balanceAndRefine
+                (
+                    "interface cell refinement iteration " + name(iter),
+                    decomposer_,
+                    distributor_,
+                    cellsToRefine,
+                    refineParams.maxLoadUnbalance()
+                );
+            }
+            else
+            {
+                meshRefiner_.refineAndBalance
+                (
+                    "interface cell refinement iteration " + name(iter),
+                    decomposer_,
+                    distributor_,
+                    cellsToRefine,
+                    refineParams.maxLoadUnbalance()
+                );
+            }
+        }
+    }
+    return iter;
+}
+
+
 void Foam::autoRefineDriver::removeInsideCells
 (
     const refinementParameters& refineParams,
@@ -692,13 +1036,15 @@ void Foam::autoRefineDriver::removeInsideCells
         nBufferLayers,                  // nBufferLayers
         globalToMasterPatch_,
         globalToSlavePatch_,
-        refineParams.keepPoints()[0]
+        refineParams.locationsInMesh(),
+        refineParams.zonesInMesh(),
+        refineParams.locationsOutsideMesh()
     );
 
     if (debug&meshRefinement::MESH)
     {
         Pout<< "Writing subsetted mesh to time "
-            << meshRefiner_.timeName() << '.' << endl;
+            << meshRefiner_.timeName() << endl;
         meshRefiner_.write
         (
             meshRefinement::debugType(debug),
@@ -753,7 +1099,7 @@ Foam::label Foam::autoRefineDriver::shellRefine
         (
             meshRefiner_.refineCandidates
             (
-                refineParams.keepPoints(),
+                refineParams.locationsInMesh(),
                 refineParams.curvature(),
                 refineParams.planarAngle(),
 
@@ -913,22 +1259,40 @@ void Foam::autoRefineDriver::baffleAndSplitMesh
         false,                          // perpendicular edge connected cells
         scalarField(0),                 // per region perpendicular angle
 
-        // Free standing baffles
-        !handleSnapProblems,            // merge free standing baffles?
-        refineParams.planarAngle(),
-
         motionDict,
         const_cast<Time&>(mesh.time()),
         globalToMasterPatch_,
         globalToSlavePatch_,
-        refineParams.keepPoints()[0]
+        refineParams.locationsInMesh(),
+        refineParams.zonesInMesh(),
+        refineParams.locationsOutsideMesh()
     );
+
+
+    if (!handleSnapProblems) // merge free standing baffles?
+    {
+        meshRefiner_.mergeFreeStandingBaffles
+        (
+            snapParams,
+            refineParams.useTopologicalSnapDetection(),
+            false,                  // perpendicular edge connected cells
+            scalarField(0),         // per region perpendicular angle
+            refineParams.planarAngle(),
+            motionDict,
+            const_cast<Time&>(mesh.time()),
+            globalToMasterPatch_,
+            globalToSlavePatch_,
+            refineParams.locationsInMesh(),
+            refineParams.locationsOutsideMesh()
+        );
+    }
 }
 
 
 void Foam::autoRefineDriver::zonify
 (
-    const refinementParameters& refineParams
+    const refinementParameters& refineParams,
+    wordPairHashTable& zonesToFaceZone
 )
 {
     // Mesh is at its finest. Do zoning
@@ -940,7 +1304,11 @@ void Foam::autoRefineDriver::zonify
     const labelList namedSurfaces =
         surfaceZonesInfo::getNamedSurfaces(meshRefiner_.surfaces().surfZones());
 
-    if (namedSurfaces.size())
+    if
+    (
+        namedSurfaces.size()
+     || refineParams.zonesInMesh().size()
+    )
     {
         Info<< nl
             << "Introducing zones for interfaces" << nl
@@ -956,14 +1324,16 @@ void Foam::autoRefineDriver::zonify
 
         meshRefiner_.zonify
         (
-            refineParams.keepPoints()[0],
-            refineParams.allowFreeStandingZoneFaces()
+            refineParams.allowFreeStandingZoneFaces(),
+            refineParams.locationsInMesh(),
+            refineParams.zonesInMesh(),
+            zonesToFaceZone
         );
 
         if (debug&meshRefinement::MESH)
         {
             Pout<< "Writing zoned mesh to time "
-                << meshRefiner_.timeName() << '.' << endl;
+                << meshRefiner_.timeName() << endl;
             meshRefiner_.write
             (
                 meshRefinement::debugType(debug),
@@ -1015,15 +1385,29 @@ void Foam::autoRefineDriver::splitAndMergeBaffles
         handleSnapProblems,                 // remove perp edge connected cells
         perpAngle,                          // perp angle
 
-        // Free standing baffles
-        true,                               // merge free standing baffles?
-        refineParams.planarAngle(),         // planar angle
+        motionDict,
+        const_cast<Time&>(mesh.time()),
+        globalToMasterPatch_,
+        globalToSlavePatch_,
+        refineParams.locationsInMesh(),
+        refineParams.zonesInMesh(),
+        refineParams.locationsOutsideMesh()
+    );
 
+    // Merge free-standing baffles always
+    meshRefiner_.mergeFreeStandingBaffles
+    (
+        snapParams,
+        refineParams.useTopologicalSnapDetection(),
+        handleSnapProblems,
+        perpAngle,
+        refineParams.planarAngle(),
         motionDict,
         const_cast<Time&>(mesh.time()),
         globalToMasterPatch_,
         globalToSlavePatch_,
-        refineParams.keepPoints()[0]
+        refineParams.locationsInMesh(),
+        refineParams.locationsOutsideMesh()
     );
 
     if (debug)
@@ -1048,7 +1432,7 @@ void Foam::autoRefineDriver::splitAndMergeBaffles
         // Actually merge baffles. Note: not exactly parallellized. Should
         // convert baffle faces into processor faces if they resulted
         // from them.
-        meshRefiner_.mergeBaffles(couples);
+        meshRefiner_.mergeBaffles(couples, Map<label>(0));
 
         if (debug)
         {
@@ -1061,7 +1445,8 @@ void Foam::autoRefineDriver::splitAndMergeBaffles
         (
             globalToMasterPatch_,
             globalToSlavePatch_,
-            refineParams.keepPoints()[0]
+            refineParams.locationsInMesh(),
+            refineParams.locationsOutsideMesh()
         );
 
         if (debug)
@@ -1077,7 +1462,7 @@ void Foam::autoRefineDriver::splitAndMergeBaffles
     if (debug&meshRefinement::MESH)
     {
         Pout<< "Writing handleProblemCells mesh to time "
-            << meshRefiner_.timeName() << '.' << endl;
+            << meshRefiner_.timeName() << endl;
         meshRefiner_.write
         (
             meshRefinement::debugType(debug),
@@ -1092,8 +1477,83 @@ void Foam::autoRefineDriver::splitAndMergeBaffles
 }
 
 
+void Foam::autoRefineDriver::addFaceZones
+(
+    meshRefinement& meshRefiner,
+    const refinementParameters& refineParams,
+    const HashTable<Pair<word> >& faceZoneToPatches
+)
+{
+    if (faceZoneToPatches.size())
+    {
+        Info<< nl
+            << "Adding patches for face zones" << nl
+            << "-----------------------------" << nl
+            << endl;
+
+        Info<< setf(ios_base::left)
+            << setw(6) << "Patch"
+            << setw(20) << "Type"
+            << setw(30) << "Name"
+            << setw(30) << "FaceZone"
+            << setw(10) << "FaceType"
+            << nl
+            << setw(6) << "-----"
+            << setw(20) << "----"
+            << setw(30) << "----"
+            << setw(30) << "--------"
+            << setw(10) << "--------"
+            << endl;
+
+        const polyMesh& mesh = meshRefiner.mesh();
+
+        // Add patches for added inter-region faceZones
+        forAllConstIter(HashTable<Pair<word> >, faceZoneToPatches, iter)
+        {
+            const word& fzName = iter.key();
+            const Pair<word>& patchNames = iter();
+
+            // Get any user-defined faceZone data
+            surfaceZonesInfo::faceZoneType fzType;
+            dictionary patchInfo = refineParams.getZoneInfo(fzName, fzType);
+
+            const word& masterName = fzName;
+            //const word slaveName = fzName + "_slave";
+            //const word slaveName = czNames.second()+"_to_"+czNames.first();
+            const word& slaveName = patchNames.second();
+
+            label mpI = meshRefiner.addMeshedPatch(masterName, patchInfo);
+
+            Info<< setf(ios_base::left)
+                << setw(6) << mpI
+                << setw(20) << mesh.boundaryMesh()[mpI].type()
+                << setw(30) << masterName
+                << setw(30) << fzName
+                << setw(10) << surfaceZonesInfo::faceZoneTypeNames[fzType]
+                << nl;
+
+
+            label slI = meshRefiner.addMeshedPatch(slaveName, patchInfo);
+
+            Info<< setf(ios_base::left)
+                << setw(6) << slI
+                << setw(20) << mesh.boundaryMesh()[slI].type()
+                << setw(30) << slaveName
+                << setw(30) << fzName
+                << setw(10) << surfaceZonesInfo::faceZoneTypeNames[fzType]
+                << nl;
+
+            meshRefiner.addFaceZone(fzName, masterName, slaveName, fzType);
+        }
+
+        Info<< endl;
+    }
+}
+
+
 void Foam::autoRefineDriver::mergePatchFaces
 (
+    const bool geometricMerge,
     const refinementParameters& refineParams,
     const dictionary& motionDict
 )
@@ -1105,14 +1565,28 @@ void Foam::autoRefineDriver::mergePatchFaces
 
     const fvMesh& mesh = meshRefiner_.mesh();
 
-    meshRefiner_.mergePatchFacesUndo
-    (
-        Foam::cos(degToRad(45.0)),
-        Foam::cos(degToRad(45.0)),
-        meshRefiner_.meshedPatches(),
-        motionDict,
-        labelList(mesh.nFaces(), -1)
-    );
+    if (geometricMerge)
+    {
+        meshRefiner_.mergePatchFacesUndo
+        (
+            Foam::cos(degToRad(45.0)),
+            Foam::cos(degToRad(45.0)),
+            meshRefiner_.meshedPatches(),
+            motionDict,
+            labelList(mesh.nFaces(), -1)
+        );
+    }
+    else
+    {
+        // Still merge refined boundary faces if all four are on same patch
+        meshRefiner_.mergePatchFaces
+        (
+            Foam::cos(degToRad(45.0)),
+            Foam::cos(degToRad(45.0)),
+            4,          // only merge faces split into 4
+            meshRefiner_.meshedPatches()
+        );
+    }
 
     if (debug)
     {
@@ -1134,6 +1608,7 @@ void Foam::autoRefineDriver::doRefine
     const refinementParameters& refineParams,
     const snapParameters& snapParams,
     const bool prepareForSnapping,
+    const bool doMergePatchFaces,
     const dictionary& motionDict
 )
 {
@@ -1145,7 +1620,7 @@ void Foam::autoRefineDriver::doRefine
     const fvMesh& mesh = meshRefiner_.mesh();
 
     // Check that all the keep points are inside the mesh.
-    refineParams.findCells(mesh);
+    refineParams.findCells(true, mesh, refineParams.locationsInMesh());
 
     // Refine around feature edges
     featureEdgeRefine
@@ -1196,6 +1671,13 @@ void Foam::autoRefineDriver::doRefine
         100     // maxIter
     );
 
+    // Refine any cells with differing refinement level on either side
+    refinementInterfaceRefine
+    (
+        refineParams,
+        10      // maxIter
+    );
+
     // Introduce baffles at surface intersections. Remove sections unreachable
     // from keepPoint.
     baffleAndSplitMesh
@@ -1206,8 +1688,31 @@ void Foam::autoRefineDriver::doRefine
         motionDict
     );
 
-    // Mesh is at its finest. Do optional zoning.
-    zonify(refineParams);
+    // Mesh is at its finest. Do optional zoning (cellZones and faceZones)
+    wordPairHashTable zonesToFaceZone;
+    zonify(refineParams, zonesToFaceZone);
+
+    // Create pairs of patches for faceZones
+    {
+        HashTable<Pair<word> > faceZoneToPatches(zonesToFaceZone.size());
+
+        //    Note: zonesToFaceZone contains the same data on different
+        //          processors but in different order. We could sort the
+        //          contents but instead just loop in sortedToc order.
+        List<Pair<word> > czs(zonesToFaceZone.sortedToc());
+
+        forAll(czs, i)
+        {
+            const Pair<word>& czNames = czs[i];
+            const word& fzName = zonesToFaceZone[czNames];
+
+            const word& masterName = fzName;
+            const word slaveName = czNames.second() + "_to_" + czNames.first();
+            Pair<word> patches(masterName, slaveName);
+            faceZoneToPatches.insert(fzName, patches);
+        }
+        addFaceZones(meshRefiner_, refineParams, faceZoneToPatches);
+    }
 
     // Pull baffles apart
     splitAndMergeBaffles
@@ -1221,7 +1726,7 @@ void Foam::autoRefineDriver::doRefine
     // Do something about cells with refined faces on the boundary
     if (prepareForSnapping)
     {
-        mergePatchFaces(refineParams, motionDict);
+        mergePatchFaces(doMergePatchFaces, refineParams, motionDict);
     }
 
 
@@ -1232,28 +1737,17 @@ void Foam::autoRefineDriver::doRefine
             << "---------------------" << nl
             << endl;
 
-        //if (debug)
-        //{
-        //    const_cast<Time&>(mesh.time())++;
-        //}
-
         // Do final balancing. Keep zoned faces on one processor since the
         // snap phase will convert them to baffles and this only works for
         // internal faces.
         meshRefiner_.balance
         (
-            true,
-            false,
-            scalarField(mesh.nCells(), 1), // dummy weights
+            true,                           // keepZoneFaces
+            false,                          // keepBaffles
+            scalarField(mesh.nCells(), 1),  // cellWeights
             decomposer_,
             distributor_
         );
-
-
-        if (debug)
-        {
-            meshRefiner_.checkZoneFaces();
-        }
     }
 }
 
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.H b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.H
index a72f3c0796d291f8d16409cefdfd09bec479f9c0..11ed1d9454d0b5c7d6db927e0fa25c0d9fdb1ef4 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.H
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoRefineDriver.H
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -34,7 +34,8 @@ SourceFiles
 #ifndef autoRefineDriver_H
 #define autoRefineDriver_H
 
-#include "treeBoundBox.H"
+#include "wordPairHashTable.H"
+#include "labelList.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -105,6 +106,13 @@ class autoRefineDriver
             const label maxIter
         );
 
+        //- Refine cells with opposite faces with differing refinement level
+        label refinementInterfaceRefine
+        (
+            const refinementParameters& refineParams,
+            const label maxIter
+        );
+
         //- Remove all cells within intersected region
         void removeInsideCells
         (
@@ -129,7 +137,11 @@ class autoRefineDriver
         );
 
         //- Add zones
-        void zonify(const refinementParameters& refineParams);
+        void zonify
+        (
+            const refinementParameters& refineParams,
+            wordPairHashTable& zonesToFaceZone
+        );
 
         void splitAndMergeBaffles
         (
@@ -142,11 +154,11 @@ class autoRefineDriver
         //- Merge refined boundary faces (from exposing coarser cell)
         void mergePatchFaces
         (
+            const bool geometricMerge,
             const refinementParameters& refineParams,
             const dictionary& motionDict
         );
 
-
         //- Disallow default bitwise copy construct
         autoRefineDriver(const autoRefineDriver&);
 
@@ -182,8 +194,18 @@ public:
             const refinementParameters& refineParams,
             const snapParameters& snapParams,
             const bool prepareForSnapping,
+            const bool mergePatchFaces,
             const dictionary& motionDict
         );
+
+        //- Helper: add faceZones and patches
+        static void addFaceZones
+        (
+            meshRefinement& meshRefiner,
+            const refinementParameters& refineParams,
+            const HashTable<Pair<word> >& faceZoneToPatches
+        );
+
 };
 
 
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.C
index 41e6ca0dccc5807d7d3234bea387e84be0ba60d9..772c9530b5395e9c84656cc4733ab4f13a86ca3b 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.C
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,9 +40,11 @@ Description
 #include "mergePoints.H"
 #include "snapParameters.H"
 #include "refinementSurfaces.H"
+#include "searchableSurfaces.H"
 #include "unitConversion.H"
 #include "localPointRegion.H"
 #include "PatchTools.H"
+#include "refinementFeatures.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -118,8 +120,187 @@ Foam::label Foam::autoSnapDriver::getCollocatedPoints
 }
 
 
+Foam::tmp<Foam::pointField> Foam::autoSnapDriver::smoothInternalDisplacement
+(
+    const meshRefinement& meshRefiner,
+    const motionSmoother& meshMover
+)
+{
+    const indirectPrimitivePatch& pp = meshMover.patch();
+    const polyMesh& mesh = meshMover.mesh();
+
+    // Get neighbour refinement
+    const hexRef8& cutter = meshRefiner.meshCutter();
+    const labelList& cellLevel = cutter.cellLevel();
+
+
+    // Get the faces on the boundary
+    PackedBoolList isFront(mesh.nFaces());
+    forAll(pp.addressing(), i)
+    {
+        isFront[pp.addressing()[i]] = true;
+    }
+
+    // Walk out from the surface a bit. Poor man's FaceCellWave.
+    // Commented out for now - not sure if needed and if so how much
+    //for (label iter = 0; iter < 2; iter++)
+    //{
+    //    PackedBoolList newIsFront(mesh.nFaces());
+    //
+    //    forAll(isFront, faceI)
+    //    {
+    //        if (isFront[faceI])
+    //        {
+    //            label own = mesh.faceOwner()[faceI];
+    //            const cell& ownFaces = mesh.cells()[own];
+    //            forAll(ownFaces, i)
+    //            {
+    //                newIsFront[ownFaces[i]] = true;
+    //            }
+    //
+    //            if (mesh.isInternalFace(faceI))
+    //            {
+    //                label nei = mesh.faceNeighbour()[faceI];
+    //                const cell& neiFaces = mesh.cells()[nei];
+    //                forAll(neiFaces, i)
+    //                {
+    //                    newIsFront[neiFaces[i]] = true;
+    //                }
+    //            }
+    //        }
+    //    }
+    //
+    //    syncTools::syncFaceList
+    //    (
+    //        mesh,
+    //        newIsFront,
+    //        orEqOp<unsigned int>()
+    //    );
+    //
+    //    isFront = newIsFront;
+    //}
+
+    // Mark all points on faces
+    //  - not on the boundary
+    //  - inbetween differing refinement levels
+    PackedBoolList isMovingPoint(mesh.nPoints());
+
+    label nInterface = 0;
+
+    for (label faceI = 0; faceI < mesh.nInternalFaces(); faceI++)
+    {
+        label ownLevel = cellLevel[mesh.faceOwner()[faceI]];
+        label neiLevel = cellLevel[mesh.faceNeighbour()[faceI]];
+
+        if (!isFront[faceI] && ownLevel != neiLevel)
+        {
+            const face& f = mesh.faces()[faceI];
+            forAll(f, fp)
+            {
+                isMovingPoint[f[fp]] = true;
+            }
+
+            nInterface++;
+        }
+    }
+
+    labelList neiCellLevel;
+    syncTools::swapBoundaryCellList(mesh, cellLevel, neiCellLevel);
+
+    for (label faceI = mesh.nInternalFaces(); faceI < mesh.nFaces(); faceI++)
+    {
+        label ownLevel = cellLevel[mesh.faceOwner()[faceI]];
+        label neiLevel = neiCellLevel[faceI-mesh.nInternalFaces()];
+
+        if (!isFront[faceI] && ownLevel != neiLevel)
+        {
+            const face& f = mesh.faces()[faceI];
+            forAll(f, fp)
+            {
+                isMovingPoint[f[fp]] = true;
+            }
+
+            nInterface++;
+        }
+    }
+
+    if (debug)
+    {
+        reduce(nInterface, sumOp<label>());
+        Info<< "Found " << nInterface << " faces out of "
+            << mesh.globalData().nTotalFaces()
+            << " inbetween refinement regions." << endl;
+    }
+
+    // Make sure that points that are coupled to a moving point are marked
+    // as well
+    syncTools::syncPointList(mesh, isMovingPoint, maxEqOp<unsigned int>(), 0);
+
+    // Unmark any point on the boundary. If we're doing zero iterations of
+    // face-cell wave we might have coupled points not being unmarked.
+    forAll(pp.meshPoints(), pointI)
+    {
+        isMovingPoint[pp.meshPoints()[pointI]] = false;
+    }
+
+    // Make sure that points that are coupled to meshPoints but not on a patch
+    // are unmarked as well
+    syncTools::syncPointList(mesh, isMovingPoint, minEqOp<unsigned int>(), 1);
+
+
+    // Calculate average of connected cells
+    labelList nCells(mesh.nPoints(), 0);
+    pointField sumLocation(mesh.nPoints(), vector::zero);
+
+    forAll(isMovingPoint, pointI)
+    {
+        if (isMovingPoint[pointI])
+        {
+            const labelList& pCells = mesh.pointCells(pointI);
+
+            forAll(pCells, i)
+            {
+                sumLocation[pointI] += mesh.cellCentres()[pCells[i]];
+                nCells[pointI]++;
+            }
+        }
+    }
+
+    // Sum
+    syncTools::syncPointList(mesh, nCells, plusEqOp<label>(), label(0));
+    syncTools::syncPointList
+    (
+        mesh,
+        sumLocation,
+        plusEqOp<point>(),
+        vector::zero
+    );
+
+    tmp<pointField> tdisplacement(new pointField(mesh.nPoints(), vector::zero));
+    pointField& displacement = tdisplacement();
+
+    label nAdapted = 0;
+
+    forAll(displacement, pointI)
+    {
+        if (nCells[pointI] > 0)
+        {
+            displacement[pointI] =
+                sumLocation[pointI]/nCells[pointI]-mesh.points()[pointI];
+            nAdapted++;
+        }
+    }
+
+    reduce(nAdapted, sumOp<label>());
+    Info<< "Smoothing " << nAdapted << " points inbetween refinement regions."
+        << endl;
+
+    return tdisplacement;
+}
+
+
 // Calculate displacement as average of patch points.
-Foam::pointField Foam::autoSnapDriver::smoothPatchDisplacement
+Foam::tmp<Foam::pointField> Foam::autoSnapDriver::smoothPatchDisplacement
 (
     const motionSmoother& meshMover,
     const List<labelPair>& baffles
@@ -327,20 +508,9 @@ Foam::pointField Foam::autoSnapDriver::smoothPatchDisplacement
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     labelList anyCell(mesh.nPoints(), -1);
-    forAll(mesh.faceNeighbour(), faceI)
-    {
-        label own = mesh.faceOwner()[faceI];
-        const face& f = mesh.faces()[faceI];
-
-        forAll(f, fp)
-        {
-            anyCell[f[fp]] = own;
-        }
-    }
-    for (label faceI = mesh.nInternalFaces(); faceI < mesh.nFaces(); faceI++)
+    forAll(mesh.faceOwner(), faceI)
     {
         label own = mesh.faceOwner()[faceI];
-
         const face& f = mesh.faces()[faceI];
 
         forAll(f, fp)
@@ -351,7 +521,8 @@ Foam::pointField Foam::autoSnapDriver::smoothPatchDisplacement
 
 
     // Displacement to calculate.
-    pointField patchDisp(meshPoints.size(), vector::zero);
+    tmp<pointField> tpatchDisp(new pointField(meshPoints.size(), vector::zero));
+    pointField& patchDisp = tpatchDisp();
 
     forAll(pointFaces, i)
     {
@@ -414,7 +585,7 @@ Foam::pointField Foam::autoSnapDriver::smoothPatchDisplacement
         patchDisp[i] = newPos - currentPos;
     }
 
-    return patchDisp;
+    return tpatchDisp;
 }
 //XXXXXXX
 //Foam::tmp<Foam::pointField> Foam::autoSnapDriver::avg
@@ -521,42 +692,6 @@ Foam::tmp<Foam::scalarField> Foam::autoSnapDriver::edgePatchDist
         edgeDist[edgeI] = Foam::sqrt(allEdgeInfo[edgeI].distSqr());
     }
 
-
-    //{
-    //    // For debugging: dump to file
-    //    pointScalarField pointDist
-    //    (
-    //        IOobject
-    //        (
-    //            "pointDist",
-    //            meshRefiner_.timeName(),
-    //            mesh.DB(),
-    //            IOobject::NO_READ,
-    //            IOobject::AUTO_WRITE
-    //        ),
-    //        pMesh,
-    //        dimensionedScalar("pointDist", dimless, 0.0)
-    //    );
-    //
-    //    forAll(allEdgeInfo, edgeI)
-    //    {
-    //        scalar d = Foam::sqrt(allEdgeInfo[edgeI].distSqr());
-    //
-    //        const edge& e = mesh.edges()[edgeI];
-    //
-    //        pointDist[e[0]] += d;
-    //        pointDist[e[1]] += d;
-    //    }
-    //    forAll(pointDist, pointI)
-    //    {
-    //        pointDist[pointI] /= mesh.pointEdges()[pointI].size();
-    //    }
-    //    Info<< "Writing patch distance to " << pointDist.name()
-    //        << " at time " << meshRefiner_.timeName() << endl;
-    //
-    //    pointDist.write();
-    //}
-
     return tedgeDist;
 }
 
@@ -648,35 +783,6 @@ Foam::autoSnapDriver::autoSnapDriver
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-Foam::autoPtr<Foam::mapPolyMesh> Foam::autoSnapDriver::mergeZoneBaffles
-(
-    const List<labelPair>& baffles
-)
-{
-    labelList zonedSurfaces =
-        surfaceZonesInfo::getNamedSurfaces(meshRefiner_.surfaces().surfZones());
-
-    autoPtr<mapPolyMesh> map;
-
-    // No need to sync; all processors will have all same zonedSurfaces.
-    label nBaffles = returnReduce(baffles.size(), sumOp<label>());
-    if (zonedSurfaces.size() && nBaffles > 0)
-    {
-        // Merge any baffles
-        Info<< "Converting " << nBaffles << " baffles back into zoned faces ..."
-            << endl;
-
-        map = meshRefiner_.mergeBaffles(baffles);
-
-        Info<< "Converted baffles in = "
-            << meshRefiner_.mesh().time().cpuTimeIncrement()
-            << " s\n" << nl << endl;
-    }
-
-    return map;
-}
-
-
 Foam::scalarField Foam::autoSnapDriver::calcSnapDistance
 (
     const fvMesh& mesh,
@@ -730,7 +836,15 @@ void Foam::autoSnapDriver::preSmoothPatch
 
     labelList checkFaces;
 
-    Info<< "Smoothing patch points ..." << endl;
+    if (snapParams.nSmoothInternal() > 0)
+    {
+        Info<< "Smoothing patch and internal points ..." << endl;
+    }
+    else
+    {
+        Info<< "Smoothing patch points ..." << endl;
+    }
+
     for
     (
         label smoothIter = 0;
@@ -745,15 +859,26 @@ void Foam::autoSnapDriver::preSmoothPatch
             checkFaces[faceI] = faceI;
         }
 
+        // If enabled smooth the internal points
+        if (snapParams.nSmoothInternal() > smoothIter)
+        {
+            // Override values on internal points on refinement interfaces
+            meshMover.pointDisplacement().internalField() =
+                smoothInternalDisplacement(meshRefiner, meshMover);
+        }
+
+        // Smooth the patch points
         pointField patchDisp(smoothPatchDisplacement(meshMover, baffles));
         //pointField patchDisp
         //(
         //  smoothLambdaMuPatchDisplacement(meshMover, baffles)
         //);
 
-        // The current mesh is the starting mesh to smooth from.
+        // Take over patch displacement as boundary condition on
+        // pointDisplacement
         meshMover.setDisplacement(patchDisp);
 
+        // Start off from current mesh.points()
         meshMover.correct();
 
         scalar oldErrorReduction = -1;
@@ -1566,9 +1691,92 @@ void Foam::autoSnapDriver::detectNearSurfaces
 }
 
 
+void Foam::autoSnapDriver::calcNearestSurface
+(
+    const refinementSurfaces& surfaces,
+
+    const labelList& surfacesToTest,
+    const labelListList& regionsToTest,
+
+    const pointField& localPoints,
+    const labelList& zonePointIndices,
+
+    scalarField& minSnapDist,
+    labelList& snapSurf,
+    vectorField& patchDisp,
+
+    // Optional: nearest point, normal
+    pointField& nearestPoint,
+    vectorField& nearestNormal
+)
+{
+    // Find nearest for points both on faceZone and pp.
+    List<pointIndexHit> hitInfo;
+    labelList hitSurface;
+
+    if (nearestNormal.size() == localPoints.size())
+    {
+        labelList hitRegion;
+        vectorField hitNormal;
+        surfaces.findNearestRegion
+        (
+            surfacesToTest,
+            regionsToTest,
+
+            pointField(localPoints, zonePointIndices),
+            sqr(scalarField(minSnapDist, zonePointIndices)),
+
+            hitSurface,
+            hitInfo,
+            hitRegion,
+            hitNormal
+        );
+
+        forAll(hitInfo, i)
+        {
+            if (hitInfo[i].hit())
+            {
+                label pointI = zonePointIndices[i];
+                nearestPoint[pointI] = hitInfo[i].hitPoint();
+                nearestNormal[pointI] = hitNormal[i];
+            }
+        }
+    }
+    else
+    {
+        surfaces.findNearest
+        (
+            surfacesToTest,
+            regionsToTest,
+
+            pointField(localPoints, zonePointIndices),
+            sqr(scalarField(minSnapDist, zonePointIndices)),
+
+            hitSurface,
+            hitInfo
+        );
+    }
+
+    forAll(hitInfo, i)
+    {
+        if (hitInfo[i].hit())
+        {
+            label pointI = zonePointIndices[i];
+
+            patchDisp[pointI] = hitInfo[i].hitPoint() - localPoints[pointI];
+            minSnapDist[pointI] = mag(patchDisp[pointI]);
+            snapSurf[pointI] = hitSurface[i];
+        }
+    }
+}
+
+
 Foam::vectorField Foam::autoSnapDriver::calcNearestSurface
 (
+    const bool strictRegionSnap,
     const meshRefinement& meshRefiner,
+    const labelList& globalToMasterPatch,
+    const labelList& globalToSlavePatch,
     const scalarField& snapDist,
     const indirectPrimitivePatch& pp,
     pointField& nearestPoint,
@@ -1577,6 +1785,23 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurface
 {
     Info<< "Calculating patchDisplacement as distance to nearest surface"
         << " point ..." << endl;
+    if (strictRegionSnap)
+    {
+        Info<< "    non-zone points : attract to local region on surface only"
+            << nl
+            << "    zone points     : attract to local region on surface only"
+            << nl
+            << endl;
+    }
+    else
+    {
+        Info<< "    non-zone points :"
+            << " attract to nearest of all non-zone surfaces"
+            << nl
+            << "    zone points     : attract to zone surface only" << nl
+            << endl;
+    }
+
 
     const pointField& localPoints = pp.localPoints();
     const refinementSurfaces& surfaces = meshRefiner.surfaces();
@@ -1587,26 +1812,109 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurface
 
     if (returnReduce(localPoints.size(), sumOp<label>()) > 0)
     {
-        // Current surface snapped to
+        // Current surface snapped to. Used to check whether points have been
+        // snapped at all
         labelList snapSurf(localPoints.size(), -1);
 
-        // Divide surfaces into zoned and unzoned
-        const labelList zonedSurfaces =
-            surfaceZonesInfo::getNamedSurfaces
-            (
-                meshRefiner.surfaces().surfZones()
-            );
-        const labelList unzonedSurfaces =
-            surfaceZonesInfo::getUnnamedSurfaces
-            (
-                meshRefiner.surfaces().surfZones()
-            );
+        // Current best snap distance (since point might be on multiple
+        // regions)
+        scalarField minSnapDist(snapDist);
+
+
+        if (strictRegionSnap)
+        {
+            // Attract patch points to same region only
+
+            forAll(surfaces.surfaces(), surfI)
+            {
+                label geomI = surfaces.surfaces()[surfI];
+                label nRegions = surfaces.geometry()[geomI].regions().size();
+
+                const labelList surfacesToTest(1, surfI);
+
+                for (label regionI = 0; regionI < nRegions; regionI++)
+                {
+                    label globalI = surfaces.globalRegion(surfI, regionI);
+                    label masterPatchI = globalToMasterPatch[globalI];
+
+                    // Get indices of points both on patch and on pp
+                    labelList zonePointIndices
+                    (
+                        getFacePoints
+                        (
+                            pp,
+                            mesh.boundaryMesh()[masterPatchI]
+                        )
+                    );
+
+                    calcNearestSurface
+                    (
+                        surfaces,
+
+                        surfacesToTest,
+                        labelListList(1, labelList(1, regionI)), //regionsToTest
+
+                        localPoints,
+                        zonePointIndices,
+
+                        minSnapDist,
+                        snapSurf,
+                        patchDisp,
+
+                        // Optional: nearest point, normal
+                        nearestPoint,
+                        nearestNormal
+                    );
+
+                    if (globalToSlavePatch[globalI] != masterPatchI)
+                    {
+                        label slavePatchI = globalToSlavePatch[globalI];
+
+                        // Get indices of points both on patch and on pp
+                        labelList zonePointIndices
+                        (
+                            getFacePoints
+                            (
+                                pp,
+                                mesh.boundaryMesh()[slavePatchI]
+                            )
+                        );
+
+                        calcNearestSurface
+                        (
+                            surfaces,
 
+                            surfacesToTest,
+                            labelListList(1, labelList(1, regionI)),
 
-        // 1. All points to non-interface surfaces
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                            localPoints,
+                            zonePointIndices,
 
+                            minSnapDist,
+                            snapSurf,
+                            patchDisp,
+
+                            // Optional: nearest point, normal
+                            nearestPoint,
+                            nearestNormal
+                        );
+                    }
+                }
+            }
+        }
+        else
         {
+            // Divide surfaces into zoned and unzoned
+            const labelList unzonedSurfaces =
+                surfaceZonesInfo::getUnnamedSurfaces
+                (
+                    meshRefiner.surfaces().surfZones()
+                );
+
+
+            // 1. All points to non-interface surfaces
+            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
             List<pointIndexHit> hitInfo;
             labelList hitSurface;
 
@@ -1657,100 +1965,67 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurface
                     snapSurf[pointI] = hitSurface[pointI];
                 }
             }
-        }
 
 
+            const labelList zonedSurfaces =
+                surfaceZonesInfo::getNamedSurfaces
+                (
+                    meshRefiner.surfaces().surfZones()
+                );
 
-        // 2. All points on zones to their respective surface
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-        // Surfaces with zone information
-        const PtrList<surfaceZonesInfo>& surfZones = surfaces.surfZones();
+            // 2. All points on zones to their respective surface
+            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-        // Current best snap distance
-        scalarField minSnapDist(snapDist);
+            // Surfaces with zone information
+            const PtrList<surfaceZonesInfo>& surfZones = surfaces.surfZones();
 
-        forAll(zonedSurfaces, i)
-        {
-            label zoneSurfI = zonedSurfaces[i];
+            forAll(zonedSurfaces, i)
+            {
+                label surfI = zonedSurfaces[i];
 
-            const word& faceZoneName = surfZones[zoneSurfI].faceZoneName();
+                const word& faceZoneName = surfZones[surfI].faceZoneName();
 
-            const labelList surfacesToTest(1, zoneSurfI);
+                const labelList surfacesToTest(1, surfI);
 
-            // Get indices of points both on faceZone and on pp.
-            labelList zonePointIndices
-            (
-                getZoneSurfacePoints
-                (
-                    mesh,
-                    pp,
-                    faceZoneName
-                )
-            );
+                label geomI = surfaces.surfaces()[surfI];
+                label nRegions = surfaces.geometry()[geomI].regions().size();
 
-            // Find nearest for points both on faceZone and pp.
-            List<pointIndexHit> hitInfo;
-            labelList hitSurface;
 
-            if (nearestNormal.size() == localPoints.size())
-            {
-                labelList hitRegion;
-                vectorField hitNormal;
-                surfaces.findNearestRegion
+                // Get indices of points both on faceZone and on pp.
+                labelList zonePointIndices
                 (
-                    surfacesToTest,
-                    pointField(localPoints, zonePointIndices),
-                    sqr(scalarField(minSnapDist, zonePointIndices)),
-                    hitSurface,
-                    hitInfo,
-                    hitRegion,
-                    hitNormal
+                    getZoneSurfacePoints
+                    (
+                        mesh,
+                        pp,
+                        faceZoneName
+                    )
                 );
 
-                forAll(hitInfo, i)
-                {
-                    if (hitInfo[i].hit())
-                    {
-                        label pointI = zonePointIndices[i];
-                        nearestPoint[pointI] = hitInfo[i].hitPoint();
-                        nearestNormal[pointI] = hitNormal[i];
-                    }
-                }
-            }
-            else
-            {
-                surfaces.findNearest
+
+                calcNearestSurface
                 (
-                    surfacesToTest,
-                    pointField(localPoints, zonePointIndices),
-                    sqr(scalarField(minSnapDist, zonePointIndices)),
-                    hitSurface,
-                    hitInfo
-                );
-            }
+                    surfaces,
 
-            forAll(hitInfo, i)
-            {
-                label pointI = zonePointIndices[i];
+                    surfacesToTest,
+                    labelListList(1, identity(nRegions)),
 
-                if (hitInfo[i].hit())
-                {
-                    patchDisp[pointI] =
-                        hitInfo[i].hitPoint()
-                      - localPoints[pointI];
+                    localPoints,
+                    zonePointIndices,
 
-                    minSnapDist[pointI] = min
-                    (
-                        minSnapDist[pointI],
-                        mag(patchDisp[pointI])
-                    );
+                    minSnapDist,
+                    snapSurf,
+                    patchDisp,
 
-                    snapSurf[pointI] = zoneSurfI;
-                }
+                    // Optional: nearest point, normal
+                    nearestPoint,
+                    nearestNormal
+                );
             }
         }
 
+
         // Check if all points are being snapped
         forAll(snapSurf, pointI)
         {
@@ -1788,7 +2063,8 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurface
         << mesh.time().cpuTimeIncrement() << " s\n" << nl << endl;
 
 
-    // Limit amount of movement.
+    // Limit amount of movement. Can not happen for triSurfaceMesh but
+    // can happen for some analytical shapes?
     forAll(patchDisp, patchPointI)
     {
         scalar magDisp = mag(patchDisp[patchPointI]);
@@ -1818,254 +2094,6 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurface
 }
 
 
-////XXXXXXXXX
-//// Get (pp-local) indices of points that are on both patches
-//Foam::labelList Foam::autoSnapDriver::getPatchSurfacePoints
-//(
-//    const fvMesh& mesh,
-//    const indirectPrimitivePatch& allPp,
-//    const polyPatch& pp
-//)
-//{
-//    // Could use PrimitivePatch & localFaces to extract points but might just
-//    // as well do it ourselves.
-//
-//    boolList pointOnZone(allPp.nPoints(), false);
-//
-//    forAll(pp, i)
-//    {
-//        const face& f = pp[i];
-//
-//        forAll(f, fp)
-//        {
-//            label meshPointI = f[fp];
-//
-//            Map<label>::const_iterator iter =
-//                allPp.meshPointMap().find(meshPointI);
-//
-//            if (iter != allPp.meshPointMap().end())
-//            {
-//                label pointI = iter();
-//                pointOnZone[pointI] = true;
-//            }
-//        }
-//    }
-//
-//    return findIndices(pointOnZone, true);
-//}
-//Foam::vectorField Foam::autoSnapDriver::calcNearestLocalSurface
-//(
-//    const meshRefinement& meshRefiner,
-//    const scalarField& snapDist,
-//    const indirectPrimitivePatch& pp
-//)
-//{
-//    Info<< "Calculating patchDisplacement as distance to nearest"
-//        << " local surface point ..." << endl;
-//
-//    const pointField& localPoints = pp.localPoints();
-//    const refinementSurfaces& surfaces = meshRefiner.surfaces();
-//    const fvMesh& mesh = meshRefiner.mesh();
-//    const polyBoundaryMesh& pbm = mesh.boundaryMesh();
-//
-//
-////    // Assume that all patch-internal points get attracted to their surface
-////    // only. So we want to know if point is on multiple regions
-////
-////    labelList minPatch(mesh.nPoints(), labelMax);
-////    labelList maxPatch(mesh.nPoints(), labelMin);
-////
-////    forAll(meshMover.adaptPatchIDs(), i)
-////    {
-////        label patchI = meshMover.adaptPatchIDs()[i];
-////        const labelList& meshPoints = pbm[patchI].meshPoints();
-////
-////        forAll(meshPoints, meshPointI)
-////        {
-////            label meshPointI = meshPoints[meshPointI];
-////            minPatch[meshPointI] = min(minPatch[meshPointI], patchI);
-////            maxPatch[meshPointI] = max(maxPatch[meshPointI], patchI);
-////        }
-////    }
-////
-////    syncTools::syncPointList
-////    (
-////        mesh,
-////        minPatch,
-////        minEqOp<label>(),   // combine op
-////        labelMax            // null value
-////    );
-////    syncTools::syncPointList
-////    (
-////        mesh,
-////        maxPatch,
-////        maxEqOp<label>(),   // combine op
-////        labelMin            // null value
-////    );
-//
-//    // Now all points with minPatch != maxPatch will be on the outside of
-//    // the patch.
-//
-//    // Displacement per patch point
-//    vectorField patchDisp(localPoints.size(), vector::zero);
-//    // Current best snap distance
-//    scalarField minSnapDist(snapDist);
-//    // Current surface snapped to
-//    labelList snapSurf(localPoints.size(), -1);
-//
-//    const labelList& surfaceGeometry = surfaces.surfaces();
-//    forAll(surfaceGeometry, surfI)
-//    {
-//        label geomI = surfaceGeometry[surfI];
-//        const wordList& regNames = allGeometry.regionNames()[geomI];
-//        forAll(regNames, regionI)
-//        {
-//            label globalRegionI = surfaces.globalRegion(surfI, regionI);
-//            // Collect master patch points
-//            label masterPatchI = globalToMasterPatch_[globalRegionI];
-//            label slavePatchI = globalToSlavePatch_[globalRegionI];
-//
-//            labelList patchPointIndices
-//            (
-//                getPatchSurfacePoints
-//                (
-//                    mesh,
-//                    pp,
-//                    pbm[masterPatchI]
-//                )
-//            );
-//
-//            // Find nearest for points both on faceZone and pp.
-//            List<pointIndexHit> hitInfo;
-//            surfaces.findNearest
-//            (
-//                surfI,
-//                regionI,
-//                pointField(localPoints, patchPointIndices),
-//                sqr(scalarField(minSnapDist, patchPointIndices)),
-//                hitInfo
-//            );
-//
-//            forAll(hitInfo, i)
-//            {
-//                label pointI = patchPointIndices[i];
-//
-//                if (hitInfo[i].hit())
-//                {
-//                    const point& pt = hitInfo[i].hitPoint();
-//                    patchDisp[pointI] = pt-localPoints[pointI];
-//                    minSnapDist[pointI] = min
-//                    (
-//                        minSnapDist[pointI],
-//                        mag(patchDisp[pointI])
-//                    );
-//                    snapSurf[pointI] = surfI;
-//                }
-//            }
-//
-//            // Slave patch
-//            if (slavePatchI != masterPatchI)
-//            {
-//                labelList patchPointIndices
-//                (
-//                    getPatchSurfacePoints
-//                    (
-//                        mesh,
-//                        pp,
-//                        pbm[slavePatchI]
-//                    )
-//                );
-//
-//                // Find nearest for points both on faceZone and pp.
-//                List<pointIndexHit> hitInfo;
-//                surfaces.findNearest
-//                (
-//                    surfI,
-//                    regionI,
-//                    pointField(localPoints, patchPointIndices),
-//                    sqr(scalarField(minSnapDist, patchPointIndices)),
-//                    hitInfo
-//                );
-//
-//                forAll(hitInfo, i)
-//                {
-//                    label pointI = patchPointIndices[i];
-//
-//                    if (hitInfo[i].hit())
-//                    {
-//                        const point& pt = hitInfo[i].hitPoint();
-//                        patchDisp[pointI] = pt-localPoints[pointI];
-//                        minSnapDist[pointI] = min
-//                        (
-//                            minSnapDist[pointI],
-//                            mag(patchDisp[pointI])
-//                        );
-//                        snapSurf[pointI] = surfI;
-//                    }
-//                }
-//            }
-//        }
-//    }
-//
-//
-//    // Check if all points are being snapped
-//    forAll(snapSurf, pointI)
-//    {
-//        if (snapSurf[pointI] == -1)
-//        {
-//            WarningIn("autoSnapDriver::calcNearestLocalSurface(..)")
-//                << "For point:" << pointI
-//                << " coordinate:" << localPoints[pointI]
-//                << " did not find any surface within:"
-//                << minSnapDist[pointI]
-//                << " metre." << endl;
-//        }
-//    }
-//
-//    {
-//        scalarField magDisp(mag(patchDisp));
-//
-//        Info<< "Wanted displacement : average:"
-//            << gSum(magDisp)/returnReduce(patchDisp.size(), sumOp<label>())
-//            << " min:" << gMin(magDisp)
-//            << " max:" << gMax(magDisp) << endl;
-//    }
-//
-//
-//    Info<< "Calculated surface displacement in = "
-//        << mesh.time().cpuTimeIncrement() << " s\n" << nl << endl;
-//
-//
-//    // Limit amount of movement.
-//    forAll(patchDisp, patchPointI)
-//    {
-//        scalar magDisp = mag(patchDisp[patchPointI]);
-//
-//        if (magDisp > snapDist[patchPointI])
-//        {
-//            patchDisp[patchPointI] *= snapDist[patchPointI] / magDisp;
-//
-//            Pout<< "Limiting displacement for " << patchPointI
-//                << " from " << magDisp << " to " << snapDist[patchPointI]
-//                << endl;
-//        }
-//    }
-//
-//    // Points on zones in one domain but only present as point on other
-//    // will not do condition 2 on all. Sync explicitly.
-//    syncTools::syncPointList
-//    (
-//        mesh,
-//        pp.meshPoints(),
-//        patchDisp,
-//        minMagSqrEqOp<point>(),    // combine op
-//        vector(GREAT, GREAT, GREAT)// null value (note: cannot use VGREAT)
-//    );
-//
-//    return patchDisp;
-//}
-////XXXXXXXXX
-
 void Foam::autoSnapDriver::smoothDisplacement
 (
     const snapParameters& snapParams,
@@ -2465,10 +2493,45 @@ void Foam::autoSnapDriver::detectWarpedFaces
 }
 
 
+Foam::labelList Foam::autoSnapDriver::getInternalOrBaffleDuplicateFace() const
+{
+    const fvMesh& mesh = meshRefiner_.mesh();
+
+    labelList internalOrBaffleFaceZones;
+    {
+        List<surfaceZonesInfo::faceZoneType> fzTypes(2);
+        fzTypes[0] = surfaceZonesInfo::INTERNAL;
+        fzTypes[1] = surfaceZonesInfo::BAFFLE;
+        internalOrBaffleFaceZones = meshRefiner_.getZones(fzTypes);
+    }
+
+    List<labelPair> baffles
+    (
+        meshRefiner_.subsetBaffles
+        (
+            mesh,
+            internalOrBaffleFaceZones,
+            localPointRegion::findDuplicateFacePairs(mesh)
+        )
+    );
+
+    labelList faceToDuplicate(mesh.nFaces(), -1);
+    forAll(baffles, i)
+    {
+        const labelPair& p = baffles[i];
+        faceToDuplicate[p[0]] = p[1];
+        faceToDuplicate[p[1]] = p[0];
+    }
+
+    return faceToDuplicate;
+}
+
+
 void Foam::autoSnapDriver::doSnap
 (
     const dictionary& snapDict,
     const dictionary& motionDict,
+    const bool mergePatchFaces,
     const scalar featureCos,
     const scalar planarAngle,
     const snapParameters& snapParams
@@ -2481,10 +2544,6 @@ void Foam::autoSnapDriver::doSnap
         << "--------------" << nl
         << endl;
 
-    // Get the labels of added patches.
-    labelList adaptPatchIDs(meshRefiner_.meshedPatches());
-
-
     // faceZone handling
     // ~~~~~~~~~~~~~~~~~
     //
@@ -2499,233 +2558,55 @@ void Foam::autoSnapDriver::doSnap
     //
     // internal
     // --------
-    // - baffles: contains all faces on faceZone so
-    //      - mesh checks check across baffles
-    //      - they get back merged into internal faces
+    // - baffles: need to be checked across
     // - duplicateFace: from face to duplicate face. Contains
     //   all faces on faceZone to prevents merging patch faces.
     //
     // baffle
     // ------
-    // - baffles: contains no faces on faceZone since need not be merged/checked
-    //   across
+    // - baffles: no need to be checked across
     // - duplicateFace: contains all faces on faceZone to prevent
     //   merging patch faces.
     //
     // boundary
     // --------
-    // - baffles: contains no faces on faceZone since need not be merged/checked
-    //   across
+    // - baffles: no need to be checked across. Also points get duplicated
+    //            so will no longer be baffles
     // - duplicateFace: contains no faces on faceZone since both sides can
     //   merge faces independently.
 
 
-    // Create baffles (pairs of faces that share the same points)
-    // Baffles stored as owner and neighbour face that have been created.
-    List<labelPair> baffles;
-    meshRefiner_.createZoneBaffles
+
+    // faceZones of type internal
+    const labelList internalFaceZones
     (
-        globalToMasterPatch_,
-        globalToSlavePatch_,
-        baffles
+        meshRefiner_.getZones
+        (
+            List<surfaceZonesInfo::faceZoneType>
+            (
+                1,
+                surfaceZonesInfo::INTERNAL
+            )
+        )
     );
 
-    // Maintain map from face to baffle face (-1 for non-baffle faces). Used
-    // later on to prevent patchface merging if faceType=baffle
-    labelList duplicateFace(mesh.nFaces(), -1);
 
-    forAll(baffles, i)
-    {
-        const labelPair& baffle = baffles[i];
-        duplicateFace[baffle.first()] = baffle.second();
-        duplicateFace[baffle.second()] = baffle.first();
-    }
-
-    // Selectively 'forget' about the baffles, i.e. not check across them
-    // or merge across them.
+    // Create baffles (pairs of faces that share the same points)
+    // Baffles stored as owner and neighbour face that have been created.
     {
-        const faceZoneMesh& fZones = mesh.faceZones();
-        const refinementSurfaces& surfaces = meshRefiner_.surfaces();
-        const PtrList<surfaceZonesInfo>& surfZones = surfaces.surfZones();
-
-        // Determine which
-        //  - faces to remove from list of baffles (so not merge)
-        //  - points to duplicate
-
-        // Per face if is on faceType 'baffle' or 'boundary'
-        labelList filterFace(mesh.nFaces(), -1);
-        label nFilterFaces = 0;
-        // Per point whether it need to be duplicated
-        PackedBoolList duplicatePoint(mesh.nPoints());
-        label nDuplicatePoints = 0;
-        forAll(surfZones, surfI)
-        {
-            const word& faceZoneName = surfZones[surfI].faceZoneName();
-
-            if (faceZoneName.size())
-            {
-                const surfaceZonesInfo::faceZoneType& faceType =
-                    surfZones[surfI].faceType();
-
-                if
-                (
-                    faceType == surfaceZonesInfo::BAFFLE
-                 || faceType == surfaceZonesInfo::BOUNDARY
-                )
-                {
-                    // Filter out all faces for this zone.
-                    label zoneI = fZones.findZoneID(faceZoneName);
-                    const faceZone& fZone = fZones[zoneI];
-                    forAll(fZone, i)
-                    {
-                        label faceI = fZone[i];
-                        filterFace[faceI] = zoneI;
-                        nFilterFaces++;
-                    }
-
-                    if (faceType == surfaceZonesInfo::BOUNDARY)
-                    {
-                        forAll(fZone, i)
-                        {
-                            label faceI = fZone[i];
-
-                            // Allow combining patch faces across this face
-                            duplicateFace[faceI] = -1;
-
-                            const face& f = mesh.faces()[faceI];
-                            forAll(f, fp)
-                            {
-                                if (!duplicatePoint[f[fp]])
-                                {
-                                    duplicatePoint[f[fp]] = 1;
-                                    nDuplicatePoints++;
-                                }
-                            }
-                        }
-                    }
-
-                    Info<< "Surface : " << surfaces.names()[surfI] << nl
-                        << "    faces to become baffle : "
-                        << returnReduce(nFilterFaces, sumOp<label>()) << nl
-                        << "    points to duplicate    : "
-                        << returnReduce(nDuplicatePoints, sumOp<label>())
-                        << endl;
-                }
-            }
-        }
-
-        // Duplicate points only if all points agree
-        syncTools::syncPointList
+        List<labelPair> baffles;
+        labelList originatingFaceZone;
+        meshRefiner_.createZoneBaffles
         (
-            mesh,
-            duplicatePoint,
-            andEqOp<unsigned int>(),    // combine op
-            0u                          // null value
+            identity(mesh.faceZones().size()),
+            baffles,
+            originatingFaceZone
         );
-        // Mark as duplicate (avoids combining patch faces) if one or both
-        syncTools::syncFaceList(mesh, duplicateFace, maxEqOp<label>());
-        // Mark as resulting from baffle/boundary face zone only if both agree
-        syncTools::syncFaceList(mesh, filterFace, minEqOp<label>());
-
-        // Duplicate points
-        if (returnReduce(nDuplicatePoints, sumOp<label>()) > 0)
-        {
-            // Collect all points (recount since syncPointList might have
-            // increased set)
-            nDuplicatePoints = 0;
-            forAll(duplicatePoint, pointI)
-            {
-                if (duplicatePoint[pointI])
-                {
-                    nDuplicatePoints++;
-                }
-            }
-            labelList candidatePoints(nDuplicatePoints);
-            nDuplicatePoints = 0;
-            forAll(duplicatePoint, pointI)
-            {
-                if (duplicatePoint[pointI])
-                {
-                    candidatePoints[nDuplicatePoints++] = pointI;
-                }
-            }
-
-
-            localPointRegion regionSide(mesh, candidatePoints);
-            autoPtr<mapPolyMesh> mapPtr = meshRefiner_.dupNonManifoldPoints
-            (
-                regionSide
-            );
-            meshRefinement::updateList
-            (
-                mapPtr().faceMap(),
-                label(-1),
-                filterFace
-            );
-            meshRefinement::updateList
-            (
-                mapPtr().faceMap(),
-                label(-1),
-                duplicateFace
-            );
-
-            // Update baffles and baffle-to-baffle addressing
-
-            const labelList& reverseFaceMap = mapPtr().reverseFaceMap();
-
-            forAll(baffles, i)
-            {
-                labelPair& baffle = baffles[i];
-                baffle.first() = reverseFaceMap[baffle.first()];
-                baffle.second() = reverseFaceMap[baffle.second()];
-            }
-
-            if (debug&meshRefinement::MESH)
-            {
-                const_cast<Time&>(mesh.time())++;
-                Pout<< "Writing duplicatedPoints mesh to time "
-                    << meshRefiner_.timeName()
-                    << endl;
-                meshRefiner_.write
-                (
-                    meshRefinement::debugType(debug),
-                    meshRefinement::writeType
-                    (
-                        meshRefinement::writeLevel()
-                      | meshRefinement::WRITEMESH
-                    ),
-                    mesh.time().path()/"duplicatedPoints"
-                );
-            }
-        }
-
-
-        // Forget about baffles in a BAFFLE/BOUNDARY type zone
-        DynamicList<labelPair> newBaffles(baffles.size());
-        forAll(baffles, i)
-        {
-            const labelPair& baffle = baffles[i];
-            if
-            (
-                filterFace[baffle.first()] == -1
-             && filterFace[baffles[i].second()] == -1
-            )
-            {
-                newBaffles.append(baffle);
-            }
-        }
-
-        if (newBaffles.size() < baffles.size())
-        {
-            //Info<< "Splitting baffles into" << nl
-            //    << "    internal : " << newBaffles.size() << nl
-            //    << "    baffle   : " << baffles.size()-newBaffles.size()
-            //    << nl << endl;
-            baffles.transfer(newBaffles);
-        }
-        Info<< endl;
     }
 
+    // Duplicate points on faceZones of type boundary
+    meshRefiner_.dupNonManifoldBoundaryPoints();
+
 
     bool doFeatures = false;
     label nFeatIter = 1;
@@ -2741,6 +2622,12 @@ void Foam::autoSnapDriver::doSnap
 
     bool meshOk = false;
 
+
+    // Get the labels of added patches.
+    labelList adaptPatchIDs(meshRefiner_.meshedPatches());
+
+
+
     {
         autoPtr<indirectPrimitivePatch> ppPtr
         (
@@ -2753,7 +2640,7 @@ void Foam::autoSnapDriver::doSnap
 
 
         // Distance to attract to nearest feature on surface
-        const scalarField snapDist(calcSnapDistance(mesh, snapParams, ppPtr()));
+        scalarField snapDist(calcSnapDistance(mesh, snapParams, ppPtr()));
 
 
         // Construct iterative mesh mover.
@@ -2795,13 +2682,27 @@ void Foam::autoSnapDriver::doSnap
         Info<< "Checked initial mesh in = "
             << mesh.time().cpuTimeIncrement() << " s\n" << nl << endl;
 
+        // Extract baffles across internal faceZones (for checking mesh quality
+        // across
+        labelPairList internalBaffles
+        (
+            meshRefiner_.subsetBaffles
+            (
+                mesh,
+                internalFaceZones,
+                localPointRegion::findDuplicateFacePairs(mesh)
+            )
+        );
+
+
+
         // Pre-smooth patch vertices (so before determining nearest)
         preSmoothPatch
         (
             meshRefiner_,
             snapParams,
             nInitErrors,
-            baffles,
+            internalBaffles,
             meshMoverPtr()
         );
 
@@ -2814,235 +2715,35 @@ void Foam::autoSnapDriver::doSnap
         List<pointConstraint> patchConstraints;
 
 
-        for (label iter = 0; iter < nFeatIter; iter++)
-        {
-            //if (doFeatures && (iter == 0 || iter == nFeatIter/2))
-            //{
-            //    Info<< "Splitting diagonal attractions" << endl;
-            //
-            //    indirectPrimitivePatch& pp = ppPtr();
-            //    motionSmoother& meshMover = meshMoverPtr();
-            //
-            //    // Calculate displacement at every patch point. Insert into
-            //    // meshMover.
-            //    // Calculate displacement at every patch point
-            //    pointField nearestPoint;
-            //    vectorField nearestNormal;
-            //
-            //    if (snapParams.detectNearSurfacesSnap())
-            //    {
-            //        nearestPoint.setSize(pp.nPoints(), vector::max);
-            //        nearestNormal.setSize(pp.nPoints(), vector::zero);
-            //    }
-            //
-            //    vectorField disp = calcNearestSurface
-            //    (
-            //        meshRefiner_,
-            //        snapDist,
-            //        pp,
-            //        nearestPoint,
-            //        nearestNormal
-            //    );
-            //
-            //
-            //    // Override displacement at thin gaps
-            //    if (snapParams.detectNearSurfacesSnap())
-            //    {
-            //        detectNearSurfaces
-            //        (
-            //            Foam::cos(degToRad(planarAngle)),// planar gaps
-            //            pp,
-            //            nearestPoint,   // surfacepoint from nearest test
-            //            nearestNormal,  // surfacenormal from nearest test
-            //
-            //            disp
-            //        );
-            //    }
-            //
-            //    // Override displacement with feature edge attempt
-            //    const label iter = 0;
-            //    calcNearestSurfaceFeature
-            //    (
-            //        snapParams,
-            //        false,   // avoidSnapProblems
-            //        iter,
-            //        featureCos,
-            //        scalar(iter+1)/nFeatIter,
-            //        snapDist,
-            //        disp,
-            //        meshMover,
-            //        patchAttraction,
-            //        patchConstraints
-            //    );
-            //
-            //
-            //    const labelList& bFaces = ppPtr().addressing();
-            //    DynamicList<label> splitFaces(bFaces.size());
-            //    DynamicList<labelPair> splits(bFaces.size());
-            //
-            //    forAll(bFaces, faceI)
-            //    {
-            //        const labelPair split
-            //        (
-            //            findDiagonalAttraction
-            //            (
-            //                ppPtr(),
-            //                patchAttraction,
-            //                patchConstraints,
-            //                faceI
-            //            )
-            //        );
-            //
-            //        if (split != labelPair(-1, -1))
-            //        {
-            //            splitFaces.append(bFaces[faceI]);
-            //            splits.append(split);
-            //        }
-            //    }
-            //
-            //    Info<< "Splitting "
-            //        << returnReduce(splitFaces.size(), sumOp<label>())
-            //        << " faces along diagonal attractions" << endl;
-            //
-            //    autoPtr<mapPolyMesh> mapPtr = meshRefiner_.splitFaces
-            //    (
-            //        splitFaces,
-            //        splits
-            //    );
-            //
-            //    const labelList& faceMap = mapPtr().faceMap();
-            //    meshRefinement::updateList(faceMap, -1, duplicateFace);
-            //    const labelList& reverseFaceMap = mapPtr().reverseFaceMap();
-            //    forAll(baffles, i)
-            //    {
-            //        labelPair& baffle = baffles[i];
-            //        baffle.first() = reverseFaceMap[baffle.first()];
-            //        baffle.second() = reverseFaceMap[baffle.second()];
-            //    }
-            //
-            //    meshMoverPtr.clear();
-            //    ppPtr.clear();
-            //
-            //    ppPtr = meshRefinement::makePatch(mesh, adaptPatchIDs);
-            //    meshMoverPtr.reset
-            //    (
-            //        new motionSmoother
-            //        (
-            //            mesh,
-            //            ppPtr(),
-            //            adaptPatchIDs,
-            //            meshRefinement::makeDisplacementField
-            //            (
-            //                pointMesh::New(mesh),
-            //                adaptPatchIDs
-            //            ),
-            //            motionDict
-            //        )
-            //    );
-            //
-            //    if (debug&meshRefinement::MESH)
-            //    {
-            //        const_cast<Time&>(mesh.time())++;
-            //        Info<< "Writing split diagonal mesh to time "
-            //            << meshRefiner_.timeName() << endl;
-            //        meshRefiner_.write
-            //        (
-            //            meshRefinement::debugType(debug),
-            //            meshRefinement::writeType
-            //            (
-            //                meshRefinement::writeLevel()
-            //              | meshRefinement::WRITEMESH
-            //            ),
-            //            mesh.time().path()/meshRefiner_.timeName()
-            //        );
-            //    }
-            //}
-            //else
-            //if
-            //(
-            //    doFeatures
-            // && (iter == 1 || iter == nFeatIter/2+1 || iter == nFeatIter-1)
-            //)
-            //{
-            //    Info<< "Splitting warped faces" << endl;
-            //
-            //    const labelList& bFaces = ppPtr().addressing();
-            //    DynamicList<label> splitFaces(bFaces.size());
-            //    DynamicList<labelPair> splits(bFaces.size());
-            //
-            //    detectWarpedFaces
-            //    (
-            //        featureCos,
-            //        ppPtr(),
-            //
-            //        splitFaces,
-            //        splits
-            //    );
-            //
-            //    Info<< "Splitting "
-            //        << returnReduce(splitFaces.size(), sumOp<label>())
-            //        << " faces along diagonal to avoid warpage" << endl;
-            //
-            //    autoPtr<mapPolyMesh> mapPtr = meshRefiner_.splitFaces
-            //    (
-            //        splitFaces,
-            //        splits
-            //    );
-            //
-            //    const labelList& faceMap = mapPtr().faceMap();
-            //    meshRefinement::updateList(faceMap, -1, duplicateFace);
-            //    const labelList& reverseFaceMap = mapPtr().reverseFaceMap();
-            //    forAll(baffles, i)
-            //    {
-            //        labelPair& baffle = baffles[i];
-            //        baffle.first() = reverseFaceMap[baffle.first()];
-            //        baffle.second() = reverseFaceMap[baffle.second()];
-            //    }
-            //
-            //    meshMoverPtr.clear();
-            //    ppPtr.clear();
-            //
-            //    ppPtr = meshRefinement::makePatch(mesh, adaptPatchIDs);
-            //    meshMoverPtr.reset
-            //    (
-            //        new motionSmoother
-            //        (
-            //            mesh,
-            //            ppPtr(),
-            //            adaptPatchIDs,
-            //            meshRefinement::makeDisplacementField
-            //            (
-            //                pointMesh::New(mesh),
-            //                adaptPatchIDs
-            //            ),
-            //            motionDict
-            //        )
-            //    );
-            //
-            //    if (debug&meshRefinement::MESH)
-            //    {
-            //        const_cast<Time&>(mesh.time())++;
-            //        Info<< "Writing split warped mesh to time "
-            //            << meshRefiner_.timeName() << endl;
-            //        meshRefiner_.write
-            //        (
-            //            meshRefinement::debugType(debug),
-            //            meshRefinement::writeType
-            //            (
-            //                meshRefinement::writeLevel()
-            //              | meshRefinement::WRITEMESH
-            //            ),
-            //            mesh.time().path()/meshRefiner_.timeName()
-            //        );
-            //    }
-            //}
-
+        //- Any faces to split
+        DynamicList<label> splitFaces;
+        //- Indices in face to split across
+        DynamicList<labelPair> splits;
 
 
+        for (label iter = 0; iter < nFeatIter; iter++)
+        {
             Info<< nl
                 << "Morph iteration " << iter << nl
                 << "-----------------" << endl;
 
+            // Splitting iteration?
+            bool doSplit = false;
+            if
+            (
+                doFeatures
+             && snapParams.nFaceSplitInterval() > 0
+             && (
+                    (iter == nFeatIter-1)
+                 || (iter > 0 && (iter%snapParams.nFaceSplitInterval()) == 0)
+                )
+            )
+            {
+                doSplit = true;
+            }
+
+
+
             indirectPrimitivePatch& pp = ppPtr();
             motionSmoother& meshMover = meshMoverPtr();
 
@@ -3061,9 +2762,13 @@ void Foam::autoSnapDriver::doSnap
 
             vectorField disp = calcNearestSurface
             (
+                snapParams.strictRegionSnap(),  // attract points to region only
                 meshRefiner_,
+                globalToMasterPatch_,           // for if strictRegionSnap
+                globalToSlavePatch_,            // for if strictRegionSnap
                 snapDist,
                 pp,
+
                 nearestPoint,
                 nearestNormal
             );
@@ -3086,18 +2791,26 @@ void Foam::autoSnapDriver::doSnap
             // Override displacement with feature edge attempt
             if (doFeatures)
             {
+                splitFaces.clear();
+                splits.clear();
                 disp = calcNearestSurfaceFeature
                 (
                     snapParams,
-                    true,   // avoidSnapProblems
+                    !doSplit,       // alignMeshEdges
                     iter,
                     featureCos,
                     scalar(iter+1)/nFeatIter,
+
                     snapDist,
                     disp,
+                    nearestNormal,
                     meshMover,
+
                     patchAttraction,
-                    patchConstraints
+                    patchConstraints,
+
+                    splitFaces,
+                    splits
                 );
             }
 
@@ -3129,7 +2842,7 @@ void Foam::autoSnapDriver::doSnap
             (
                 snapParams,
                 nInitErrors,
-                baffles,
+                internalBaffles,
                 meshMover
             );
 
@@ -3169,71 +2882,190 @@ void Foam::autoSnapDriver::doSnap
 
             // Use current mesh as base mesh
             meshMover.correct();
+
+
+
+            // See if any faces need splitting
+            label nTotalSplit = returnReduce(splitFaces.size(), sumOp<label>());
+            if (nTotalSplit && doSplit)
+            {
+                // Filter out baffle faces from faceZones of type
+                // internal/baffle
+
+                labelList duplicateFace(getInternalOrBaffleDuplicateFace());
+
+                {
+                    labelList oldSplitFaces(splitFaces.xfer());
+                    List<labelPair> oldSplits(splits.xfer());
+                    forAll(oldSplitFaces, i)
+                    {
+                        if (duplicateFace[oldSplitFaces[i]] == -1)
+                        {
+                            splitFaces.append(oldSplitFaces[i]);
+                            splits.append(oldSplits[i]);
+                        }
+                    }
+                    nTotalSplit = returnReduce
+                    (
+                        splitFaces.size(),
+                        sumOp<label>()
+                    );
+                }
+
+                // Update mesh
+                meshRefiner_.splitFacesUndo
+                (
+                    splitFaces,
+                    splits,
+                    motionDict,
+
+                    duplicateFace,
+                    internalBaffles
+                );
+
+                // Redo meshMover
+                meshMoverPtr.clear();
+                ppPtr.clear();
+
+                // Update mesh mover
+                ppPtr = meshRefinement::makePatch(mesh, adaptPatchIDs);
+                meshMoverPtr.reset
+                (
+                    new motionSmoother
+                    (
+                        mesh,
+                        ppPtr(),
+                        adaptPatchIDs,
+                        meshRefinement::makeDisplacementField
+                        (
+                            pointMesh::New(mesh),
+                            adaptPatchIDs
+                        ),
+                        motionDict
+                    )
+                );
+
+                // Update snapping distance
+                snapDist = calcSnapDistance(mesh, snapParams, ppPtr());
+
+
+                if (debug&meshRefinement::MESH)
+                {
+                    const_cast<Time&>(mesh.time())++;
+                    Info<< "Writing split-faces mesh to time "
+                        << meshRefiner_.timeName() << endl;
+                    meshRefiner_.write
+                    (
+                        meshRefinement::debugType(debug),
+                        meshRefinement::writeType
+                        (
+                            meshRefinement::writeLevel()
+                          | meshRefinement::WRITEMESH
+                        ),
+                        mesh.time().path()/meshRefiner_.timeName()
+                    );
+                }
+            }
+
+
+            if (debug&meshRefinement::MESH)
+            {
+                forAll(internalBaffles, i)
+                {
+                    const labelPair& p = internalBaffles[i];
+                    const point& fc0 = mesh.faceCentres()[p[0]];
+                    const point& fc1 = mesh.faceCentres()[p[1]];
+
+                    if (mag(fc0-fc1) > meshRefiner_.mergeDistance())
+                    {
+                        FatalErrorIn("autoSnapDriver::doSnap(..)")
+                            << "Separated baffles : f0:" << p[0]
+                            << " centre:" << fc0
+                            << " f1:" << p[1] << " centre:" << fc1
+                            << " distance:" << mag(fc0-fc1)
+                            << exit(FatalError);
+                    }
+                }
+            }
         }
     }
 
 
     // Merge any introduced baffles (from faceZones of faceType 'internal')
     {
-        autoPtr<mapPolyMesh> mapPtr = mergeZoneBaffles(baffles);
+        autoPtr<mapPolyMesh> mapPtr = meshRefiner_.mergeZoneBaffles
+        (
+            true,   // internal zones
+            false   // baffle zones
+        );
 
         if (mapPtr.valid())
         {
-            forAll(duplicateFace, faceI)
+            if (debug & meshRefinement::MESH)
             {
-                if (duplicateFace[faceI] != -1)
-                {
-                    duplicateFace[faceI] = mapPtr().reverseFaceMap()[faceI];
-                }
+                const_cast<Time&>(mesh.time())++;
+                Info<< "Writing baffle-merged mesh to time "
+                    << meshRefiner_.timeName() << endl;
+                meshRefiner_.write
+                (
+                    meshRefinement::debugType(debug),
+                    meshRefinement::writeType
+                    (
+                        meshRefinement::writeLevel()
+                      | meshRefinement::WRITEMESH
+                    ),
+                    meshRefiner_.timeName()
+                );
             }
         }
     }
 
     // Repatch faces according to nearest. Do not repatch baffle faces.
     {
-        autoPtr<mapPolyMesh> mapPtr = repatchToSurface
-        (
-            snapParams,
-            adaptPatchIDs,
-            duplicateFace
-        );
-        meshRefinement::updateList
+        labelList duplicateFace(getInternalOrBaffleDuplicateFace());
+
+        repatchToSurface(snapParams, adaptPatchIDs, duplicateFace);
+    }
+
+    if (mergePatchFaces)
+    {
+        labelList duplicateFace(getInternalOrBaffleDuplicateFace());
+
+        // Repatching might have caused faces to be on same patch and hence
+        // mergeable so try again to merge coplanar faces. Do not merge baffle
+        // faces to ensure they both stay the same.
+        label nChanged = meshRefiner_.mergePatchFacesUndo
         (
-            mapPtr().faceMap(),
-            label(-1),
-            duplicateFace
+            featureCos,     // minCos
+            featureCos,     // concaveCos
+            meshRefiner_.meshedPatches(),
+            motionDict,
+            duplicateFace   // faces not to merge
         );
-    }
 
-    // Repatching might have caused faces to be on same patch and hence
-    // mergeable so try again to merge coplanar faces. Do not merge baffle
-    // faces to ensure they both stay the same.
-    label nChanged = meshRefiner_.mergePatchFacesUndo
-    (
-        featureCos,     // minCos
-        featureCos,     // concaveCos
-        meshRefiner_.meshedPatches(),
-        motionDict,
-        duplicateFace   // faces not to merge
-    );
+        nChanged += meshRefiner_.mergeEdgesUndo(featureCos, motionDict);
 
-    nChanged += meshRefiner_.mergeEdgesUndo(featureCos, motionDict);
+        if (nChanged > 0 && debug & meshRefinement::MESH)
+        {
+            const_cast<Time&>(mesh.time())++;
+            Info<< "Writing patchFace merged mesh to time "
+                << meshRefiner_.timeName() << endl;
+            meshRefiner_.write
+            (
+                meshRefinement::debugType(debug),
+                meshRefinement::writeType
+                (
+                    meshRefinement::writeLevel()
+                  | meshRefinement::WRITEMESH
+                ),
+                meshRefiner_.timeName()
+            );
+        }
+    }
 
-    if (nChanged > 0 && debug & meshRefinement::MESH)
+    if (debug & meshRefinement::MESH)
     {
         const_cast<Time&>(mesh.time())++;
-        Info<< "Writing patchFace merged mesh to time "
-            << meshRefiner_.timeName() << endl;
-        meshRefiner_.write
-        (
-            meshRefinement::debugType(debug),
-            meshRefinement::writeType
-            (
-                meshRefinement::writeLevel()
-              | meshRefinement::WRITEMESH
-            ),
-            meshRefiner_.timeName()
-        );
     }
 }
 
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.H b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.H
index b72d147a844f74e7426d318d4fbaf0460a1beacb..630d24d24e3ad3f9552f0ee158a47f6ac1d397a0 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.H
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriver.H
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -37,6 +37,7 @@ SourceFiles
 #define autoSnapDriver_H
 
 #include "meshRefinement.H"
+#include "DynamicField.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -45,6 +46,7 @@ namespace Foam
 
 // Forward declaration of classes
 class motionSmoother;
+class refinementParameters;
 class snapParameters;
 class pointConstraint;
 
@@ -79,26 +81,34 @@ class autoSnapDriver
                 PackedBoolList&
             );
 
+            //- Calculate displacement (over all mesh points) to move points
+            //  to average of connected cell centres
+            static tmp<pointField> smoothInternalDisplacement
+            (
+                const meshRefinement& meshRefiner,
+                const motionSmoother&
+            );
+
             //- Calculate displacement per patch point to smooth out patch.
             //  Quite complicated in determining which points to move where.
-            static pointField smoothPatchDisplacement
+            static tmp<pointField> smoothPatchDisplacement
             (
                 const motionSmoother&,
                 const List<labelPair>&
             );
 
-            //static tmp<pointField> avg
-            //(
-            //    const indirectPrimitivePatch&,
-            //    const pointField&
-            //);
+            static tmp<pointField> avg
+            (
+                const indirectPrimitivePatch&,
+                const pointField&
+            );
 
             //- Calculate displacement per patch point. Wip.
-            //static tmp<pointField> smoothLambdaMuPatchDisplacement
-            //(
-            //    const motionSmoother& meshMover,
-            //    const List<labelPair>& baffles
-            //);
+            static pointField smoothLambdaMuPatchDisplacement
+            (
+                const motionSmoother& meshMover,
+                const List<labelPair>& baffles
+            );
 
 
             //- Check that face zones are synced
@@ -136,6 +146,48 @@ class autoSnapDriver
                 DynamicList<labelPair>& splits
             ) const;
 
+            //- Get per face -1 or label of opposite face if on internal/baffle
+            //  faceZone
+            labelList getInternalOrBaffleDuplicateFace() const;
+
+            //- Get points both on patch and facezone.
+            static labelList getZoneSurfacePoints
+            (
+                const fvMesh& mesh,
+                const indirectPrimitivePatch&,
+                const word& zoneName
+            );
+
+            //- Get points both on patch and facezone.
+            template<class FaceList>
+            static labelList getFacePoints
+            (
+                const indirectPrimitivePatch& pp,
+                const FaceList& faces
+            );
+
+            //- Per patch point calculate point on nearest surface.
+            //  Return displacement of patch points.
+            static void calcNearestSurface
+            (
+                const refinementSurfaces& surfaces,
+
+                const labelList& surfacesToTest,
+                const labelListList& regionsToTest,
+
+                const pointField& localPoints,
+                const labelList& zonePointIndices,
+
+                scalarField& minSnapDist,
+                labelList& snapSurf,
+                vectorField& patchDisp,
+
+                // Optional: nearest point, normal
+                pointField& nearestPoint,
+                vectorField& nearestNormal
+            );
+
+
             // Feature line snapping
 
                 //- Is point on two feature edges that make a largish angle?
@@ -177,8 +229,8 @@ class autoSnapDriver
                     const scalarField& faceSnapDist,
                     vectorField& faceDisp,
                     vectorField& faceSurfaceNormal,
-                    labelList& faceSurfaceRegion,
-                    vectorField& faceRotation
+                    labelList& faceSurfaceRegion
+                    //vectorField& faceRotation
                 ) const;
                 void calcNearestFacePointProperties
                 (
@@ -253,6 +305,86 @@ class autoSnapDriver
                     const label faceI
                 ) const;
 
+                scalar pyrVol
+                (
+                    const indirectPrimitivePatch& pp,
+                    const vectorField& featureAttraction,
+                    const face& localF,
+                    const point& cc
+                ) const;
+                void facePoints
+                (
+                    const indirectPrimitivePatch& pp,
+                    const vectorField& featureAttraction,
+                    const vectorField& nearestAttraction,
+                    const face& f,
+                    DynamicField<point>& points
+                ) const;
+                scalar pyrVol
+                (
+                    const indirectPrimitivePatch& pp,
+                    const vectorField& featureAttraction,
+                    const vectorField& nearestAttraction,
+                    const face& localF,
+                    const point& cc
+                ) const;
+                Tuple2<point, vector> centreAndNormal
+                (
+                    const indirectPrimitivePatch& pp,
+                    const vectorField& featureAttraction,
+                    const vectorField& nearestAttraction,
+                    const face& localF
+                ) const;
+                bool isSplitAlignedWithFeature
+                (
+                    const scalar featureCos,
+                    const point& newPt0,
+                    const pointConstraint& pc0,
+                    const point& newPt1,
+                    const pointConstraint& pc1
+                ) const;
+                bool isConcave
+                (
+                    const point& c0,
+                    const vector& area0,
+                    const point& c1,
+                    const vector& area1,
+                    const scalar concaveCos
+                ) const;
+                labelPair findDiagonalAttraction
+                (
+                    const scalar featureCos,
+                    const scalar concaveCos,
+                    const scalar minAreaFraction,
+                    const indirectPrimitivePatch& pp,
+                    const vectorField& patchAttraction,
+                    const List<pointConstraint>& patchConstraints,
+                    const vectorField& nearestAttraction,
+                    const vectorField& nearestNormal,
+                    const label faceI,
+
+                    DynamicField<point>& points0,
+                    DynamicField<point>& points1
+                ) const;
+
+                //- Do all logic on whether to add face cut to diagonal
+                //  attraction
+                void splitDiagonals
+                (
+                    const scalar featureCos,
+                    const scalar concaveCos,
+                    const scalar minAreaFraction,
+
+                    const indirectPrimitivePatch& pp,
+                    const vectorField& nearestAttraction,
+                    const vectorField& nearestNormal,
+
+                    vectorField& patchAttraction,
+                    List<pointConstraint>& patchConstraints,
+                    DynamicList<label>& splitFaces,
+                    DynamicList<labelPair>& splits
+                ) const;
+
                 //- Avoid attraction across face diagonal since would
                 //  cause face squeeze
                 void avoidDiagonalAttraction
@@ -264,6 +396,14 @@ class autoSnapDriver
                     List<pointConstraint>& patchConstraints
                 ) const;
 
+                //- Write some stats about constraints
+                void writeStats
+                (
+                    const indirectPrimitivePatch& pp,
+                    const PackedBoolList& isMasterPoint,
+                    const List<pointConstraint>& patchConstraints
+                ) const;
+
                 //- Return hit if on multiple points
                 pointIndexHit findMultiPatchPoint
                 (
@@ -320,7 +460,6 @@ class autoSnapDriver
                 void featureAttractionUsingReconstruction
                 (
                     const label iter,
-                    const bool avoidSnapProblems,
                     const scalar featureCos,
                     const indirectPrimitivePatch& pp,
                     const scalarField& snapDist,
@@ -366,6 +505,7 @@ class autoSnapDriver
                 void determineBaffleFeatures
                 (
                     const label iter,
+                    const bool baffleFeaturePoints,
                     const scalar featureCos,
 
                     const indirectPrimitivePatch& pp,
@@ -450,12 +590,20 @@ class autoSnapDriver
                 void featureAttractionUsingFeatureEdges
                 (
                     const label iter,
-                    const bool avoidSnapProblems,
-                    const scalar featureCos,
                     const bool multiRegionFeatureSnap,
+
+                    const bool detectBaffles,
+                    const bool baffleFeaturePoints,
+                    const bool releasePoints,
+                    const bool stringFeatures,
+                    const bool avoidDiagonal,
+
+                    const scalar featureCos,
+
                     const indirectPrimitivePatch& pp,
                     const scalarField& snapDist,
                     const vectorField& nearestDisp,
+                    const vectorField& nearestNormal,
 
                     const List<List<point> >& pointFaceSurfNormals,
                     const List<List<point> >& pointFaceDisp,
@@ -465,12 +613,14 @@ class autoSnapDriver
                     vectorField& patchAttraction,
                     List<pointConstraint>& patchConstraints
                 ) const;
+
                 void preventFaceSqueeze
                 (
                     const label iter,
                     const scalar featureCos,
                     const indirectPrimitivePatch& pp,
                     const scalarField& snapDist,
+                    const vectorField& nearestAttraction,
 
                     vectorField& patchAttraction,
                     List<pointConstraint>& patchConstraints
@@ -483,15 +633,19 @@ class autoSnapDriver
                 vectorField calcNearestSurfaceFeature
                 (
                     const snapParameters& snapParams,
-                    const bool avoidSnapProblems,
+                    const bool alignMeshEdges,
                     const label iter,
                     const scalar featureCos,
                     const scalar featureAttract,
                     const scalarField& snapDist,
                     const vectorField& nearestDisp,
+                    const vectorField& nearestNormal,
                     motionSmoother& meshMover,
                     vectorField& patchAttraction,
-                    List<pointConstraint>& patchConstraints
+                    List<pointConstraint>& patchConstraints,
+
+                    DynamicList<label>& splitFaces,
+                    DynamicList<labelPair>& splits
                 ) const;
 
 
@@ -545,14 +699,6 @@ public:
                 motionSmoother&
             );
 
-            //- Get points both on patch and facezone.
-            static labelList getZoneSurfacePoints
-            (
-                const fvMesh& mesh,
-                const indirectPrimitivePatch&,
-                const word& zoneName
-            );
-
             //- Helper: calculate average cell centre per point
             static tmp<pointField> avgCellCentres
             (
@@ -575,7 +721,10 @@ public:
             //  displacement of patch points.
             static vectorField calcNearestSurface
             (
+                const bool strictRegionSnap,
                 const meshRefinement& meshRefiner,
+                const labelList& globalToMasterPatch,
+                const labelList& globalToSlavePatch,
                 const scalarField& snapDist,
                 const indirectPrimitivePatch&,
                 pointField& nearestPoint,
@@ -622,6 +771,7 @@ public:
             (
                 const dictionary& snapDict,
                 const dictionary& motionDict,
+                const bool mergePatchFaces,
                 const scalar featureCos,
                 const scalar planarAngle,
                 const snapParameters& snapParams
@@ -636,6 +786,12 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+#ifdef NoRepository
+#   include "autoSnapDriverTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
 #endif
 
 // ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriverFeature.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriverFeature.C
index 6fc6363bb95cbd96f088d59eca39abb27b0c2821..ac236f72b5bd74a522f44bdef820abfb816eb607 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriverFeature.C
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriverFeature.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -38,6 +38,8 @@ License
 #include "indexedOctree.H"
 #include "snapParameters.H"
 #include "PatchTools.H"
+#include "pyramidPointFaceRef.H"
+#include "localPointRegion.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -222,8 +224,8 @@ void Foam::autoSnapDriver::calcNearestFace
     const scalarField& faceSnapDist,
     vectorField& faceDisp,
     vectorField& faceSurfaceNormal,
-    labelList& faceSurfaceGlobalRegion,
-    vectorField& faceRotation
+    labelList& faceSurfaceGlobalRegion
+    //vectorField& faceRotation
 ) const
 {
     const fvMesh& mesh = meshRefiner_.mesh();
@@ -407,38 +409,38 @@ void Foam::autoSnapDriver::calcNearestFace
     }
 
 
-    // Determine rotation
-    // ~~~~~~~~~~~~~~~~~~
-
-    // Determine rotation axis
-    faceRotation.setSize(pp.size());
-    faceRotation = vector::zero;
-
-    forAll(faceRotation, faceI)
-    {
-        // Note: extend to >180 degrees checking
-        faceRotation[faceI] =
-            pp.faceNormals()[faceI]
-          ^ faceSurfaceNormal[faceI];
-    }
-
-    if (debug&meshRefinement::ATTRACTION)
-    {
-        dumpMove
-        (
-            mesh.time().path()
-          / "faceDisp_" + name(iter) + ".obj",
-            pp.faceCentres(),
-            pp.faceCentres() + faceDisp
-        );
-        dumpMove
-        (
-            mesh.time().path()
-          / "faceRotation_" + name(iter) + ".obj",
-            pp.faceCentres(),
-            pp.faceCentres() + faceRotation
-        );
-    }
+    //// Determine rotation
+    //// ~~~~~~~~~~~~~~~~~~
+    //
+    //// Determine rotation axis
+    //faceRotation.setSize(pp.size());
+    //faceRotation = vector::zero;
+    //
+    //forAll(faceRotation, faceI)
+    //{
+    //    // Note: extend to >180 degrees checking
+    //    faceRotation[faceI] =
+    //        pp.faceNormals()[faceI]
+    //      ^ faceSurfaceNormal[faceI];
+    //}
+    //
+    //if (debug&meshRefinement::ATTRACTION)
+    //{
+    //    dumpMove
+    //    (
+    //        mesh.time().path()
+    //      / "faceDisp_" + name(iter) + ".obj",
+    //        pp.faceCentres(),
+    //        pp.faceCentres() + faceDisp
+    //    );
+    //    dumpMove
+    //    (
+    //        mesh.time().path()
+    //      / "faceRotation_" + name(iter) + ".obj",
+    //        pp.faceCentres(),
+    //        pp.faceCentres() + faceRotation
+    //    );
+    //}
 }
 
 
@@ -553,34 +555,77 @@ void Foam::autoSnapDriver::calcNearestFacePointProperties
             patchID[meshFaceI-mesh.nInternalFaces()] = -1;
         }
 
-        // See if pp point uses any non-meshed boundary faces
 
-        const labelList& boundaryPoints = pp.boundaryPoints();
-        forAll(boundaryPoints, i)
+
+        // See if edge of pp uses any non-meshed boundary faces. If so add the
+        // boundary face as additional constraint. Note that we account for
+        // both 'real' boundary edges and boundary edge of baffles
+
+        const labelList bafflePair
+        (
+            localPointRegion::findDuplicateFaces(mesh, pp.addressing())
+        );
+
+
+        // Mark all points on 'boundary' edges
+        PackedBoolList isBoundaryPoint(pp.nPoints());
+
+        const labelListList& edgeFaces = pp.edgeFaces();
+        const edgeList& edges = pp.edges();
+
+        forAll(edgeFaces, edgeI)
         {
-            label pointI = boundaryPoints[i];
-            label meshPointI = pp.meshPoints()[pointI];
-            const point& pt = mesh.points()[meshPointI];
-            const labelList& pFaces = mesh.pointFaces()[meshPointI];
+            const edge& e = edges[edgeI];
+            const labelList& eFaces = edgeFaces[edgeI];
+
+            if (eFaces.size() == 1)
+            {
+                // 'real' boundary edge
+                isBoundaryPoint[e[0]] = true;
+                isBoundaryPoint[e[1]] = true;
+            }
+            else if (eFaces.size() == 2 && bafflePair[eFaces[0]] == eFaces[1])
+            {
+                // 'baffle' boundary edge
+                isBoundaryPoint[e[0]] = true;
+                isBoundaryPoint[e[1]] = true;
+            }
+        }
+
 
-            List<point>& pNormals = pointFaceSurfNormals[pointI];
-            List<point>& pDisp = pointFaceDisp[pointI];
-            List<point>& pFc = pointFaceCentres[pointI];
-            labelList& pFid = pointFacePatchID[pointI];
+        // Construct labelList equivalent of meshPointMap
+        labelList meshToPatchPoint(mesh.nPoints(), -1);
+        forAll(pp.meshPoints(), pointI)
+        {
+            meshToPatchPoint[pp.meshPoints()[pointI]] = pointI;
+        }
 
-            forAll(pFaces, i)
+        forAll(patchID, bFaceI)
+        {
+            label patchI = patchID[bFaceI];
+
+            if (patchI != -1)
             {
-                label meshFaceI = pFaces[i];
-                if (!mesh.isInternalFace(meshFaceI))
+                label faceI = mesh.nInternalFaces()+bFaceI;
+                const face& f = mesh.faces()[faceI];
+
+                forAll(f, fp)
                 {
-                    label patchI = patchID[meshFaceI-mesh.nInternalFaces()];
+                    label pointI = meshToPatchPoint[f[fp]];
 
-                    if (patchI != -1)
+                    if (pointI != -1 && isBoundaryPoint[pointI])
                     {
-                        vector fn = mesh.faceAreas()[meshFaceI];
+                        List<point>& pNormals = pointFaceSurfNormals[pointI];
+                        List<point>& pDisp = pointFaceDisp[pointI];
+                        List<point>& pFc = pointFaceCentres[pointI];
+                        labelList& pFid = pointFacePatchID[pointI];
+
+                        const point& pt = mesh.points()[f[fp]];
+                        vector fn = mesh.faceAreas()[faceI];
+
                         pNormals.append(fn/mag(fn));
-                        pDisp.append(mesh.faceCentres()[meshFaceI]-pt);
-                        pFc.append(mesh.faceCentres()[meshFaceI]);
+                        pDisp.append(mesh.faceCentres()[faceI]-pt);
+                        pFc.append(mesh.faceCentres()[faceI]);
                         pFid.append(patchI);
                     }
                 }
@@ -823,6 +868,54 @@ Foam::pointIndexHit Foam::autoSnapDriver::findMultiPatchPoint
 }
 
 
+void Foam::autoSnapDriver::writeStats
+(
+    const indirectPrimitivePatch& pp,
+    const PackedBoolList& isPatchMasterPoint,
+    const List<pointConstraint>& patchConstraints
+) const
+{
+    label nMasterPoints = 0;
+    label nPlanar = 0;
+    label nEdge = 0;
+    label nPoint = 0;
+
+    forAll(patchConstraints, pointI)
+    {
+        if (isPatchMasterPoint[pointI])
+        {
+            nMasterPoints++;
+
+            if (patchConstraints[pointI].first() == 1)
+            {
+                nPlanar++;
+            }
+            else if (patchConstraints[pointI].first() == 2)
+            {
+                nEdge++;
+            }
+            else if (patchConstraints[pointI].first() == 3)
+            {
+                nPoint++;
+            }
+        }
+    }
+
+    reduce(nMasterPoints, sumOp<label>());
+    reduce(nPlanar, sumOp<label>());
+    reduce(nEdge, sumOp<label>());
+    reduce(nPoint, sumOp<label>());
+    Info<< "total master points :" << nMasterPoints
+        << " of which attracted to :" << nl
+        << "    feature point   : " << nPoint << nl
+        << "    feature edge    : " << nEdge << nl
+        << "    nearest surface : " << nPlanar << nl
+        << "    rest            : " << nMasterPoints-nPoint-nEdge-nPlanar
+        << nl
+        << endl;
+}
+
+
 void Foam::autoSnapDriver::featureAttractionUsingReconstruction
 (
     const label iter,
@@ -1019,7 +1112,6 @@ void Foam::autoSnapDriver::featureAttractionUsingReconstruction
 void Foam::autoSnapDriver::featureAttractionUsingReconstruction
 (
     const label iter,
-    const bool avoidSnapProblems,
     const scalar featureCos,
 
     const indirectPrimitivePatch& pp,
@@ -1443,7 +1535,6 @@ void Foam::autoSnapDriver::releasePointsNextToMultiPatch
 }
 
 
-// If only two attractions and across face return the face indices
 Foam::labelPair Foam::autoSnapDriver::findDiagonalAttraction
 (
     const indirectPrimitivePatch& pp,
@@ -1460,50 +1551,424 @@ Foam::labelPair Foam::autoSnapDriver::findDiagonalAttraction
 
     if (f.size() >= 4)
     {
-        forAll(f, fp)
+        for (label startFp = 0; startFp < f.size()-2; startFp++)
         {
-            label pointI = f[fp];
-            if (patchConstraints[pointI].first() >= 2)
+            label minFp = f.rcIndex(startFp);
+
+            for
+            (
+                label endFp = f.fcIndex(f.fcIndex(startFp));
+                endFp < f.size() && endFp != minFp;
+                endFp++
+            )
             {
-                // Attract to feature edge or point
-                if (attractIndices[0] == -1)
+                if
+                (
+                    patchConstraints[f[startFp]].first() >= 2
+                 && patchConstraints[f[endFp]].first() >= 2
+                )
                 {
-                    // First attraction. Store
-                    attractIndices[0] = fp;
+                    attractIndices = labelPair(startFp, endFp);
+                    break;
                 }
-                else if (attractIndices[1] == -1)
+            }
+        }
+    }
+    return attractIndices;
+}
+
+
+bool Foam::autoSnapDriver::isSplitAlignedWithFeature
+(
+    const scalar featureCos,
+    const point& p0,
+    const pointConstraint& pc0,
+    const point& p1,
+    const pointConstraint& pc1
+) const
+{
+    vector d(p1-p0);
+    scalar magD = mag(d);
+    if (magD < VSMALL)
+    {
+        // Two diagonal points already colocated?
+        return false;
+    }
+    else
+    {
+        d /= magD;
+
+        // Is diagonal d aligned with at least one of the feature
+        // edges?
+
+        if (pc0.first() == 2 && mag(d & pc0.second()) > featureCos)
+        {
+            return true;
+        }
+        else if (pc1.first() == 2 && mag(d & pc1.second()) > featureCos)
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}
+
+
+// Is situation very concave
+bool Foam::autoSnapDriver::isConcave
+(
+    const point& c0,
+    const vector& area0,
+    const point& c1,
+    const vector& area1,
+    const scalar concaveCos
+) const
+{
+    vector n0 = area0;
+    scalar magN0 = mag(n0);
+    if (magN0 < VSMALL)
+    {
+        // Zero area face. What to return? For now disable splitting.
+        return true;
+    }
+    n0 /= magN0;
+
+    // Distance from c1 to plane of face0
+    scalar d = (c1-c0)&n0;
+
+    if (d <= 0)
+    {
+        // Convex (face1 centre on 'inside' of face0)
+        return false;
+    }
+    else
+    {
+        // Is a bit or very concave?
+        vector n1 = area1;
+        scalar magN1 = mag(n1);
+        if (magN1 < VSMALL)
+        {
+            // Zero area face. See above
+            return true;
+        }
+        n1 /= magN1;
+
+        if ((n0&n1) < concaveCos)
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}
+
+
+Foam::labelPair Foam::autoSnapDriver::findDiagonalAttraction
+(
+    const scalar featureCos,
+    const scalar concaveCos,
+    const scalar minAreaRatio,
+    const indirectPrimitivePatch& pp,
+    const vectorField& patchAttr,
+    const List<pointConstraint>& patchConstraints,
+    const vectorField& nearestAttr,
+    const vectorField& nearestNormal,
+    const label faceI,
+
+    DynamicField<point>& points0,
+    DynamicField<point>& points1
+) const
+{
+    const face& localF = pp.localFaces()[faceI];
+
+    labelPair attractIndices(-1, -1);
+
+    if (localF.size() >= 4)
+    {
+        const pointField& localPts = pp.localPoints();
+
+        //// Estimate cell centre taking patchAttraction into account
+        //// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        //// (is this necessary?)
+        //const polyMesh& mesh = meshRefiner_.mesh();
+        //label meshFaceI = pp.addressing()[faceI];
+        //const face& meshF = mesh.faces()[meshFaceI];
+        //label cellI = mesh.faceOwner()[meshFaceI];
+        //const labelList& cPoints = mesh.cellPoints(cellI);
+        //
+        //point cc(mesh.points()[meshF[0]]);
+        //for (label i = 1; i < meshF.size(); i++)
+        //{
+        //    cc += mesh.points()[meshF[i]]+patchAttr[localF[i]];
+        //}
+        //forAll(cPoints, i)
+        //{
+        //    label pointI = cPoints[i];
+        //    if (findIndex(meshF, pointI) == -1)
+        //    {
+        //        cc += mesh.points()[pointI];
+        //    }
+        //}
+        //cc /= cPoints.size();
+        ////const point& cc = mesh.cellCentres()[cellI];
+        //
+        //const scalar vol = pyrVol(pp, patchAttr, localF, cc);
+        //const scalar area = localF.mag(localPts);
+
+
+
+        // Try all diagonal cuts
+        // ~~~~~~~~~~~~~~~~~~~~~
+
+        face f0(3);
+        face f1(3);
+
+        for (label startFp = 0; startFp < localF.size()-2; startFp++)
+        {
+            label minFp = localF.rcIndex(startFp);
+
+            for
+            (
+                label endFp = localF.fcIndex(localF.fcIndex(startFp));
+                endFp < localF.size() && endFp != minFp;
+                endFp++
+            )
+            {
+                label startPtI = localF[startFp];
+                label endPtI = localF[endFp];
+
+                const pointConstraint& startPc = patchConstraints[startPtI];
+                const pointConstraint& endPc = patchConstraints[endPtI];
+
+                if (startPc.first() >= 2 && endPc.first() >= 2)
                 {
-                    // Second attraction. Check if not consecutive to first
-                    // attraction
-                    label fp0 = attractIndices[0];
-                    if (f.fcIndex(fp0) == fp || f.fcIndex(fp) == fp0)
+                    if (startPc.first() == 2 || endPc.first() == 2)
                     {
-                        // Consecutive. Skip.
-                        attractIndices = labelPair(-1, -1);
-                        break;
+                        // Check if
+                        // - sameish feature edge normal
+                        // - diagonal aligned with feature edge normal
+                        point start = localPts[startPtI]+patchAttr[startPtI];
+                        point end = localPts[endPtI]+patchAttr[endPtI];
+
+                        if
+                        (
+                           !isSplitAlignedWithFeature
+                            (
+                                featureCos,
+                                start,
+                                startPc,
+                                end,
+                                endPc
+                            )
+                        )
+                        {
+                            // Attract to different features. No need to
+                            // introduce split
+                            continue;
+                        }
+                    }
+
+
+
+                    // Form two faces
+                    // ~~~~~~~~~~~~~~
+                    // Predict position of faces. End points of the faces
+                    // attract to the feature
+                    // and all the other points just attract to the nearest
+
+                    // face0
+
+                    f0.setSize(endFp-startFp+1);
+                    label i0 = 0;
+                    for (label fp = startFp; fp <= endFp; fp++)
+                    {
+                        f0[i0++] = localF[fp];
+                    }
+
+                    // Get compact face and points
+                    const face compact0(identity(f0.size()));
+                    points0.clear();
+                    points0.append(localPts[f0[0]] + patchAttr[f0[0]]);
+                    for (label fp=1; fp < f0.size()-1; fp++)
+                    {
+                        label pI = f0[fp];
+                        points0.append(localPts[pI] + nearestAttr[pI]);
+                    }
+                    points0.append
+                    (
+                        localPts[f0.last()] + patchAttr[f0.last()]
+                    );
+
+
+                    // face1
+
+                    f1.setSize(localF.size()+2-f0.size());
+                    label i1 = 0;
+                    for
+                    (
+                        label fp = endFp;
+                        fp != startFp;
+                        fp = localF.fcIndex(fp)
+                    )
+                    {
+                        f1[i1++] = localF[fp];
+                    }
+                    f1[i1++] = localF[startFp];
+
+
+                    // Get compact face and points
+                    const face compact1(identity(f1.size()));
+                    points1.clear();
+                    points1.append(localPts[f1[0]] + patchAttr[f1[0]]);
+                    for (label fp=1; fp < f1.size()-1; fp++)
+                    {
+                        label pI = f1[fp];
+                        points1.append(localPts[pI] + nearestAttr[pI]);
+                    }
+                    points1.append
+                    (
+                        localPts[f1.last()] + patchAttr[f1.last()]
+                    );
+
+
+
+                    // Avoid splitting concave faces
+                    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+                    if
+                    (
+                        isConcave
+                        (
+                            compact0.centre(points0),
+                            compact0.normal(points0),
+                            compact1.centre(points1),
+                            compact1.normal(points1),
+                            concaveCos
+                        )
+                    )
+                    {
+                        //// Dump to obj
+                        //Pout<< "# f0:" << f0 << endl;
+                        //forAll(p, i)
+                        //{
+                        //    meshTools::writeOBJ(Pout, points0[i]);
+                        //}
+                        //Pout<< "# f1:" << f1 << endl;
+                        //forAll(p, i)
+                        //{
+                        //    meshTools::writeOBJ(Pout, points1[i]);
+                        //}
                     }
                     else
                     {
-                        attractIndices[1] = fp;
+                        // Existing areas
+                        const scalar area0 = f0.mag(localPts);
+                        const scalar area1 = f1.mag(localPts);
+
+                        if
+                        (
+                            area0/area1 >= minAreaRatio
+                         && area1/area0 >= minAreaRatio
+                        )
+                        {
+                            attractIndices = labelPair(startFp, endFp);
+                        }
                     }
                 }
-                else
-                {
-                    // More than two attractions. Skip.
-                    attractIndices = labelPair(-1, -1);
-                    break;
-                }
             }
         }
+    }
+    return attractIndices;
+}
+
+
+void Foam::autoSnapDriver::splitDiagonals
+(
+    const scalar featureCos,
+    const scalar concaveCos,
+    const scalar minAreaRatio,
+
+    const indirectPrimitivePatch& pp,
+    const vectorField& nearestAttraction,
+    const vectorField& nearestNormal,
+
+    vectorField& patchAttraction,
+    List<pointConstraint>& patchConstraints,
+    DynamicList<label>& splitFaces,
+    DynamicList<labelPair>& splits
+) const
+{
+    const labelList& bFaces = pp.addressing();
+
+    splitFaces.clear();
+    splitFaces.setCapacity(bFaces.size());
+    splits.clear();
+    splits.setCapacity(bFaces.size());
+
+
+    // Work arrays for storing points of face
+    DynamicField<point> facePoints0;
+    DynamicField<point> facePoints1;
+
+    forAll(bFaces, faceI)
+    {
+        const labelPair split
+        (
+            findDiagonalAttraction
+            (
+                featureCos,
+                concaveCos,
+                minAreaRatio,
+
+                pp,
+                patchAttraction,
+                patchConstraints,
 
+                nearestAttraction,
+                nearestNormal,
+                faceI,
+
+                facePoints0,
+                facePoints1
+            )
+        );
 
-        if (attractIndices[1] == -1)
+        if (split != labelPair(-1, -1))
         {
-            // Found only one attraction. Skip.
-            attractIndices = labelPair(-1, -1);
+            splitFaces.append(bFaces[faceI]);
+            splits.append(split);
+
+            const face& f = pp.localFaces()[faceI];
+
+            // Knock out other attractions on face
+            forAll(f, fp)
+            {
+                // Knock out any other constraints
+                if
+                (
+                    fp != split[0]
+                 && fp != split[1]
+                 && patchConstraints[f[fp]].first() >= 2
+                )
+                {
+                    patchConstraints[f[fp]] = pointConstraint
+                    (
+                        Tuple2<label, vector>
+                        (
+                            1,
+                            nearestNormal[f[fp]]
+                        )
+                    );
+                    patchAttraction[f[fp]] = nearestAttraction[f[fp]];
+                }
+            }
         }
     }
-    return attractIndices;
 }
 
 
@@ -1556,7 +2021,8 @@ void Foam::autoSnapDriver::avoidDiagonalAttraction
 
             if (cosAngle > featureCos)
             {
-                // Add the nearest of the other two points as
+                // The two feature edges are roughly in the same direction.
+                // Add the nearest of the other points in the face as
                 // attractor
                 label minFp = -1;
                 scalar minDistSqr = GREAT;
@@ -1579,8 +2045,7 @@ void Foam::autoSnapDriver::avoidDiagonalAttraction
                     label minPointI = f[minFp];
                     patchAttraction[minPointI] =
                         mid-pp.localPoints()[minPointI];
-                    patchConstraints[minPointI] =
-                        patchConstraints[f[diag[0]]];
+                    patchConstraints[minPointI] = patchConstraints[f[diag[0]]];
                 }
             }
             else
@@ -1665,8 +2130,7 @@ Foam::autoSnapDriver::findNearFeatureEdge
         edgeConstraints[featI][nearInfo.index()].append(c);
 
         // Store for later use
-        patchAttraction[pointI] =
-            nearInfo.hitPoint()-pp.localPoints()[pointI];
+        patchAttraction[pointI] = nearInfo.hitPoint()-pp.localPoints()[pointI];
         patchConstraints[pointI] = c;
     }
     return Tuple2<label, pointIndexHit>(featI, nearInfo);
@@ -1696,6 +2160,8 @@ Foam::autoSnapDriver::findNearFeaturePoint
 {
     const refinementFeatures& features = meshRefiner_.features();
 
+    // Search for for featurePoints only! This ignores any non-feature points.
+
     labelList nearFeat;
     List<pointIndexHit> nearInfo;
     features.findNearestPoint
@@ -1737,8 +2203,7 @@ Foam::autoSnapDriver::findNearFeaturePoint
 
                 // Store for later use
                 patchAttraction[pointI] = featPt-pt;
-                patchConstraints[pointI] =
-                    pointConstraints[featI][featPointI];
+                patchConstraints[pointI] = pointConstraints[featI][featPointI];
 
                 // Reset oldPointI to nearest on feature edge
                 patchAttraction[oldPointI] = vector::zero;
@@ -1808,6 +2273,8 @@ void Foam::autoSnapDriver::determineFeatures
     autoPtr<OBJstream> featureEdgeStr;
     autoPtr<OBJstream> missedEdgeStr;
     autoPtr<OBJstream> featurePointStr;
+    autoPtr<OBJstream> missedMP0Str;
+    autoPtr<OBJstream> missedMP1Str;
 
     if (debug&meshRefinement::ATTRACTION)
     {
@@ -1843,6 +2310,28 @@ void Foam::autoSnapDriver::determineFeatures
         );
         Info<< "Dumping feature-point sampling to "
             << featurePointStr().name() << endl;
+
+        missedMP0Str.reset
+        (
+            new OBJstream
+            (
+                meshRefiner_.mesh().time().path()
+              / "missedFeatureEdgeFromMPEdge_" + name(iter) + ".obj"
+            )
+        );
+        Info<< "Dumping region-edges that are too far away to "
+            << missedMP0Str().name() << endl;
+
+        missedMP1Str.reset
+        (
+            new OBJstream
+            (
+                meshRefiner_.mesh().time().path()
+              / "missedFeatureEdgeFromMPPoint_" + name(iter) + ".obj"
+            )
+        );
+        Info<< "Dumping region-points that are too far away to "
+            << missedMP1Str().name() << endl;
     }
 
 
@@ -1854,6 +2343,15 @@ void Foam::autoSnapDriver::determineFeatures
     {
         const point& pt = pp.localPoints()[pointI];
 
+
+        // Determine the geometric planes the point is (approximately) on.
+        // This is returned as a
+        // - attraction vector
+        // - and a constraint
+        //   (1: attract to surface, constraint is normal of plane
+        //    2: attract to feature line, constraint is feature line direction
+        //    3: attract to feature point, constraint is zero)
+
         vector attraction = vector::zero;
         pointConstraint constraint;
 
@@ -1881,6 +2379,35 @@ void Foam::autoSnapDriver::determineFeatures
             constraint
         );
 
+        // Now combine the reconstruction with the current state of the
+        // point. The logic is quite complicated:
+        // - the new constraint (from reconstruction) will only win if
+        //   - the constraint is higher (feature-point wins from feature-edge
+        //     etc.)
+        //   - or the constraint is the same but the attraction distance is less
+        //
+        // - then this will be combined with explicit searching on the
+        //   features and optionally the analysis of the patches using the
+        //   point. This analysis can do three thing:
+        //      - the point is not on multiple patches
+        //      - the point is on multiple patches but these are also
+        //        different planes (so the region feature is also a geometric
+        //        feature)
+        //      - the point is on multiple patches some of which are on
+        //        the same plane. This is the problem one - do we assume it is
+        //        an additional constraint (feat edge upgraded to region point,
+        //        see below)?
+        //
+        //      Reconstruction  MultiRegionFeatureSnap          Attraction
+        //      -------         ----------------------          -----------
+        //      surface         false                           surface
+        //      surface         true                            region edge
+        //      feat edge       false                           feat edge
+        //      feat edge       true and no planar regions      feat edge
+        //      feat edge       true and yes planar regions     region point
+        //      feat point      false                           feat point
+        //      feat point      true                            region point
+
 
         if
         (
@@ -1920,7 +2447,7 @@ void Foam::autoSnapDriver::determineFeatures
                         Tuple2<label, pointIndexHit> nearInfo =
                         findNearFeatureEdge
                         (
-                            true,                       // isRegionPoint
+                            true,                       // isRegionEdge
                             pp,
                             snapDist,
                             pointI,
@@ -1951,7 +2478,7 @@ void Foam::autoSnapDriver::determineFeatures
                             {
                                 missedEdgeStr().write
                                 (
-                                    linePointRef(pt, info.missPoint())
+                                    linePointRef(pt, multiPatchPt.hitPoint())
                                 );
                             }
                         }
@@ -1985,10 +2512,10 @@ void Foam::autoSnapDriver::determineFeatures
                     {
                         if (multiPatchPt.index() == 0)
                         {
-                            // Geometric feature edge is also region edge
+                            // Region edge is also a geometric feature edge
                             nearInfo = findNearFeatureEdge
                             (
-                                true,               // isRegionPoint
+                                true,               // isRegionEdge
                                 pp,
                                 snapDist,
                                 pointI,
@@ -2001,11 +2528,28 @@ void Foam::autoSnapDriver::determineFeatures
                                 patchConstraints
                             );
                             hasSnapped = true;
+
+                            // Debug: dump missed feature point
+                            if
+                            (
+                                missedMP0Str.valid()
+                            && !nearInfo.second().hit()
+                            )
+                            {
+                                missedMP0Str().write
+                                (
+                                    linePointRef(pt, estimatedPt)
+                                );
+                            }
                         }
                         else
                         {
                             // One of planes of feature contains multiple
-                            // regions
+                            // regions. We assume (contentious!) that the
+                            // separation between
+                            // the regions is not aligned with the geometric
+                            // feature so is an additional constraint on the
+                            // point -> is region-feature-point.
                             nearInfo = findNearFeaturePoint
                             (
                                 true,           // isRegionPoint
@@ -2025,6 +2569,47 @@ void Foam::autoSnapDriver::determineFeatures
                                 patchConstraints
                             );
                             hasSnapped = true;
+
+                            // More contentious: if we don't find
+                            // a near feature point we will never find the
+                            // attraction to a feature edge either since
+                            // the edgeAttractors/edgeConstraints do not get
+                            // filled and we're using reverse attraction
+                            // Note that we're in multiRegionFeatureSnap which
+                            // where findMultiPatchPoint can decide the
+                            // wrong thing. So: if failed finding a near
+                            // feature point try for a feature edge
+                            if (!nearInfo.second().hit())
+                            {
+                                nearInfo = findNearFeatureEdge
+                                (
+                                    true,           // isRegionEdge
+                                    pp,
+                                    snapDist,
+                                    pointI,
+                                    estimatedPt,
+
+                                    // Feature-edge to pp point
+                                    edgeAttractors,
+                                    edgeConstraints,
+                                    // pp point to nearest feature
+                                    patchAttraction,
+                                    patchConstraints
+                                );
+                            }
+
+                            // Debug: dump missed feature point
+                            if
+                            (
+                                missedMP1Str.valid()
+                            && !nearInfo.second().hit()
+                            )
+                            {
+                                missedMP1Str().write
+                                (
+                                    linePointRef(pt, estimatedPt)
+                                );
+                            }
                         }
                     }
                 }
@@ -2085,7 +2670,7 @@ void Foam::autoSnapDriver::determineFeatures
                     {
                         missedEdgeStr().write
                         (
-                            linePointRef(pt, info.missPoint())
+                            linePointRef(pt, estimatedPt)
                         );
                     }
                 }
@@ -2204,6 +2789,7 @@ void Foam::autoSnapDriver::determineFeatures
 void Foam::autoSnapDriver::determineBaffleFeatures
 (
     const label iter,
+    const bool baffleFeaturePoints,
     const scalar featureCos,
 
     const indirectPrimitivePatch& pp,
@@ -2258,8 +2844,8 @@ void Foam::autoSnapDriver::determineBaffleFeatures
 
     // Detect baffle edges. Assume initial mesh will have 0,90 or 180
     // (baffle) degree angles so smoothing should make 0,90
-    // to be less than 90.
-    const scalar baffleFeatureCos = Foam::cos(degToRad(91));
+    // to be less than 90. Choose reasonable value
+    const scalar baffleFeatureCos = Foam::cos(degToRad(110));
 
 
     autoPtr<OBJstream> baffleEdgeStr;
@@ -2308,40 +2894,58 @@ void Foam::autoSnapDriver::determineBaffleFeatures
         }
     }
 
-    Info<< "Detected "
-        << returnReduce(nBaffleEdges, sumOp<label>())
+    reduce(nBaffleEdges, sumOp<label>());
+
+    Info<< "Detected " << nBaffleEdges
         << " baffle edges out of "
         << returnReduce(pp.nEdges(), sumOp<label>())
         << " edges." << endl;
 
 
-    forAll(pp.pointEdges(), pointI)
+    //- Baffle edges will be too ragged to sensibly determine feature points
+    //forAll(pp.pointEdges(), pointI)
+    //{
+    //    if
+    //    (
+    //        isFeaturePoint
+    //        (
+    //            featureCos,
+    //            pp,
+    //            isBaffleEdge,
+    //            pointI
+    //        )
+    //    )
+    //    {
+    //        //Pout<< "Detected feature point:" << pp.localPoints()[pointI]
+    //        //    << endl;
+    //        //-TEMPORARILY DISABLED:
+    //        //pointStatus[pointI] = 1;
+    //    }
+    //}
+
+
+    label nBafflePoints = 0;
+    forAll(pointStatus, pointI)
     {
-        if
-        (
-            isFeaturePoint
-            (
-                featureCos,
-                pp,
-                isBaffleEdge,
-                pointI
-            )
-        )
+        if (pointStatus[pointI] != -1)
         {
-            //Pout<< "Detected feature point:" << pp.localPoints()[pointI]
-            //    << endl;
-            //-TEMPORARILY DISABLED:
-            //pointStatus[pointI] = 1;
+            nBafflePoints++;
         }
     }
+    reduce(nBafflePoints, sumOp<label>());
 
 
+    label nPointAttract = 0;
+    label nEdgeAttract = 0;
+
     forAll(pointStatus, pointI)
     {
         const point& pt = pp.localPoints()[pointI];
 
         if (pointStatus[pointI] == 0)   // baffle edge
         {
+            // 1: attract to near feature edge first
+
             Tuple2<label, pointIndexHit> nearInfo = findNearFeatureEdge
             (
                 false,          // isRegionPoint?
@@ -2356,10 +2960,43 @@ void Foam::autoSnapDriver::determineBaffleFeatures
                 patchConstraints
             );
 
-            if (!nearInfo.second().hit())
+
+            //- MEJ:
+            // 2: optionally override with nearest feature point.
+            //    On baffles we don't have enough normals to construct a feature
+            //    point so assume all feature edges are close to feature points
+            if (nearInfo.second().hit())
             {
-                //Pout<< "*** Failed to find close edge to point " << pt
-                //    << endl;
+                nEdgeAttract++;
+
+                if (baffleFeaturePoints)
+                {
+                    nearInfo = findNearFeaturePoint
+                    (
+                        false,          // isRegionPoint,
+
+                        pp,
+                        snapDist,
+                        pointI,
+                        pt,             // estimatedPt,
+
+                        // Feature-point to pp point
+                        pointAttractor,
+                        pointConstraints,
+                        // Feature-edge to pp point
+                        edgeAttractors,
+                        edgeConstraints,
+                        // pp point to nearest feature
+                        patchAttraction,
+                        patchConstraints
+                    );
+
+                    if (nearInfo.first() != -1)
+                    {
+                        nEdgeAttract--;
+                        nPointAttract++;
+                    }
+                }
             }
         }
         else if (pointStatus[pointI] == 1)   // baffle point
@@ -2378,6 +3015,8 @@ void Foam::autoSnapDriver::determineBaffleFeatures
 
             if (featI != -1)
             {
+                nPointAttract++;
+
                 label featPointI = nearInfo[0].index();
                 const point& featPt = nearInfo[0].hitPoint();
                 scalar distSqr = magSqr(featPt-pt);
@@ -2440,7 +3079,7 @@ void Foam::autoSnapDriver::determineBaffleFeatures
                 //    << " for baffle-feature-point " << pt
                 //    << endl;
 
-                findNearFeatureEdge
+                Tuple2<label, pointIndexHit> nearInfo = findNearFeatureEdge
                 (
                     false,                  // isRegionPoint
                     pp,
@@ -2453,9 +3092,25 @@ void Foam::autoSnapDriver::determineBaffleFeatures
                     patchAttraction,
                     patchConstraints
                 );
+
+                if (nearInfo.first() != -1)
+                {
+                    nEdgeAttract++;
+                }
             }
         }
     }
+
+    reduce(nPointAttract, sumOp<label>());
+    reduce(nEdgeAttract, sumOp<label>());
+
+    Info<< "Baffle points     : " << nBafflePoints
+        << " of which attracted to :" << nl
+        << "    feature point : " << nPointAttract << nl
+        << "    feature edge  : " << nEdgeAttract << nl
+        << "    rest          : " << nBafflePoints-nPointAttract-nEdgeAttract
+        << nl
+        << endl;
 }
 
 
@@ -2518,7 +3173,8 @@ void Foam::autoSnapDriver::reverseAttractMeshPoints
         }
 
         Info<< "Initially selected " << returnReduce(nFeats, sumOp<label>())
-            << " points out of " << returnReduce(pp.nPoints(), sumOp<label>())
+            << " mesh points out of "
+            << returnReduce(pp.nPoints(), sumOp<label>())
             << " for reverse attraction." << endl;
 
         // Make sure is synchronised (note: check if constraint is already
@@ -2578,7 +3234,8 @@ void Foam::autoSnapDriver::reverseAttractMeshPoints
 
         Info<< "Selected "
             << returnReduce(attractPoints.size(), sumOp<label>())
-            << " points out of " << returnReduce(pp.nPoints(), sumOp<label>())
+            << " mesh points out of "
+            << returnReduce(pp.nPoints(), sumOp<label>())
             << " for reverse attraction." << endl;
     }
 
@@ -2724,13 +3381,21 @@ void Foam::autoSnapDriver::reverseAttractMeshPoints
 void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
 (
     const label iter,
-    const bool avoidSnapProblems,
-    const scalar featureCos,
     const bool multiRegionFeatureSnap,
 
+    const bool detectBaffles,
+    const bool baffleFeaturePoints,
+
+    const bool releasePoints,
+    const bool stringFeatures,
+    const bool avoidDiagonal,
+
+    const scalar featureCos,
+
     const indirectPrimitivePatch& pp,
     const scalarField& snapDist,
     const vectorField& nearestDisp,
+    const vectorField& nearestNormal,
 
     const List<List<point> >& pointFaceSurfNormals,
     const List<List<point> >& pointFaceDisp,
@@ -2742,6 +3407,17 @@ void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
 ) const
 {
     const refinementFeatures& features = meshRefiner_.features();
+    const fvMesh& mesh = meshRefiner_.mesh();
+
+    const PackedBoolList isPatchMasterPoint
+    (
+        meshRefinement::getMasterPoints
+        (
+            mesh,
+            pp.meshPoints()
+        )
+    );
+
 
     // Collect ordered attractions on feature edges
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2802,7 +3478,12 @@ void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
         rawPatchConstraints
     );
 
-
+    // Print a bit about the attraction from patch point to feature
+    if (debug)
+    {
+        Info<< "Raw geometric feature analysis : ";
+        writeStats(pp, isPatchMasterPoint, rawPatchConstraints);
+    }
 
     // Baffle handling
     // ~~~~~~~~~~~~~~~
@@ -2815,25 +3496,35 @@ void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
     // detected anything. So explicitly pick up feature edges on the pp
     // (after duplicating points & smoothing so will already have been
     // expanded) and match these to the features.
-    determineBaffleFeatures
-    (
-        iter,
-        featureCos,
+    if (detectBaffles)
+    {
+        determineBaffleFeatures
+        (
+            iter,
+            baffleFeaturePoints,
+            featureCos,
 
-        pp,
-        snapDist,
+            pp,
+            snapDist,
 
-        // Feature-point to pp point
-        pointAttractor,
-        pointConstraints,
-        // Feature-edge to pp point
-        edgeAttractors,
-        edgeConstraints,
-        // pp point to nearest feature
-        rawPatchAttraction,
-        rawPatchConstraints
-    );
+            // Feature-point to pp point
+            pointAttractor,
+            pointConstraints,
+            // Feature-edge to pp point
+            edgeAttractors,
+            edgeConstraints,
+            // pp point to nearest feature
+            rawPatchAttraction,
+            rawPatchConstraints
+        );
+    }
 
+    // Print a bit about the attraction from patch point to feature
+    if (debug)
+    {
+        Info<< "After baffle feature analysis : ";
+        writeStats(pp, isPatchMasterPoint, rawPatchConstraints);
+    }
 
 
     // Reverse lookup: Find nearest mesh point to feature edge
@@ -2863,6 +3554,12 @@ void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
         patchConstraints
     );
 
+    // Print a bit about the attraction from patch point to feature
+    if (debug)
+    {
+        Info<< "Reverse attract feature analysis : ";
+        writeStats(pp, isPatchMasterPoint, patchConstraints);
+    }
 
     // Dump
     if (debug&meshRefinement::ATTRACTION)
@@ -2900,38 +3597,38 @@ void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
     }
 
 
-    if (avoidSnapProblems)
+    //MEJ: any faces that have multi-patch points only keep the
+    //     multi-patch
+    //     points. The other points on the face will be dragged along
+    //     (hopefully)
+    if (releasePoints)
     {
-        //MEJ: any faces that have multi-patch points only keep the multi-patch
-        //     points. The other points on the face will be dragged along
-        //     (hopefully)
-        if (multiRegionFeatureSnap)
-        {
-            releasePointsNextToMultiPatch
-            (
-                iter,
-                featureCos,
-
-                pp,
-                snapDist,
+        releasePointsNextToMultiPatch
+        (
+            iter,
+            featureCos,
 
-                pointFaceCentres,
-                pointFacePatchID,
+            pp,
+            snapDist,
 
-                rawPatchAttraction,
-                rawPatchConstraints,
+            pointFaceCentres,
+            pointFacePatchID,
 
-                patchAttraction,
-                patchConstraints
-            );
-        }
+            rawPatchAttraction,
+            rawPatchConstraints,
 
+            patchAttraction,
+            patchConstraints
+        );
+    }
 
-        // Snap edges to feature edges
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-        // Walk existing edges and snap remaining ones (that are marked as
-        // feature edges in rawPatchConstraints)
 
+    // Snap edges to feature edges
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    // Walk existing edges and snap remaining ones (that are marked as
+    // feature edges in rawPatchConstraints)
+    if (stringFeatures)
+    {
         stringFeatureEdges
         (
             iter,
@@ -2946,12 +3643,14 @@ void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
             patchAttraction,
             patchConstraints
         );
+    }
 
 
-        // Avoid diagonal attraction
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~
-        // Attract one of the non-diagonal points.
-
+    // Avoid diagonal attraction
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~
+    // Attract one of the non-diagonal points.
+    if (avoidDiagonal)
+    {
         avoidDiagonalAttraction
         (
             iter,
@@ -2962,6 +3661,7 @@ void Foam::autoSnapDriver::featureAttractionUsingFeatureEdges
         );
     }
 
+
     if (debug&meshRefinement::ATTRACTION)
     {
         dumpMove
@@ -2983,11 +3683,27 @@ void Foam::autoSnapDriver::preventFaceSqueeze
 
     const indirectPrimitivePatch& pp,
     const scalarField& snapDist,
+    const vectorField& nearestAttraction,
 
     vectorField& patchAttraction,
     List<pointConstraint>& patchConstraints
 ) const
 {
+    autoPtr<OBJstream> strPtr;
+    if (debug&meshRefinement::ATTRACTION)
+    {
+        strPtr.reset
+        (
+            new OBJstream
+            (
+                meshRefiner_.mesh().time().path()
+              / "faceSqueeze_" + name(iter) + ".obj"
+            )
+        );
+        Info<< "Dumping faceSqueeze corrections to "
+            << strPtr().name() << endl;
+    }
+
     pointField points;
     face singleF;
     forAll(pp.localFaces(), faceI)
@@ -3022,16 +3738,19 @@ void Foam::autoSnapDriver::preventFaceSqueeze
 
         if (nConstraints == f.size())
         {
-            scalar oldArea = f.mag(pp.localPoints());
-            scalar newArea = singleF.mag(points);
-            if (newArea < 0.1*oldArea)
+            if (f.size() == 3)
             {
-                // For now remove the point with largest distance
+                // Triangle: knock out attraction altogether
+
+                // For now keep the points on the longest edge
                 label maxFp = -1;
                 scalar maxS = -1;
                 forAll(f, fp)
                 {
-                    scalar s = magSqr(patchAttraction[f[fp]]);
+                    const point& pt = pp.localPoints()[f[fp]];
+                    const point& nextPt = pp.localPoints()[f.nextLabel(fp)];
+
+                    scalar s = magSqr(pt-nextPt);
                     if (s > maxS)
                     {
                         maxS = s;
@@ -3040,9 +3759,52 @@ void Foam::autoSnapDriver::preventFaceSqueeze
                 }
                 if (maxFp != -1)
                 {
-                    label pointI = f[maxFp];
-                    // Lower attraction on pointI
-                    patchAttraction[pointI] *= 0.5;
+                    label pointI = f.prevLabel(maxFp);
+
+                    // Reset attraction on pointI to nearest
+
+                    const point& pt = pp.localPoints()[pointI];
+
+                    //Pout<< "** on triangle " << pp.faceCentres()[faceI]
+                    //    << " knocking out attraction to " << pointI
+                    //    << " at:" << pt
+                    //    << endl;
+
+                    patchAttraction[pointI] = nearestAttraction[pointI];
+
+                    if (strPtr.valid())
+                    {
+                        strPtr().write
+                        (
+                            linePointRef(pt, pt+patchAttraction[pointI])
+                        );
+                    }
+                }
+            }
+            else
+            {
+                scalar oldArea = f.mag(pp.localPoints());
+                scalar newArea = singleF.mag(points);
+                if (newArea < 0.1*oldArea)
+                {
+                    // For now remove the point with largest distance
+                    label maxFp = -1;
+                    scalar maxS = -1;
+                    forAll(f, fp)
+                    {
+                        scalar s = magSqr(patchAttraction[f[fp]]);
+                        if (s > maxS)
+                        {
+                            maxS = s;
+                            maxFp = fp;
+                        }
+                    }
+                    if (maxFp != -1)
+                    {
+                        label pointI = f[maxFp];
+                        // Lower attraction on pointI
+                        patchAttraction[pointI] *= 0.5;
+                    }
                 }
             }
         }
@@ -3053,15 +3815,20 @@ void Foam::autoSnapDriver::preventFaceSqueeze
 Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
 (
     const snapParameters& snapParams,
-    const bool avoidSnapProblems,
+    const bool alignMeshEdges,
     const label iter,
     const scalar featureCos,
     const scalar featureAttract,
     const scalarField& snapDist,
     const vectorField& nearestDisp,
+    const vectorField& nearestNormal,
     motionSmoother& meshMover,
     vectorField& patchAttraction,
-    List<pointConstraint>& patchConstraints
+    List<pointConstraint>& patchConstraints,
+
+    DynamicList<label>& splitFaces,
+    DynamicList<labelPair>& splits
+
 ) const
 {
     const Switch implicitFeatureAttraction = snapParams.implicitFeatureSnap();
@@ -3080,6 +3847,15 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
     const fvMesh& mesh = meshRefiner_.mesh();
 
 
+    //const PackedBoolList isMasterPoint(syncTools::getMasterPoints(mesh));
+    const PackedBoolList isPatchMasterPoint
+    (
+        meshRefinement::getMasterPoints
+        (
+            mesh,
+            pp.meshPoints()
+        )
+    );
 
     // Per point, per surrounding face:
     // - faceSurfaceNormal
@@ -3112,7 +3888,7 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
         // normal of surface at point on surface
         vectorField faceSurfaceNormal(pp.size(), vector::zero);
         labelList faceSurfaceGlobalRegion(pp.size(), -1);
-        vectorField faceRotation(pp.size(), vector::zero);
+        //vectorField faceRotation(pp.size(), vector::zero);
 
         calcNearestFace
         (
@@ -3121,8 +3897,8 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
             faceSnapDist,
             faceDisp,
             faceSurfaceNormal,
-            faceSurfaceGlobalRegion,
-            faceRotation
+            faceSurfaceGlobalRegion
+            //faceRotation
         );
 
 
@@ -3173,7 +3949,6 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
         featureAttractionUsingReconstruction
         (
             iter,
-            avoidSnapProblems,
             featureCos,
 
             pp,
@@ -3192,6 +3967,18 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
 
     if (explicitFeatureAttraction)
     {
+        // Only do fancy stuff if alignMeshEdges
+        bool releasePoints = false;
+        bool stringFeatures = false;
+        bool avoidDiagonal = false;
+        if (alignMeshEdges)
+        {
+            releasePoints = snapParams.releasePoints();
+            stringFeatures = snapParams.stringFeatures();
+            avoidDiagonal = snapParams.avoidDiagonal();
+        }
+
+
         // Sample faces around each point and see if nearest surface normal
         // differs. For those find the nearest real feature edge/point and
         // store the correspondence. Then loop over feature edge/point
@@ -3201,13 +3988,22 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
         featureAttractionUsingFeatureEdges
         (
             iter,
-            avoidSnapProblems,
-            featureCos,
             multiRegionFeatureSnap,
 
+            snapParams.detectBaffles(),
+            snapParams.baffleFeaturePoints(),   // all points on baffle edges
+                                                // are attracted to feature pts
+
+            releasePoints,
+            stringFeatures,
+            avoidDiagonal,
+
+            featureCos,
+
             pp,
             snapDist,
             nearestDisp,
+            nearestNormal,
 
             pointFaceSurfNormals,
             pointFaceDisp,
@@ -3219,6 +4015,46 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
         );
     }
 
+    if (!alignMeshEdges)
+    {
+        const scalar concaveCos = Foam::cos
+        (
+            degToRad(snapParams.concaveAngle())
+        );
+        const scalar minAreaRatio = snapParams.minAreaRatio();
+
+        Info<< "Experimental: introducing face splits to avoid rotating"
+            << " mesh edges. Splitting faces when" << nl
+            << indent << "- angle not concave by more than "
+            << snapParams.concaveAngle() << " degrees" << nl
+            << indent << "- resulting triangles of similar area "
+            << " (ratio within " << minAreaRatio << ")" << nl
+            << endl;
+
+        splitDiagonals
+        (
+            featureCos,
+            concaveCos,
+            minAreaRatio,
+            pp,
+
+            nearestDisp,
+            nearestNormal,
+
+            patchAttraction,
+            patchConstraints,
+            splitFaces,
+            splits
+        );
+
+        if (debug)
+        {
+            Info<< "Diagonal attraction feature correction : ";
+            writeStats(pp, isPatchMasterPoint, patchConstraints);
+        }
+    }
+
+
     preventFaceSqueeze
     (
         iter,
@@ -3226,20 +4062,12 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
 
         pp,
         snapDist,
+        nearestDisp,
 
         patchAttraction,
         patchConstraints
     );
 
-    //const PackedBoolList isMasterPoint(syncTools::getMasterPoints(mesh));
-    const PackedBoolList isPatchMasterPoint
-    (
-        meshRefinement::getMasterPoints
-        (
-            mesh,
-            pp.meshPoints()
-        )
-    );
     {
         vector avgPatchDisp = meshRefinement::gAverage
         (
@@ -3253,9 +4081,9 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
         );
 
         Info<< "Attraction:" << endl
-            << "     linear   : max:" << gMaxMagSqr(patchDisp)
+            << "    linear   : max:" << gMaxMagSqr(patchDisp)
             << " avg:" << avgPatchDisp << endl
-            << "     feature  : max:" << gMaxMagSqr(patchAttraction)
+            << "    feature  : max:" << gMaxMagSqr(patchAttraction)
             << " avg:" << avgPatchAttr << endl;
     }
 
@@ -3284,45 +4112,8 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
 
     // Count
     {
-        label nMasterPoints = 0;
-        label nPlanar = 0;
-        label nEdge = 0;
-        label nPoint = 0;
-
-        forAll(patchConstraints, pointI)
-        {
-            if (isPatchMasterPoint[pointI])
-            {
-                nMasterPoints++;
-
-                if (patchConstraints[pointI].first() == 1)
-                {
-                    nPlanar++;
-                }
-                else if (patchConstraints[pointI].first() == 2)
-                {
-                    nEdge++;
-                }
-                else if (patchConstraints[pointI].first() == 3)
-                {
-                    nPoint++;
-                }
-            }
-        }
-
-        reduce(nMasterPoints, sumOp<label>());
-        reduce(nPlanar, sumOp<label>());
-        reduce(nEdge, sumOp<label>());
-        reduce(nPoint, sumOp<label>());
-        Info<< "Feature analysis : total master points:"
-            << nMasterPoints
-            << " attraction to :" << nl
-            << "    feature point   : " << nPoint << nl
-            << "    feature edge    : " << nEdge << nl
-            << "    nearest surface : " << nPlanar << nl
-            << "    rest            : " << nMasterPoints-nPoint-nEdge-nPlanar
-            << nl
-            << endl;
+        Info<< "Feature analysis : ";
+        writeStats(pp, isPatchMasterPoint, patchConstraints);
     }
 
 
@@ -3362,7 +4153,6 @@ Foam::vectorField Foam::autoSnapDriver::calcNearestSurfaceFeature
             )
         );
 
-
         // 1. Smoothed all displacement
         vectorField smoothedPatchDisp = patchDisp;
         smoothAndConstrain
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriverTemplates.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriverTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..f047752b07985e93f6054c9a4f87c3be1c546201
--- /dev/null
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/autoSnapDriverTemplates.C
@@ -0,0 +1,65 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 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 "autoSnapDriver.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+template<class FaceList>
+Foam::labelList Foam::autoSnapDriver::getFacePoints
+(
+    const indirectPrimitivePatch& pp,
+    const FaceList& faces
+)
+{
+    // Could use PrimitivePatch & localFaces to extract points but might just
+    // as well do it ourselves.
+
+    boolList pointOnZone(pp.nPoints(), false);
+
+    forAll(faces, i)
+    {
+        const face& f = faces[i];
+
+        forAll(f, fp)
+        {
+            label meshPointI = f[fp];
+
+            Map<label>::const_iterator iter =
+                pp.meshPointMap().find(meshPointI);
+
+            if (iter != pp.meshPointMap().end())
+            {
+                label pointI = iter();
+                pointOnZone[pointI] = true;
+            }
+        }
+    }
+
+    return findIndices(pointOnZone, true);
+}
+
+
+// ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.C
index be4272e37a575ab78c3562150e32e824ab7f41ba..a4148b950b5494eb6fa6e48bb12dd12d92d9fc50 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.C
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -124,6 +124,14 @@ Foam::layerParameters::layerParameters
         readScalar(dict.lookup("minThickness"))
     ),
     featureAngle_(readScalar(dict.lookup("featureAngle"))),
+    mergePatchFacesAngle_
+    (
+        dict.lookupOrDefault<scalar>
+        (
+            "mergePatchFacesAngle",
+            featureAngle_
+        )
+    ),
     concaveAngle_
     (
         dict.lookupOrDefault("concaveAngle", defaultConcaveAngle)
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.H b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.H
index 21d52fdd6924a75e88f251107a990630d4b052e1..505fb8b07bd1cd7a0f29e186fb6557ec4546b182 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.H
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/layerParameters/layerParameters.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -114,6 +114,8 @@ private:
 
         scalar featureAngle_;
 
+        scalar mergePatchFacesAngle_;
+
         scalar concaveAngle_;
 
         label nGrow_;
@@ -247,6 +249,11 @@ public:
                 return featureAngle_;
             }
 
+            scalar mergePatchFacesAngle() const
+            {
+                return mergePatchFacesAngle_;
+            }
+
             scalar concaveAngle() const
             {
                 return concaveAngle_;
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.C
index 0ed25cf81c7300a9e6fe333ef44018786d6985a5..704dcf16adbc3f6c4c4907871055ca31e39b996a 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.C
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,6 +27,8 @@ License
 #include "unitConversion.H"
 #include "polyMesh.H"
 #include "globalIndex.H"
+#include "Tuple2.H"
+#include "wallPolyPatch.H"
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
@@ -44,7 +46,15 @@ Foam::refinementParameters::refinementParameters(const dictionary& dict)
         )
     ),
     nBufferLayers_(readLabel(dict.lookup("nCellsBetweenLevels"))),
-    keepPoints_(pointField(1, dict.lookup("locationInMesh"))),
+    locationsOutsideMesh_
+    (
+        dict.lookupOrDefault
+        (
+            "locationsOutsideMesh",
+            pointField(0)
+        )
+    ),
+    faceZoneControls_(dict.subOrEmptyDict("faceZoneControls")),
     allowFreeStandingZoneFaces_(dict.lookup("allowFreeStandingZoneFaces")),
     useTopologicalSnapDetection_
     (
@@ -54,8 +64,35 @@ Foam::refinementParameters::refinementParameters(const dictionary& dict)
     handleSnapProblems_
     (
         dict.lookupOrDefault<Switch>("handleSnapProblems", true)
+    ),
+    interfaceRefine_
+    (
+        dict.lookupOrDefault<Switch>("interfaceRefine", true)
     )
 {
+    point locationInMesh;
+    if (dict.readIfPresent("locationInMesh", locationInMesh))
+    {
+        locationsInMesh_.append(locationInMesh);
+        zonesInMesh_.append("noneIfNotSet");// special name for no cellZone
+    }
+
+    List<Tuple2<point, word> > pointsToZone;
+    if (dict.readIfPresent("locationsInMesh", pointsToZone))
+    {
+        label nZones = locationsInMesh_.size();
+        locationsInMesh_.setSize(nZones+pointsToZone.size());
+        zonesInMesh_.setSize(locationsInMesh_.size());
+
+        forAll(pointsToZone, i)
+        {
+            locationsInMesh_[nZones] = pointsToZone[i].first();
+            zonesInMesh_[nZones] = pointsToZone[i].second();
+            nZones++;
+        }
+    }
+
+
     scalar featAngle(readScalar(dict.lookup("resolveFeatureAngle")));
 
     if (featAngle < 0 || featAngle > 180)
@@ -71,8 +108,68 @@ Foam::refinementParameters::refinementParameters(const dictionary& dict)
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-Foam::labelList Foam::refinementParameters::findCells(const polyMesh& mesh)
-const
+Foam::dictionary Foam::refinementParameters::getZoneInfo
+(
+    const word& fzName,
+    surfaceZonesInfo::faceZoneType& faceType
+) const
+{
+    dictionary patchInfo;
+    patchInfo.add("type", wallPolyPatch::typeName);
+    faceType = surfaceZonesInfo::INTERNAL;
+
+    if (faceZoneControls_.found(fzName))
+    {
+        const dictionary& fzDict = faceZoneControls_.subDict(fzName);
+
+        if (fzDict.found("patchInfo"))
+        {
+            patchInfo = fzDict.subDict("patchInfo");
+        }
+
+        word faceTypeName;
+        if (fzDict.readIfPresent("faceType", faceTypeName))
+        {
+            faceType = surfaceZonesInfo::faceZoneTypeNames[faceTypeName];
+        }
+    }
+    return patchInfo;
+}
+
+
+Foam::labelList Foam::refinementParameters::addCellZonesToMesh
+(
+    polyMesh& mesh
+) const
+{
+    labelList zoneIDs(zonesInMesh_.size(), -1);
+    forAll(zonesInMesh_, i)
+    {
+        if
+        (
+            zonesInMesh_[i] != word::null
+         && zonesInMesh_[i] != "none"
+         && zonesInMesh_[i] != "noneIfNotSet"
+        )
+        {
+            zoneIDs[i] = surfaceZonesInfo::addCellZone
+            (
+                zonesInMesh_[i],    // name
+                labelList(0),       // addressing
+                mesh
+            );
+        }
+    }
+    return zoneIDs;
+}
+
+
+Foam::labelList Foam::refinementParameters::findCells
+(
+    const bool checkInsideMesh,
+    const polyMesh& mesh,
+    const pointField& locations
+)
 {
     // Force calculation of tet-diag decomposition (for use in findCell)
     (void)mesh.tetBasePtIs();
@@ -81,13 +178,13 @@ const
     globalIndex globalCells(mesh.nCells());
 
     // Cell label per point
-    labelList cellLabels(keepPoints_.size());
+    labelList cellLabels(locations.size());
 
-    forAll(keepPoints_, i)
+    forAll(locations, i)
     {
-        const point& keepPoint = keepPoints_[i];
+        const point& location = locations[i];
 
-        label localCellI = mesh.findCell(keepPoint);
+        label localCellI = mesh.findCell(location);
 
         label globalCellI = -1;
 
@@ -98,12 +195,13 @@ const
 
         reduce(globalCellI, maxOp<label>());
 
-        if (globalCellI == -1)
+        if (checkInsideMesh && globalCellI == -1)
         {
             FatalErrorIn
             (
-                "refinementParameters::findCells(const polyMesh&) const"
-            )   << "Point " << keepPoint
+                "refinementParameters::findCells"
+                "(const polyMesh&, const pointField&) const"
+            )   << "Point " << location
                 << " is not inside the mesh or on a face or edge." << nl
                 << "Bounding box of the mesh:" << mesh.bounds()
                 << exit(FatalError);
@@ -113,10 +211,9 @@ const
         label procI = globalCells.whichProcID(globalCellI);
         label procCellI = globalCells.toLocal(procI, globalCellI);
 
-        Info<< "Found point " << keepPoint << " in cell " << procCellI
+        Info<< "Found point " << location << " in cell " << procCellI
             << " on processor " << procI << endl;
 
-
         if (globalCells.isLocal(globalCellI))
         {
             cellLabels[i] = localCellI;
@@ -130,4 +227,48 @@ const
 }
 
 
+Foam::labelList Foam::refinementParameters::zonedLocations
+(
+    const wordList& zonesInMesh
+)
+{
+    DynamicList<label> indices(zonesInMesh.size());
+
+    forAll(zonesInMesh, i)
+    {
+        if
+        (
+            zonesInMesh[i] == word::null
+        ||  zonesInMesh[i] != "noneIfNotSet"
+        )
+        {
+            indices.append(i);
+        }
+    }
+    return indices;
+}
+
+
+Foam::labelList Foam::refinementParameters::unzonedLocations
+(
+    const wordList& zonesInMesh
+)
+{
+    DynamicList<label> indices(0);
+
+    forAll(zonesInMesh, i)
+    {
+        if
+        (
+            zonesInMesh[i] != word::null
+        &&  zonesInMesh[i] == "noneIfNotSet"
+        )
+        {
+            indices.append(i);
+        }
+    }
+    return indices;
+}
+
+
 // ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.H b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.H
index 06b90b07580b41986f45d86db2c77352fc7af2a5..9504efa56726941156392b218c11636391f4c125 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.H
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/refinementParameters.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -38,6 +38,8 @@ SourceFiles
 #include "dictionary.H"
 #include "pointField.H"
 #include "Switch.H"
+#include "wordPairHashTable.H"
+#include "surfaceZonesInfo.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -73,8 +75,21 @@ class refinementParameters
         //- Number of layers between different refinement levels
         const label nBufferLayers_;
 
-        //- Areas to keep
-        const pointField keepPoints_;
+
+        // Selection of areas
+
+            //- Areas not to keep
+            const pointField locationsOutsideMesh_;
+
+            //- Areas to keep
+            pointField locationsInMesh_;
+
+            //- Region for location
+            wordList zonesInMesh_;
+
+            //- Information on how to handle faces on faceZones
+            dictionary faceZoneControls_;
+
 
         //- FaceZone faces allowed which have owner and neighbour in same
         //  cellZone?
@@ -89,6 +104,8 @@ class refinementParameters
 
         Switch handleSnapProblems_;
 
+        Switch interfaceRefine_;
+
     // Private Member Functions
 
         //- Disallow default bitwise copy construct
@@ -147,9 +164,21 @@ public:
             }
 
             //- Areas to keep
-            const pointField& keepPoints() const
+            const pointField& locationsInMesh() const
             {
-                return keepPoints_;
+                return locationsInMesh_;
+            }
+
+            //- Per area the zone name
+            const wordList& zonesInMesh() const
+            {
+                return zonesInMesh_;
+            }
+
+            //- Optional points which are checked to be outside the mesh
+            const pointField& locationsOutsideMesh() const
+            {
+                return locationsOutsideMesh_;
             }
 
             //- Are zone faces allowed only inbetween different cell zones
@@ -177,11 +206,39 @@ public:
                 return handleSnapProblems_;
             }
 
+            //- Refine cell with opposite faces with different refinement level
+            bool interfaceRefine() const
+            {
+                return interfaceRefine_;
+            }
+
 
         // Other
 
-            //- Checks that cells are in mesh. Returns cells they are in.
-            labelList findCells(const polyMesh&) const;
+            //- Get patchInfo and faceType for faceZone
+            dictionary getZoneInfo
+            (
+                const word& fzName,
+                surfaceZonesInfo::faceZoneType& faceType
+            ) const;
+
+            //- Add cellZones to mesh. Return indices of cellZones (or -1)
+            labelList addCellZonesToMesh(polyMesh&) const;
+
+            //- Checks that cells are in mesh. Returns cells (or -1) they
+            //  are in.
+            static labelList findCells
+            (
+                const bool checkInsideMesh,
+                const polyMesh&,
+                const pointField& locations
+            );
+
+            //- Extract indices of named locations (so excludes 'keepPoints')
+            static labelList zonedLocations(const wordList& zonesInMesh);
+
+            //- Extract indices of unnamed locations ('keepPoints')
+            static labelList unzonedLocations(const wordList& zonesInMesh);
 
 };
 
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/wordPairHashTable.H b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/wordPairHashTable.H
new file mode 100644
index 0000000000000000000000000000000000000000..f095f1f4e75b1d1cadccc1af024aa98be2f153d3
--- /dev/null
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/refinementParameters/wordPairHashTable.H
@@ -0,0 +1,56 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 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/>.
+
+Typedef
+    Foam::wordPairHashTable
+
+Description
+    HashTable of Pair<word>
+
+Typedef
+    Foam::wordPairHashTable
+
+Description
+    HashTable of Pair<word>
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef wordPairHashTable_H
+#define wordPairHashTable_H
+
+#include "Pair.H"
+#include "HashTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    typedef HashTable<word, Pair<word>, FixedList<word, 2>::Hash<> >
+        wordPairHashTable;
+}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.C b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.C
index 364a6aae490a4f36dc44b0b74e2ccbe53640fe54..3f74c035aaf221d9df19be9eb5b1d4779cd4c977 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.C
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -31,6 +31,7 @@ License
 Foam::snapParameters::snapParameters(const dictionary& dict)
 :
     nSmoothPatch_(readLabel(dict.lookup("nSmoothPatch"))),
+    nSmoothInternal_(dict.lookupOrDefault("nSmoothInternal", 0)),
     snapTol_(readScalar(dict.lookup("tolerance"))),
     nSmoothDispl_(readLabel(dict.lookup("nSolveIter"))),
     nSnap_(readLabel(dict.lookup("nRelaxIter"))),
@@ -44,7 +45,22 @@ Foam::snapParameters::snapParameters(const dictionary& dict)
     detectNearSurfacesSnap_
     (
         dict.lookupOrDefault("detectNearSurfacesSnap", true)
-    )
+    ),
+    strictRegionSnap_
+    (
+        dict.lookupOrDefault("strictRegionSnap", false)
+    ),
+    detectBaffles_(dict.lookupOrDefault("detectBaffles", true)),
+    baffleFeaturePoints_(dict.lookupOrDefault("baffleFeaturePoints", false)),
+    releasePoints_(dict.lookupOrDefault("releasePoints", false)),
+    stringFeatures_(dict.lookupOrDefault("stringFeatures", true)),
+    avoidDiagonal_(dict.lookupOrDefault("avoidDiagonal", false)),
+    nFaceSplitInterval_
+    (
+        dict.lookupOrDefault("nFaceSplitInterval", labelMin)
+    ),
+    concaveAngle_(dict.lookupOrDefault("concaveAngle", 45)),
+    minAreaRatio_(dict.lookupOrDefault("minAreaRatio", 0.3))
 {}
 
 
diff --git a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.H b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.H
index 5dc4ca34e80a28d9cb494a02ad20b6d7b6d8e131..26982ea79dd7a40a34be89ebf6b99e4a9195047d 100644
--- a/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.H
+++ b/src/mesh/autoMesh/autoHexMesh/autoHexMeshDriver/snapParameters/snapParameters.H
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -44,10 +44,8 @@ SourceFiles
 namespace Foam
 {
 
-// Class forward declarations
-
 /*---------------------------------------------------------------------------*\
-                           Class snapParameters Declaration
+                       Class snapParameters Declaration
 \*---------------------------------------------------------------------------*/
 
 class snapParameters
@@ -56,6 +54,8 @@ class snapParameters
 
         const label nSmoothPatch_;
 
+        const label nSmoothInternal_;
+
         const scalar snapTol_;
 
         const label nSmoothDispl_;
@@ -72,6 +72,28 @@ class snapParameters
 
         const Switch detectNearSurfacesSnap_;
 
+        const Switch strictRegionSnap_;
+
+        const Switch detectBaffles_;
+
+        const Switch baffleFeaturePoints_;
+
+        const Switch releasePoints_;
+
+        const Switch stringFeatures_;
+
+        const Switch avoidDiagonal_;
+
+
+        //- How often needs face splitting be run
+        label nFaceSplitInterval_;
+
+        //- When is angle too concave too split
+        scalar concaveAngle_;
+
+        //- When is face-split not sufficiently diagonal
+        scalar minAreaRatio_;
+
 
     // Private Member Functions
 
@@ -101,6 +123,13 @@ public:
                 return nSmoothPatch_;
             }
 
+            //- Number of internal point smoothing iterations (combined with
+            //  nSmoothPatch
+            label nSmoothInternal() const
+            {
+                return nSmoothInternal_;
+            }
+
             //- Relative distance for points to be attracted by surface
             //  feature point
             //  or edge. True distance is this factor times local
@@ -123,30 +152,90 @@ public:
                 return nSnap_;
             }
 
-            label nFeatureSnap() const
-            {
-                return nFeatureSnap_;
-            }
 
-            Switch explicitFeatureSnap() const
-            {
-                return explicitFeatureSnap_;
-            }
+            // Surface snapping specific
+
+                //- Override attraction to nearest with intersection location
+                //  at near surfaces
+                Switch detectNearSurfacesSnap() const
+                {
+                    return detectNearSurfacesSnap_;
+                }
+
+                //- Attract point to corresponding surface region only
+                Switch strictRegionSnap() const
+                {
+                    return strictRegionSnap_;
+                }
+
+
+            // Feature edge snapping specific
+
+                label nFeatureSnap() const
+                {
+                    return nFeatureSnap_;
+                }
+
+                Switch explicitFeatureSnap() const
+                {
+                    return explicitFeatureSnap_;
+                }
+
+                Switch implicitFeatureSnap() const
+                {
+                    return implicitFeatureSnap_;
+                }
+
+                Switch multiRegionFeatureSnap() const
+                {
+                    return multiRegionFeatureSnap_;
+                }
+
+                Switch detectBaffles() const
+                {
+                    return detectBaffles_;
+                }
+
+                Switch baffleFeaturePoints() const
+                {
+                    return baffleFeaturePoints_;
+                }
+
+                Switch releasePoints() const
+                {
+                    return releasePoints_;
+                }
+
+                Switch stringFeatures() const
+                {
+                    return stringFeatures_;
+                }
+
+                Switch avoidDiagonal() const
+                {
+                    return avoidDiagonal_;
+                }
+
+
+                // Face splitting
+
+                    label nFaceSplitInterval() const
+                    {
+                        return nFaceSplitInterval_;
+                    }
+
+                    scalar concaveAngle() const
+                    {
+                        return concaveAngle_;
+                    }
+
+                    scalar minAreaRatio() const
+                    {
+                        return minAreaRatio_;
+                    }
 
-            Switch implicitFeatureSnap() const
-            {
-                return implicitFeatureSnap_;
-            }
 
-            Switch multiRegionFeatureSnap() const
-            {
-                return multiRegionFeatureSnap_;
-            }
 
-            Switch detectNearSurfacesSnap() const
-            {
-                return detectNearSurfacesSnap_;
-            }
 
 };
 
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.C b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.C
index 84d212e4251ce0b684056c4068b6a360210f7237..d44bc26597af40270d792641c7e0edc5b8473d18 100644
--- a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.C
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -42,9 +42,6 @@ namespace Foam
 }
 
 
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::displacementMeshMoverMotionSolver::displacementMeshMoverMotionSolver
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.H b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.H
index 273f5fc301b0c19ecd821f9d9820e22ad20d23c9..26a5be3aa8628942e500e0b631579333a37178e7 100644
--- a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.H
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMeshMoverMotionSolver.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,8 +25,8 @@ Class
     Foam::displacementMeshMoverMotionSolver
 
 Description
-    Mesh motion solver for an fvMesh.  Based on solving the cell-centre
-    Laplacian for the motion displacement.
+    Mesh motion solver for an fvMesh. Uses externalDisplacementMeshMover
+    to do the mesh motion.
 
 SourceFiles
     displacementMeshMoverMotionSolver.C
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMotionSolverMeshMover.C b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMotionSolverMeshMover.C
new file mode 100644
index 0000000000000000000000000000000000000000..0891b067bc8472964d7b177754c28ae7ff91fbf5
--- /dev/null
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMotionSolverMeshMover.C
@@ -0,0 +1,316 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 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 "displacementMotionSolverMeshMover.H"
+#include "addToRunTimeSelectionTable.H"
+#include "pointConstraints.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(displacementMotionSolverMeshMover, 1);
+
+    addToRunTimeSelectionTable
+    (
+        externalDisplacementMeshMover,
+        displacementMotionSolverMeshMover,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+bool Foam::displacementMotionSolverMeshMover::moveMesh
+(
+    const dictionary& moveDict,
+    const label nAllowableErrors,
+    labelList& checkFaces
+)
+{
+    const label nRelaxIter = readLabel(moveDict.lookup("nRelaxIter"));
+
+    meshMover_.setDisplacementPatchFields();
+
+    Info<< typeName << " : Moving mesh ..." << endl;
+
+    scalar oldErrorReduction = -1;
+
+    bool meshOk = false;
+
+    for (label iter = 0; iter < 2*nRelaxIter; ++ iter)
+    {
+        Info<< typeName << " : Iteration " << iter << endl;
+
+        if (iter == nRelaxIter)
+        {
+            Info<< typeName
+                << " : Displacement scaling for error reduction set to 0."
+                << endl;
+            oldErrorReduction = meshMover_.setErrorReduction(0.0);
+        }
+
+        if
+        (
+            meshMover_.scaleMesh
+            (
+                checkFaces,
+                baffles_,
+                meshMover_.paramDict(),
+                moveDict,
+                true,
+                nAllowableErrors
+            )
+        )
+        {
+            Info<< typeName << " : Successfully moved mesh" << endl;
+            meshOk = true;
+            break;
+        }
+    }
+
+    if (oldErrorReduction >= 0)
+    {
+        meshMover_.setErrorReduction(oldErrorReduction);
+    }
+
+    Info<< typeName << " : Finished moving mesh ..." << endl;
+
+    return meshOk;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::displacementMotionSolverMeshMover::displacementMotionSolverMeshMover
+(
+    const dictionary& dict,
+    const List<labelPair>& baffles,
+    pointVectorField& pointDisplacement
+)
+:
+    externalDisplacementMeshMover(dict, baffles, pointDisplacement),
+
+    solverPtr_
+    (
+        displacementMotionSolver::New
+        (
+            dict.lookup("solver"),
+            pointDisplacement.mesh()(),
+            IOdictionary
+            (
+                IOobject
+                (
+                    "motionSolverDict",
+                    pointDisplacement.mesh().time().constant(),
+                    pointDisplacement.db(),
+                    IOobject::NO_READ,
+                    IOobject::NO_WRITE,
+                    false
+                ),
+                dict
+            ),
+            pointDisplacement,
+            pointIOField
+            (
+                IOobject
+                (
+                    "points0",
+                    pointDisplacement.mesh().time().constant(),
+                    pointDisplacement.db(),
+                    IOobject::NO_READ,
+                    IOobject::NO_WRITE,
+                    false
+                ),
+                pointDisplacement.mesh()().points()
+            )
+        )
+    ),
+
+    adaptPatchIDs_(getFixedValueBCs(pointDisplacement)),
+    adaptPatchPtr_(getPatch(mesh(), adaptPatchIDs_)),
+
+    scale_
+    (
+        IOobject
+        (
+            "scale",
+            pointDisplacement.time().timeName(),
+            pointDisplacement.db(),
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        pMesh(),
+        dimensionedScalar("scale", dimless, 1.0)
+    ),
+
+    oldPoints_(mesh().points()),
+
+    meshMover_
+    (
+        const_cast<polyMesh&>(mesh()),
+        const_cast<pointMesh&>(pMesh()),
+        adaptPatchPtr_(),
+        pointDisplacement,
+        scale_,
+        oldPoints_,
+        adaptPatchIDs_,
+        dict
+    ),
+
+    fieldSmoother_(mesh())
+{}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::displacementMotionSolverMeshMover::~displacementMotionSolverMeshMover()
+{}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+bool Foam::displacementMotionSolverMeshMover::move
+(
+    const dictionary& moveDict,
+    const label nAllowableErrors,
+    labelList& checkFaces
+)
+{
+    // Correct and smooth the patch displacements so points next to
+    // points where the extrusion was disabled also use less extrusion.
+    // Note that this has to update the pointDisplacement boundary conditions
+    // as well, not just the internal field.
+    {
+        const label nSmoothPatchThickness = readLabel
+        (
+            moveDict.lookup("nSmoothThickness")
+        );
+
+        const word minThicknessName = word(moveDict.lookup("minThicknessName"));
+
+        scalarField zeroMinThickness;
+
+        if (minThicknessName == "none")
+        {
+            zeroMinThickness = scalarField(adaptPatchPtr_().nPoints(), 0.0);
+        }
+
+        const scalarField& minThickness =
+        (
+            (minThicknessName == "none")
+          ? zeroMinThickness
+          : mesh().lookupObject<scalarField>(minThicknessName)
+        );
+
+        const PackedBoolList isPatchMasterPoint
+        (
+            meshRefinement::getMasterPoints
+            (
+                mesh(),
+                adaptPatchPtr_().meshPoints()
+            )
+        );
+
+        const PackedBoolList isPatchMasterEdge
+        (
+            meshRefinement::getMasterEdges
+            (
+                mesh(),
+                adaptPatchPtr_().meshEdges
+                (
+                    mesh().edges(),
+                    mesh().pointEdges()
+                )
+            )
+        );
+
+        // Smooth patch displacement
+
+        vectorField displacement
+        (
+            pointDisplacement().internalField(),
+            adaptPatchPtr_().meshPoints()
+        );
+
+        fieldSmoother_.minSmoothField
+        (
+            nSmoothPatchThickness,
+            isPatchMasterPoint,
+            isPatchMasterEdge,
+            adaptPatchPtr_(),
+            minThickness,
+            displacement
+        );
+
+
+        scalar resid = 0;
+
+        forAll(displacement, patchPointI)
+        {
+            const label pointI(adaptPatchPtr_().meshPoints()[patchPointI]);
+
+            resid += mag(pointDisplacement()[pointI]-displacement[patchPointI]);
+
+            pointDisplacement()[pointI] = displacement[patchPointI];
+        }
+
+        // Take over smoothed displacements on bcs
+        meshMover_.setDisplacementPatchFields();
+    }
+
+    // Use motionSolver to calculate internal displacement
+    {
+        solverPtr_->pointDisplacement() == pointDisplacement();
+        // Force solving and constraining - just so its pointDisplacement gets
+        // the correct value
+        (void)solverPtr_->newPoints();
+        pointDisplacement() == solverPtr_->pointDisplacement();
+    }
+
+    return moveMesh(moveDict, nAllowableErrors, checkFaces);
+}
+
+
+void Foam::displacementMotionSolverMeshMover::movePoints
+(
+    const pointField& p
+)
+{
+    externalDisplacementMeshMover::movePoints(p);
+
+    // Update motion solver for new geometry
+    solverPtr_->movePoints(p);
+
+    // Update motionSmoother for new geometry (moves adaptPatchPtr_)
+    meshMover_.movePoints();
+
+    // Assume current mesh location is correct (reset oldPoints, scale)
+    meshMover_.correct();
+}
+
+
+// ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMotionSolverMeshMover.H b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMotionSolverMeshMover.H
new file mode 100644
index 0000000000000000000000000000000000000000..73acb7a8261266cf6f3ebbe3eb5114d1a79e2d37
--- /dev/null
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/displacementMotionSolverMeshMover.H
@@ -0,0 +1,158 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify i
+    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::displacementMotionSolverMeshMover
+
+Description
+    Quality-based under-relaxation wrapped around generic
+    displacementMotionSolver.
+
+    Example of use in layer settings in snappyHexMeshDict:
+    \verbatim
+    meshShrinker displacementMotionSolver;
+    solver displacementLaplacian;
+    displacementLaplacianCoeffs
+    {
+        diffusivity  quadratic inverseDistance 1(wall);
+    }
+    \endverbatim
+
+SourceFiles
+    displacementMotionSolverMeshMover.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef displacementMotionSolverMeshMover_H
+#define displacementMotionSolverMeshMover_H
+
+#include "externalDisplacementMeshMover.H"
+#include "displacementMotionSolver.H"
+#include "motionSmootherAlgo.H"
+#include "fieldSmoother.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+              Class displacementMotionSolverMeshMover Declaration
+\*---------------------------------------------------------------------------*/
+
+class displacementMotionSolverMeshMover
+:
+    public externalDisplacementMeshMover
+{
+    // Private Data
+
+        //- Mesh motion solver
+        autoPtr<displacementMotionSolver> solverPtr_;
+
+        //- IDs of fixedValue patches that we can modify
+        const labelList adaptPatchIDs_;
+
+        //- Combined indirect fixedValue patches that we can modify
+        autoPtr<indirectPrimitivePatch> adaptPatchPtr_;
+
+        //- Scale factor for displacemen
+        pointScalarField scale_;
+
+        //- Old point field
+        pointField oldPoints_;
+
+        //- Mesh mover algorithm
+        motionSmootherAlgo meshMover_;
+
+        //- Field smoothing
+        fieldSmoother fieldSmoother_;
+
+
+    // Private Member Functions
+
+        //- Apply the mesh mover algorithm
+        bool moveMesh
+        (
+            const dictionary& moveDict,
+            const label nAllowableErrors,
+            labelList& checkFaces
+        );
+
+
+public:
+
+    //- Runtime type information
+    TypeName("displacementMotionSolver");
+
+
+    // Constructors
+
+        //- Construct from a polyMesh and an IOdictionary
+        displacementMotionSolverMeshMover
+        (
+            const dictionary& dict,
+            const List<labelPair>& baffles,
+            pointVectorField& pointDisplacemen
+        );
+
+
+    //- Destructor
+    virtual ~displacementMotionSolverMeshMover();
+
+
+    // Member Functions
+
+        //- Move mesh using current pointDisplacement boundary values.
+        //  Return true if succesful (errors on checkFaces less than
+        //  allowable). Updates pointDisplacement.
+        virtual bool move
+        (
+            const dictionary&,
+            const label nAllowableErrors,
+            labelList& checkFaces
+        );
+
+        //- Update local data for geometry changes
+        virtual void movePoints(const pointField&);
+
+        //-  Update local data for topology changes
+        virtual void updateMesh(const mapPolyMesh&)
+        {
+            notImplemented
+            (
+                "displacementMotionSolverMeshMover::updateMesh"
+                "(const mapPolyMesh&)"
+            );
+        }
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.C b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.C
index 2067b24caceb2e244920c11df214b587a58a27bd..4d5531c330c09816320f63f1afad81a6f273c189 100644
--- a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.C
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2013-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,6 +25,7 @@ License
 
 #include "externalDisplacementMeshMover.H"
 #include "mapPolyMesh.H"
+#include "zeroFixedValuePointPatchFields.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -35,6 +36,84 @@ namespace Foam
 }
 
 
+// * * * * * * * * * * * *  Protected Member Functions * * * * * * * * * * * //
+
+Foam::labelList Foam::externalDisplacementMeshMover::getFixedValueBCs
+(
+    const pointVectorField& field
+)
+{
+    DynamicList<label> adaptPatchIDs;
+
+    forAll(field.boundaryField(), patchI)
+    {
+        const pointPatchField<vector>& patchField =
+            field.boundaryField()[patchI];
+
+        if (isA<valuePointPatchField<vector> >(patchField))
+        {
+            if (isA<zeroFixedValuePointPatchField<vector> >(patchField))
+            {
+                // Special condition of fixed boundary condition. Does not
+                // get adapted
+            }
+            else
+            {
+                adaptPatchIDs.append(patchI);
+            }
+        }
+    }
+
+    return adaptPatchIDs;
+}
+
+
+Foam::autoPtr<Foam::indirectPrimitivePatch>
+Foam::externalDisplacementMeshMover::getPatch
+(
+    const polyMesh& mesh,
+    const labelList& patchIDs
+)
+{
+    const polyBoundaryMesh& patches = mesh.boundaryMesh();
+
+    // Count faces.
+    label nFaces = 0;
+
+    forAll(patchIDs, i)
+    {
+        const polyPatch& pp = patches[patchIDs[i]];
+
+        nFaces += pp.size();
+    }
+
+    // Collect faces.
+    labelList addressing(nFaces);
+    nFaces = 0;
+
+    forAll(patchIDs, i)
+    {
+        const polyPatch& pp = patches[patchIDs[i]];
+
+        label meshFaceI = pp.start();
+
+        forAll(pp, i)
+        {
+            addressing[nFaces++] = meshFaceI++;
+        }
+    }
+
+    return autoPtr<indirectPrimitivePatch>
+    (
+        new indirectPrimitivePatch
+        (
+            IndirectList<face>(mesh.faces(), addressing),
+            mesh.points()
+        )
+    );
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::externalDisplacementMeshMover::externalDisplacementMeshMover
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.H b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.H
index 9d98b435b8d0d0d12268c9b422bf99be6b551f16..d37a2bb9f2ee18abd660bce92b1fe42162ee0df3 100644
--- a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.H
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/externalDisplacementMeshMover.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -67,6 +67,19 @@ protected:
         pointVectorField& pointDisplacement_;
 
 
+    // Protected Member functions
+
+        //- Extract fixed-value patchfields
+        static labelList getFixedValueBCs(const pointVectorField&);
+
+        //- Construct patch on selected patches
+        static autoPtr<indirectPrimitivePatch> getPatch
+        (
+            const polyMesh&,
+            const labelList&
+        );
+
+
 private:
 
     // Private Member Functions
@@ -80,6 +93,7 @@ private:
         //- Disallow default bitwise assignment
         void operator=(const externalDisplacementMeshMover&);
 
+
 public:
 
     //- Runtime type information
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmoother.C b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmoother.C
new file mode 100644
index 0000000000000000000000000000000000000000..d80a7865ac10cb1ebbdcd9c88cf7e4eeee4d4fab
--- /dev/null
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmoother.C
@@ -0,0 +1,299 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 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 "fieldSmoother.H"
+#include "pointFields.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(fieldSmoother, 0);
+}
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::fieldSmoother::fieldSmoother(const polyMesh& mesh)
+:
+    mesh_(mesh)
+{}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::fieldSmoother::~fieldSmoother()
+{}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+void Foam::fieldSmoother::smoothNormals
+(
+    const label nIter,
+    const PackedBoolList& isMeshMasterPoint,
+    const PackedBoolList& isMeshMasterEdge,
+    const labelList& fixedPoints,
+    pointVectorField& normals
+) const
+{
+    // Get smoothly varying internal normals field.
+    Info<< typeName
+        << " : Smoothing normals in interior ..." << endl;
+
+    const edgeList& edges = mesh_.edges();
+
+    // Points that do not change.
+    PackedBoolList isFixedPoint(mesh_.nPoints());
+
+    // Internal points that are fixed
+    forAll(fixedPoints, i)
+    {
+        label meshPointI = fixedPoints[i];
+        isFixedPoint.set(meshPointI, 1);
+    }
+
+    // Make sure that points that are coupled to meshPoints but not on a patch
+    // are fixed as well
+    syncTools::syncPointList(mesh_, isFixedPoint, maxEqOp<unsigned int>(), 0);
+
+
+    // Correspondence between local edges/points and mesh edges/points
+    const labelList meshPoints(identity(mesh_.nPoints()));
+
+    // Calculate inverse sum of weights
+
+    scalarField edgeWeights(mesh_.nEdges());
+    scalarField invSumWeight(meshPoints.size());
+    meshRefinement::calculateEdgeWeights
+    (
+        mesh_,
+        isMeshMasterEdge,
+        meshPoints,
+        edges,
+        edgeWeights,
+        invSumWeight
+    );
+
+    vectorField average;
+    for (label iter = 0; iter < nIter; iter++)
+    {
+        meshRefinement::weightedSum
+        (
+            mesh_,
+            isMeshMasterEdge,
+            meshPoints,
+            edges,
+            edgeWeights,
+            normals,
+            average
+        );
+        average *= invSumWeight;
+
+        // Do residual calculation every so often.
+        if ((iter % 10) == 0)
+        {
+            scalar resid = meshRefinement::gAverage
+            (
+                isMeshMasterPoint,
+                mag(normals-average)()
+            );
+            Info<< "    Iteration " << iter << "   residual " << resid << endl;
+        }
+
+        // Transfer to normals vector field
+        forAll(average, pointI)
+        {
+            if (isFixedPoint.get(pointI) == 0)
+            {
+                //full smoothing neighbours + point value
+                average[pointI] = 0.5*(normals[pointI]+average[pointI]);
+                normals[pointI] = average[pointI];
+                normals[pointI] /= mag(normals[pointI]) + VSMALL;
+            }
+        }
+    }
+}
+
+
+void Foam::fieldSmoother::smoothPatchNormals
+(
+    const label nIter,
+    const PackedBoolList& isPatchMasterPoint,
+    const PackedBoolList& isPatchMasterEdge,
+    const indirectPrimitivePatch& adaptPatch,
+    pointField& normals
+) const
+{
+    const edgeList& edges = adaptPatch.edges();
+    const labelList& meshPoints = adaptPatch.meshPoints();
+
+    // Get smoothly varying internal normals field.
+    Info<< typeName << " : Smoothing normals ..." << endl;
+
+    scalarField edgeWeights(edges.size());
+    scalarField invSumWeight(meshPoints.size());
+    meshRefinement::calculateEdgeWeights
+    (
+        mesh_,
+        isPatchMasterEdge,
+        meshPoints,
+        edges,
+        edgeWeights,
+        invSumWeight
+    );
+
+
+    vectorField average;
+    for (label iter = 0; iter < nIter; iter++)
+    {
+        meshRefinement::weightedSum
+        (
+            mesh_,
+            isPatchMasterEdge,
+            meshPoints,
+            edges,
+            edgeWeights,
+            normals,
+            average
+        );
+        average *= invSumWeight;
+
+        // Do residual calculation every so often.
+        if ((iter % 10) == 0)
+        {
+            scalar resid = meshRefinement::gAverage
+            (
+                isPatchMasterPoint,
+                mag(normals-average)()
+            );
+            Info<< "    Iteration " << iter << "   residual " << resid << endl;
+        }
+
+        // Transfer to normals vector field
+        forAll(average, pointI)
+        {
+            // full smoothing neighbours + point value
+            average[pointI] = 0.5*(normals[pointI]+average[pointI]);
+            normals[pointI] = average[pointI];
+            normals[pointI] /= mag(normals[pointI]) + VSMALL;
+        }
+    }
+}
+
+
+void Foam::fieldSmoother::smoothLambdaMuDisplacement
+(
+    const label nIter,
+    const PackedBoolList& isMeshMasterPoint,
+    const PackedBoolList& isMeshMasterEdge,
+    const PackedBoolList& isToBeSmoothed,
+    vectorField& displacement
+) const
+{
+    const edgeList& edges = mesh_.edges();
+
+    // Correspondence between local edges/points and mesh edges/points
+    const labelList meshPoints(identity(mesh_.nPoints()));
+
+    // Calculate inverse sum of weights
+    scalarField edgeWeights(mesh_.nEdges());
+    scalarField invSumWeight(meshPoints.size());
+    meshRefinement::calculateEdgeWeights
+    (
+        mesh_,
+        isMeshMasterEdge,
+        meshPoints,
+        edges,
+        edgeWeights,
+        invSumWeight
+    );
+
+    // Get smoothly varying patch field.
+    Info<< typeName << " : Smoothing displacement ..." << endl;
+
+    const scalar lambda = 0.33;
+    const scalar mu = -0.34;
+
+    vectorField average;
+
+    for (label iter = 0; iter < nIter; iter++)
+    {
+        meshRefinement::weightedSum
+        (
+            mesh_,
+            isMeshMasterEdge,
+            meshPoints,
+            edges,
+            edgeWeights,
+            displacement,
+            average
+        );
+        average *= invSumWeight;
+
+        forAll(displacement, i)
+        {
+            if (isToBeSmoothed[i])
+            {
+                displacement[i] = (1-lambda)*displacement[i]+lambda*average[i];
+            }
+        }
+
+        meshRefinement::weightedSum
+        (
+            mesh_,
+            isMeshMasterEdge,
+            meshPoints,
+            edges,
+            edgeWeights,
+            displacement,
+            average
+        );
+        average *= invSumWeight;
+
+
+        forAll(displacement, i)
+        {
+            if (isToBeSmoothed[i])
+            {
+                displacement[i] = (1-mu)*displacement[i]+mu*average[i];
+            }
+        }
+
+
+        // Do residual calculation every so often.
+        if ((iter % 10) == 0)
+        {
+            scalar resid = meshRefinement::gAverage
+            (
+                isMeshMasterPoint,
+                mag(displacement-average)()
+            );
+            Info<< "    Iteration " << iter << "   residual " << resid << endl;
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmoother.H b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmoother.H
new file mode 100644
index 0000000000000000000000000000000000000000..bb379f76a3c889367f1bcf0efe5c9ab37831706f
--- /dev/null
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmoother.H
@@ -0,0 +1,141 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 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::fieldSmoother
+
+Description
+    Utility functions for mesh motion solvers
+
+SourceFiles
+    fieldSmoother.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef fieldSmoother_H
+#define fieldSmoother_H
+
+#include "meshRefinement.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                         Class fieldSmoother Declaration
+\*---------------------------------------------------------------------------*/
+
+class fieldSmoother
+{
+    // Private data
+
+        //- Reference to the poly mesh
+        const polyMesh& mesh_;
+
+
+    // Private Member Functions
+
+        //- Disallow default bitwise copy construct
+        fieldSmoother(const fieldSmoother&);
+
+        //- Disallow default bitwise assignment
+        void operator=(const fieldSmoother&);
+
+
+public:
+
+    // Run-time type information
+    TypeName("fieldSmoother");
+
+
+    // Constructors
+
+        //- Construct from a polyMesh
+        fieldSmoother(const polyMesh&);
+
+
+    //- Destructor
+    virtual ~fieldSmoother();
+
+
+    // Member Functions
+
+        //- Smooth interior normals
+        void smoothNormals
+        (
+            const label nIter,
+            const PackedBoolList& isMeshMasterPoint,
+            const PackedBoolList& isMeshMasterEdge,
+            const labelList& fixedPoints,
+            pointVectorField& normals
+        ) const;
+
+        //- Smooth patch normals
+        void smoothPatchNormals
+        (
+            const label nIter,
+            const PackedBoolList& isPatchMasterPoint,
+            const PackedBoolList& isPatchMasterEdge,
+            const indirectPrimitivePatch& adaptPatch,
+            pointField& normals
+        ) const;
+
+        //- Smooth a scalar field towards, but not beyond, a minimum value
+        template <class Type>
+        void minSmoothField
+        (
+            const label nIter,
+            const PackedBoolList& isPatchMasterPoint,
+            const PackedBoolList& isPatchMasterEdge,
+            const indirectPrimitivePatch& adaptPatch,
+            const scalarField& fieldMin,
+            Field<Type>& field
+        ) const;
+
+        //- Smooth and then un-smooth a displacement
+        void smoothLambdaMuDisplacement
+        (
+            const label nIter,
+            const PackedBoolList& isMeshMasterPoint,
+            const PackedBoolList& isMeshMasterEdge,
+            const PackedBoolList& isSmoothable,
+            vectorField& displacement
+        ) const;
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+#   include "fieldSmootherTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmootherTemplates.C b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmootherTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..be2f2cfd0b4f8883ef58f82783c6e6c948ebbbc3
--- /dev/null
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/fieldSmoother/fieldSmootherTemplates.C
@@ -0,0 +1,103 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2015 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+template <class Type>
+void Foam::fieldSmoother::minSmoothField
+(
+    const label nIter,
+    const PackedBoolList& isPatchMasterPoint,
+    const PackedBoolList& isPatchMasterEdge,
+    const indirectPrimitivePatch& adaptPatch,
+    const scalarField& fieldMinMag,
+    Field<Type>& field
+) const
+{
+    const edgeList& edges = adaptPatch.edges();
+    const labelList& meshPoints = adaptPatch.meshPoints();
+
+    scalarField edgeWeights(edges.size());
+    scalarField invSumWeight(meshPoints.size());
+    meshRefinement::calculateEdgeWeights
+    (
+        mesh_,
+        isPatchMasterEdge,
+        meshPoints,
+        edges,
+        edgeWeights,
+        invSumWeight
+    );
+
+    // Get smoothly varying patch field.
+    Info<< typeName << " : Smoothing field ..." << endl;
+
+    for (label iter = 0; iter < nIter; iter++)
+    {
+        Field<Type> average(adaptPatch.nPoints());
+        meshRefinement::weightedSum
+        (
+            mesh_,
+            isPatchMasterEdge,
+            meshPoints,
+            edges,
+            edgeWeights,
+            field,
+            average
+        );
+        average *= invSumWeight;
+
+        // Transfer to field
+        forAll(field, pointI)
+        {
+            //full smoothing neighbours + point value
+            average[pointI] = 0.5*(field[pointI]+average[pointI]);
+
+            // perform monotonic smoothing
+            if
+            (
+                mag(average[pointI]) < mag(field[pointI])
+             && mag(average[pointI]) >= mag(fieldMinMag[pointI])
+            )
+            {
+                field[pointI] = average[pointI];
+            }
+        }
+
+        // Do residual calculation every so often.
+        if ((iter % 10) == 0)
+        {
+            scalar resid = meshRefinement::gAverage
+            (
+                isPatchMasterPoint,
+                mag(field-average)()
+            );
+            Info<< "    Iteration " << iter << "   residual " << resid << endl;
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.C b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.C
index df17b0ece4a2d35c717f06776fa56929e296aed6..fd993ae20bc07313dd12cadeb2c0eb691f9ff7db 100644
--- a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.C
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,7 +32,7 @@ License
 #include "unitConversion.H"
 #include "PatchTools.H"
 #include "OBJstream.H"
-#include "pointData.H"
+#include "PointData.H"
 #include "zeroFixedValuePointPatchFields.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -52,241 +52,11 @@ namespace Foam
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-Foam::labelList Foam::medialAxisMeshMover::getFixedValueBCs
-(
-    const pointVectorField& fld
-)
-{
-    DynamicList<label> adaptPatchIDs;
-    forAll(fld.boundaryField(), patchI)
-    {
-        const pointPatchField<vector>& patchFld =
-            fld.boundaryField()[patchI];
-
-        if (isA<valuePointPatchField<vector> >(patchFld))
-        {
-            if (isA<zeroFixedValuePointPatchField<vector> >(patchFld))
-            {
-                // Special condition of fixed boundary condition. Does not
-                // get adapted
-            }
-            else
-            {
-                adaptPatchIDs.append(patchI);
-            }
-        }
-    }
-    return adaptPatchIDs;
-}
-
-
-Foam::autoPtr<Foam::indirectPrimitivePatch>
-Foam::medialAxisMeshMover::getPatch
-(
-    const polyMesh& mesh,
-    const labelList& patchIDs
-)
-{
-    const polyBoundaryMesh& patches = mesh.boundaryMesh();
-
-    // Count faces.
-    label nFaces = 0;
-
-    forAll(patchIDs, i)
-    {
-        const polyPatch& pp = patches[patchIDs[i]];
-
-        nFaces += pp.size();
-    }
-
-    // Collect faces.
-    labelList addressing(nFaces);
-    nFaces = 0;
-
-    forAll(patchIDs, i)
-    {
-        const polyPatch& pp = patches[patchIDs[i]];
-
-        label meshFaceI = pp.start();
-
-        forAll(pp, i)
-        {
-            addressing[nFaces++] = meshFaceI++;
-        }
-    }
-
-    return autoPtr<indirectPrimitivePatch>
-    (
-        new indirectPrimitivePatch
-        (
-            IndirectList<face>(mesh.faces(), addressing),
-            mesh.points()
-        )
-    );
-}
-
-
-void Foam::medialAxisMeshMover::smoothPatchNormals
-(
-    const label nSmoothDisp,
-    const PackedBoolList& isPatchMasterPoint,
-    const PackedBoolList& isPatchMasterEdge,
-    pointField& normals
-) const
-{
-    const indirectPrimitivePatch& pp = adaptPatchPtr_();
-    const edgeList& edges = pp.edges();
-    const labelList& meshPoints = pp.meshPoints();
-
-    // Get smoothly varying internal normals field.
-    Info<< typeName << " : Smoothing normals ..." << endl;
-
-    scalarField edgeWeights(edges.size());
-    scalarField invSumWeight(meshPoints.size());
-    meshRefinement::calculateEdgeWeights
-    (
-        mesh(),
-        isPatchMasterEdge,
-        meshPoints,
-        edges,
-        edgeWeights,
-        invSumWeight
-    );
-
-
-    vectorField average;
-    for (label iter = 0; iter < nSmoothDisp; iter++)
-    {
-        meshRefinement::weightedSum
-        (
-            mesh(),
-            isPatchMasterEdge,
-            meshPoints,
-            edges,
-            edgeWeights,
-            normals,
-            average
-        );
-        average *= invSumWeight;
-
-        // Do residual calculation every so often.
-        if ((iter % 10) == 0)
-        {
-            scalar resid = meshRefinement::gAverage
-            (
-                isPatchMasterPoint,
-                mag(normals-average)()
-            );
-            Info<< "    Iteration " << iter << "   residual " << resid << endl;
-        }
-
-        // Transfer to normals vector field
-        forAll(average, pointI)
-        {
-            // full smoothing neighbours + point value
-            average[pointI] = 0.5*(normals[pointI]+average[pointI]);
-            normals[pointI] = average[pointI];
-            normals[pointI] /= mag(normals[pointI]) + VSMALL;
-        }
-    }
-}
-
-
-// Smooth normals in interior.
-void Foam::medialAxisMeshMover::smoothNormals
-(
-    const label nSmoothDisp,
-    const PackedBoolList& isMeshMasterPoint,
-    const PackedBoolList& isMeshMasterEdge,
-    const labelList& fixedPoints,
-    pointVectorField& normals
-) const
-{
-    // Get smoothly varying internal normals field.
-    Info<< typeName
-        << " : Smoothing normals in interior ..." << endl;
-
-    const edgeList& edges = mesh().edges();
-
-    // Points that do not change.
-    PackedBoolList isFixedPoint(mesh().nPoints());
-
-    // Internal points that are fixed
-    forAll(fixedPoints, i)
-    {
-        label meshPointI = fixedPoints[i];
-        isFixedPoint.set(meshPointI, 1);
-    }
-
-    // Make sure that points that are coupled to meshPoints but not on a patch
-    // are fixed as well
-    syncTools::syncPointList(mesh(), isFixedPoint, maxEqOp<unsigned int>(), 0);
-
-
-    // Correspondence between local edges/points and mesh edges/points
-    const labelList meshPoints(identity(mesh().nPoints()));
-
-    // Calculate inverse sum of weights
-
-    scalarField edgeWeights(mesh().nEdges());
-    scalarField invSumWeight(meshPoints.size());
-    meshRefinement::calculateEdgeWeights
-    (
-        mesh(),
-        isMeshMasterEdge,
-        meshPoints,
-        edges,
-        edgeWeights,
-        invSumWeight
-    );
-
-    vectorField average;
-    for (label iter = 0; iter < nSmoothDisp; iter++)
-    {
-        meshRefinement::weightedSum
-        (
-            mesh(),
-            isMeshMasterEdge,
-            meshPoints,
-            edges,
-            edgeWeights,
-            normals,
-            average
-        );
-        average *= invSumWeight;
-
-        // Do residual calculation every so often.
-        if ((iter % 10) == 0)
-        {
-            scalar resid = meshRefinement::gAverage
-            (
-                isMeshMasterPoint,
-                mag(normals-average)()
-            );
-            Info<< "    Iteration " << iter << "   residual " << resid << endl;
-        }
-
-
-        // Transfer to normals vector field
-        forAll(average, pointI)
-        {
-            if (isFixedPoint.get(pointI) == 0)
-            {
-                //full smoothing neighbours + point value
-                average[pointI] = 0.5*(normals[pointI]+average[pointI]);
-                normals[pointI] = average[pointI];
-                normals[pointI] /= mag(normals[pointI]) + VSMALL;
-            }
-        }
-    }
-}
-
-
 // Tries and find a medial axis point. Done by comparing vectors to nearest
 // wall point for both vertices of edge.
 bool Foam::medialAxisMeshMover::isMaxEdge
 (
-    const List<pointData>& pointWallDist,
+    const List<PointData<vector> >& pointWallDist,
     const label edgeI,
     const scalar minCos
 ) const
@@ -331,7 +101,7 @@ bool Foam::medialAxisMeshMover::isMaxEdge
     //- Detect based on extrusion vector differing for both endpoints
     //  the idea is that e.g. a sawtooth wall can still be extruded
     //  successfully as long as it is done all to the same direction.
-    if ((pointWallDist[e[0]].v() & pointWallDist[e[1]].v()) < minCos)
+    if ((pointWallDist[e[0]].data() & pointWallDist[e[1]].data()) < minCos)
     {
         return true;
     }
@@ -439,11 +209,12 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
     pointField pointNormals(PatchTools::pointNormals(mesh(), pp));
 
     // Smooth patch normal vectors
-    smoothPatchNormals
+    fieldSmoother_.smoothPatchNormals
     (
         nSmoothSurfaceNormals,
         isPatchMasterPoint,
         isPatchMasterEdge,
+        pp,
         pointNormals
     );
 
@@ -452,7 +223,7 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     // Distance to wall
-    List<pointData> pointWallDist(mesh().nPoints());
+    List<PointData<vector> > pointWallDist(mesh().nPoints());
 
     // Dummy additional info for PointEdgeWave
     int dummyTrackData = 0;
@@ -461,23 +232,22 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
     // 1. Calculate distance to points where displacement is specified.
     {
         // Seed data.
-        List<pointData> wallInfo(meshPoints.size());
+        List<PointData<vector> > wallInfo(meshPoints.size());
 
         forAll(meshPoints, patchPointI)
         {
             label pointI = meshPoints[patchPointI];
-            wallInfo[patchPointI] = pointData
+            wallInfo[patchPointI] = PointData<vector>
             (
                 points[pointI],
                 0.0,
-                pointI,                       // passive scalar
                 pointNormals[patchPointI]     // surface normals
             );
         }
 
         // Do all calculations
-        List<pointData> edgeWallDist(mesh().nEdges());
-        PointEdgeWave<pointData> wallDistCalc
+        List<PointData<vector> > edgeWallDist(mesh().nEdges());
+        PointEdgeWave<PointData<vector> > wallDistCalc
         (
             mesh(),
             meshPoints,
@@ -525,11 +295,11 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
     // 2. Find points with max distance and transport information back to
     //    wall.
     {
-        List<pointData> pointMedialDist(mesh().nPoints());
-        List<pointData> edgeMedialDist(mesh().nEdges());
+        List<pointEdgePoint> pointMedialDist(mesh().nPoints());
+        List<pointEdgePoint> edgeMedialDist(mesh().nEdges());
 
         // Seed point data.
-        DynamicList<pointData> maxInfo(meshPoints.size());
+        DynamicList<pointEdgePoint> maxInfo(meshPoints.size());
         DynamicList<label> maxPoints(meshPoints.size());
 
         // 1. Medial axis points
@@ -556,12 +326,10 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
                         maxPoints.append(pointI);
                         maxInfo.append
                         (
-                            pointData
+                            pointEdgePoint
                             (
                                 points[pointI],
-                                0.0,
-                                pointI,         // passive data
-                                vector::zero    // passive data
+                                0.0
                             )
                         );
                         pointMedialDist[pointI] = maxInfo.last();
@@ -612,12 +380,10 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
                             maxPoints.append(pointI);
                             maxInfo.append
                             (
-                                pointData
+                                pointEdgePoint
                                 (
                                     medialAxisPt,   //points[pointI],
-                                    magSqr(points[pointI]-medialAxisPt),//0.0,
-                                    pointI,         // passive data
-                                    vector::zero    // passive data
+                                    magSqr(points[pointI]-medialAxisPt)//0.0
                                 )
                             );
                             pointMedialDist[pointI] = maxInfo.last();
@@ -669,12 +435,10 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
                             maxPoints.append(pointI);
                             maxInfo.append
                             (
-                                pointData
+                                pointEdgePoint
                                 (
                                     points[pointI],
-                                    0.0,
-                                    pointI,         // passive data
-                                    vector::zero    // passive data
+                                    0.0
                                 )
                             );
                             pointMedialDist[pointI] = maxInfo.last();
@@ -715,7 +479,7 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
                             // Check if angle not too large.
                             scalar cosAngle =
                             (
-                               -pointWallDist[pointI].v()
+                               -pointWallDist[pointI].data()
                               & pointNormals[i]
                             );
                             if (cosAngle > slipFeatureAngleCos)
@@ -726,12 +490,10 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
                                 maxPoints.append(pointI);
                                 maxInfo.append
                                 (
-                                    pointData
+                                    pointEdgePoint
                                     (
                                         points[pointI],
-                                        0.0,
-                                        pointI,         // passive data
-                                        vector::zero    // passive data
+                                        0.0
                                     )
                                 );
                                 pointMedialDist[pointI] = maxInfo.last();
@@ -751,7 +513,7 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
         maxPoints.shrink();
 
         // Do all calculations
-        PointEdgeWave<pointData> medialDistCalc
+        PointEdgeWave<pointEdgePoint> medialDistCalc
         (
             mesh(),
             maxPoints,
@@ -794,12 +556,12 @@ void Foam::medialAxisMeshMover::update(const dictionary& coeffDict)
         }
         else
         {
-            dispVec_[i] = pointWallDist[i].v();
+            dispVec_[i] = pointWallDist[i].data();
         }
     }
 
     // Smooth normal vectors. Do not change normals on pp.meshPoints
-    smoothNormals
+    fieldSmoother_.smoothNormals
     (
         nSmoothNormals,
         isMeshMasterPoint,
@@ -1003,79 +765,6 @@ void Foam::medialAxisMeshMover::syncPatchDisplacement
 }
 
 
-void Foam::medialAxisMeshMover::minSmoothField
-(
-    const label nSmoothDisp,
-    const PackedBoolList& isPatchMasterPoint,
-    const PackedBoolList& isPatchMasterEdge,
-    const scalarField& fieldMin,
-    scalarField& field
-) const
-{
-    const indirectPrimitivePatch& pp = adaptPatchPtr_();
-    const edgeList& edges = pp.edges();
-    const labelList& meshPoints = pp.meshPoints();
-
-    scalarField edgeWeights(edges.size());
-    scalarField invSumWeight(meshPoints.size());
-    meshRefinement::calculateEdgeWeights
-    (
-        mesh(),
-        isPatchMasterEdge,
-        meshPoints,
-        edges,
-        edgeWeights,
-        invSumWeight
-    );
-
-    // Get smoothly varying patch field.
-    Info<< typeName << " : Smoothing field ..." << endl;
-
-    for (label iter = 0; iter < nSmoothDisp; iter++)
-    {
-        scalarField average(pp.nPoints());
-        meshRefinement::weightedSum
-        (
-            mesh(),
-            isPatchMasterEdge,
-            meshPoints,
-            edges,
-            edgeWeights,
-            field,
-            average
-        );
-        average *= invSumWeight;
-
-        // Transfer to field
-        forAll(field, pointI)
-        {
-            //full smoothing neighbours + point value
-            average[pointI] = 0.5*(field[pointI]+average[pointI]);
-
-            // perform monotonic smoothing
-            if
-            (
-                average[pointI] < field[pointI]
-             && average[pointI] >= fieldMin[pointI]
-            )
-            {
-                field[pointI] = average[pointI];
-            }
-        }
-
-        // Do residual calculation every so often.
-        if ((iter % 10) == 0)
-        {
-            scalar resid = meshRefinement::gAverage
-            (
-                isPatchMasterPoint,
-                mag(field-average)()
-            );
-            Info<< "    Iteration " << iter << "   residual " << resid << endl;
-        }
-    }
-}
-
 // Stop layer growth where mesh wraps around edge with a
 // large feature angle
 void Foam::medialAxisMeshMover::
@@ -1204,7 +893,8 @@ handleFeatureAngleLayerTerminations
 
     //Info<< "Added "
     //    << returnReduce(nPointCounter-nOldPointCounter, sumOp<label>())
-    //    << " point not to extrude." << endl;
+    //    << " point not to extrude due to minCos "
+    //    << minCos << endl;
 }
 
 
@@ -1226,47 +916,43 @@ void Foam::medialAxisMeshMover::findIsolatedRegions
     const labelListList& pointFaces = pp.pointFaces();
     const labelList& meshPoints = pp.meshPoints();
 
-    Info<< typeName << " : Removing isolated regions ..." << endl;
-
-    // Keep count of number of points unextruded
-    label nPointCounter = 0;
 
-
-    autoPtr<OBJstream> str;
-    if (debug)
+    Info<< typeName << " : Removing isolated regions ..." << nl
+        << indent << "- if partially extruded faces make angle < "
+        << Foam::radToDeg(Foam::acos(minCosLayerTermination)) <<  nl;
+    if (detectExtrusionIsland)
     {
-        str.reset
-        (
-            new OBJstream
-            (
-                mesh().time().path()
-              / "islandExcludePoints_"
-              + mesh().time().timeName()
-              + ".obj"
-            )
-        );
-        Info<< typeName
-            << " : Writing points surrounded by non-extruded points to "
-            << str().name() << endl;
+        Info<< indent << "- if exclusively surrounded by non-extruded points"
+            << nl;
     }
+    else
+    {
+        Info<< indent << "- if exclusively surrounded by non-extruded faces"
+            << nl;
+    }
+
+    // Keep count of number of points unextruded
+    label nPointCounter = 0;
 
     while (true)
     {
         // Stop layer growth where mesh wraps around edge with a
         // large feature angle
-        handleFeatureAngleLayerTerminations
-        (
-            minCosLayerTermination,
-            isPatchMasterPoint,
-            meshEdges,
-
-            extrudeStatus,
-            patchDisp,
-            nPointCounter
-        );
+        if (minCosLayerTermination > -1)
+        {
+            handleFeatureAngleLayerTerminations
+            (
+                minCosLayerTermination,
+                isPatchMasterPoint,
+                meshEdges,
 
-        syncPatchDisplacement(minThickness, patchDisp, extrudeStatus);
+                extrudeStatus,
+                patchDisp,
+                nPointCounter
+            );
 
+            syncPatchDisplacement(minThickness, patchDisp, extrudeStatus);
+        }
 
 
         // Detect either:
@@ -1389,11 +1075,6 @@ void Foam::medialAxisMeshMover::findIsolatedRegions
                 {
                     nPointCounter++;
                     nChanged++;
-
-                    if (str.valid())
-                    {
-                        str().write(pp.points()[meshPoints[patchPointI]]);
-                    }
                 }
             }
         }
@@ -1483,11 +1164,6 @@ void Foam::medialAxisMeshMover::findIsolatedRegions
                     )
                     {
                         nPointCounter++;
-
-                        if (str.valid())
-                        {
-                            str().write(pp.points()[meshPoints[f[fp]]]);
-                        }
                     }
                 }
             }
@@ -1501,98 +1177,6 @@ void Foam::medialAxisMeshMover::findIsolatedRegions
 }
 
 
-void Foam::medialAxisMeshMover::smoothLambdaMuDisplacement
-(
-    const label nSmoothDisp,
-    const PackedBoolList& isMeshMasterPoint,
-    const PackedBoolList& isMeshMasterEdge,
-    vectorField& displacement
-) const
-{
-    const edgeList& edges = mesh().edges();
-
-    // Correspondence between local edges/points and mesh edges/points
-    const labelList meshPoints(identity(mesh().nPoints()));
-
-    // Calculate inverse sum of weights
-    scalarField edgeWeights(mesh().nEdges());
-    scalarField invSumWeight(meshPoints.size());
-    meshRefinement::calculateEdgeWeights
-    (
-        mesh(),
-        isMeshMasterEdge,
-        meshPoints,
-        edges,
-        edgeWeights,
-        invSumWeight
-    );
-
-    // Get smoothly varying patch field.
-    Info<< typeName << " : Smoothing displacement ..." << endl;
-
-    const scalar lambda = 0.33;
-    const scalar mu = -0.34;
-
-    vectorField average;
-
-    for (label iter = 0; iter < nSmoothDisp; iter++)
-    {
-        meshRefinement::weightedSum
-        (
-            mesh(),
-            isMeshMasterEdge,
-            meshPoints,
-            edges,
-            edgeWeights,
-            displacement,
-            average
-        );
-        average *= invSumWeight;
-
-        forAll(displacement, i)
-        {
-            if (medialRatio_[i] > SMALL && medialRatio_[i] < 1-SMALL)
-            {
-                displacement[i] = (1-lambda)*displacement[i]+lambda*average[i];
-            }
-        }
-
-        meshRefinement::weightedSum
-        (
-            mesh(),
-            isMeshMasterEdge,
-            meshPoints,
-            edges,
-            edgeWeights,
-            displacement,
-            average
-        );
-        average *= invSumWeight;
-
-
-        forAll(displacement, i)
-        {
-            if (medialRatio_[i] > SMALL && medialRatio_[i] < 1-SMALL)
-            {
-                displacement[i] = (1-mu)*displacement[i]+mu*average[i];
-            }
-        }
-
-
-        // Do residual calculation every so often.
-        if ((iter % 10) == 0)
-        {
-            scalar resid = meshRefinement::gAverage
-            (
-                isMeshMasterPoint,
-                mag(displacement-average)()
-            );
-            Info<< "    Iteration " << iter << "   residual " << resid << endl;
-        }
-    }
-}
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::medialAxisMeshMover::medialAxisMeshMover
@@ -1630,6 +1214,7 @@ Foam::medialAxisMeshMover::medialAxisMeshMover
         adaptPatchIDs_,
         dict
     ),
+    fieldSmoother_(mesh()),
     dispVec_
     (
         IOobject
@@ -1733,10 +1318,12 @@ void Foam::medialAxisMeshMover::calculateDisplacement
     const scalar featureAngle = readScalar(coeffDict.lookup("featureAngle"));
 
     //- Stop layer growth where mesh wraps around sharp edge
-    const scalar minCosLayerTermination = Foam::cos
+    scalar layerTerminationAngle = coeffDict.lookupOrDefault<scalar>
     (
-        degToRad(0.5*featureAngle)
+        "layerTerminationAngle",
+        0.5*featureAngle
     );
+    scalar minCosLayerTermination = Foam::cos(degToRad(layerTerminationAngle));
 
     //- Smoothing wanted patch thickness
     const label nSmoothPatchThickness = readLabel
@@ -1791,9 +1378,7 @@ void Foam::medialAxisMeshMover::calculateDisplacement
     );
 
 
-    scalarField thickness(patchDisp.size());
-
-    thickness = mag(patchDisp);
+    scalarField thickness(mag(patchDisp));
 
     forAll(thickness, patchPointI)
     {
@@ -1860,7 +1445,7 @@ void Foam::medialAxisMeshMover::calculateDisplacement
             vector n =
                 patchDisp[patchPointI]
               / (mag(patchDisp[patchPointI]) + VSMALL);
-            vector mVec = mesh().points()[pointI]-medialVec_[pointI];
+            vector mVec = medialVec_[pointI]-mesh().points()[pointI];
             mVec /= mag(mVec)+VSMALL;
             thicknessRatio *= (n&mVec);
 
@@ -1947,14 +1532,17 @@ void Foam::medialAxisMeshMover::calculateDisplacement
     }
 
 
-    // smooth layer thickness on moving patch
-    minSmoothField
+    // Smooth layer thickness on moving patch. Since some locations will have
+    // disabled the extrusion this might cause big jumps in wanted displacement
+    // for neighbouring patch points. So smooth the wanted displacement
+    // before actually trying to move the mesh.
+    fieldSmoother_.minSmoothField
     (
         nSmoothPatchThickness,
         isPatchMasterPoint,
         isPatchMasterEdge,
+        pp,
         minThickness,
-
         thickness
     );
 
@@ -1962,34 +1550,33 @@ void Foam::medialAxisMeshMover::calculateDisplacement
     // Dummy additional info for PointEdgeWave
     int dummyTrackData = 0;
 
-    List<pointData> pointWallDist(mesh().nPoints());
+    List<PointData<scalar> > pointWallDist(mesh().nPoints());
 
     const pointField& points = mesh().points();
     // 1. Calculate distance to points where displacement is specified.
     // This wave is used to transport layer thickness
     {
         // Distance to wall and medial axis on edges.
-        List<pointData> edgeWallDist(mesh().nEdges());
+        List<PointData<scalar> > edgeWallDist(mesh().nEdges());
         labelList wallPoints(meshPoints.size());
 
         // Seed data.
-        List<pointData> wallInfo(meshPoints.size());
+        List<PointData<scalar> > wallInfo(meshPoints.size());
 
         forAll(meshPoints, patchPointI)
         {
             label pointI = meshPoints[patchPointI];
             wallPoints[patchPointI] = pointI;
-            wallInfo[patchPointI] = pointData
+            wallInfo[patchPointI] = PointData<scalar>
             (
                 points[pointI],
                 0.0,
-                thickness[patchPointI],       // transport layer thickness
-                vector::zero                  // passive vector
+                thickness[patchPointI]        // transport layer thickness
             );
         }
 
         // Do all calculations
-        PointEdgeWave<pointData> wallDistCalc
+        PointEdgeWave<PointData<scalar> > wallDistCalc
         (
             mesh(),
             wallPoints,
@@ -2016,12 +1603,12 @@ void Foam::medialAxisMeshMover::calculateDisplacement
         {
             // 1) displacement on nearest wall point, scaled by medialRatio
             //    (wall distance / medial distance)
-            // 2) pointWallDist[pointI].s() is layer thickness transported
+            // 2) pointWallDist[pointI].data() is layer thickness transported
             //    from closest wall point.
             // 3) shrink in opposite direction of addedPoints
             displacement[pointI] =
                 -medialRatio_[pointI]
-                *pointWallDist[pointI].s()
+                *pointWallDist[pointI].data()
                 *dispVec_[pointI];
         }
     }
@@ -2030,11 +1617,20 @@ void Foam::medialAxisMeshMover::calculateDisplacement
     // Smear displacement away from fixed values (medialRatio=0 or 1)
     if (nSmoothDisplacement > 0)
     {
-        smoothLambdaMuDisplacement
+        PackedBoolList isToBeSmoothed(displacement.size(), false);
+
+        forAll(displacement, i)
+        {
+            isToBeSmoothed[i] =
+                medialRatio_[i] > SMALL && medialRatio_[i] < 1-SMALL;
+        }
+
+        fieldSmoother_.smoothLambdaMuDisplacement
         (
             nSmoothDisplacement,
             isMeshMasterPoint,
             isMeshMasterEdge,
+            isToBeSmoothed,
             displacement
         );
     }
@@ -2052,8 +1648,6 @@ bool Foam::medialAxisMeshMover::shrinkMesh
     const label nSnap  = readLabel(meshQualityDict.lookup("nRelaxIter"));
 
 
-
-
     // Make sure displacement boundary conditions is uptodate with
     // internal field
     meshMover_.setDisplacementPatchFields();
@@ -2119,14 +1713,6 @@ bool Foam::medialAxisMeshMover::move
     const word minThicknessName = word(moveDict.lookup("minThicknessName"));
 
 
-    // The points have moved so before calculation update
-    // the mesh and motionSolver accordingly
-    movePoints(mesh().points());
-    //
-    //// Update any point motion bcs (e.g. timevarying)
-    //pointDisplacement_.boundaryField().updateCoeffs();
-
-
     // Extract out patch-wise displacement
     const indirectPrimitivePatch& pp = adaptPatchPtr_();
 
@@ -2180,13 +1766,10 @@ void Foam::medialAxisMeshMover::movePoints(const pointField& p)
 {
     externalDisplacementMeshMover::movePoints(p);
 
-    // Update local data for new geometry
-    adaptPatchPtr_().movePoints(p);
-
-    // Update motionSmoother for new geometry
+    // Update motionSmoother for new geometry (moves adaptPatchPtr_)
     meshMover_.movePoints();
 
-    // Assume corrent mesh location is correct
+    // Assume corrent mesh location is correct (reset oldPoints, scale)
     meshMover_.correct();
 }
 
diff --git a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.H b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.H
index a060a04fe6fb2ea685ee0f812a341b993d24eec4..b235182f6897564d0faeee26fd8bdf461a5c2b3e 100644
--- a/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.H
+++ b/src/mesh/autoMesh/autoHexMesh/externalDisplacementMeshMover/medialAxisMeshMover.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2014-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,10 +28,13 @@ Description
     Mesh motion solver that uses a medial axis algorithm to work
     out a fraction between the (nearest point on a) moving surface
     and the (nearest point on a) fixed surface.
+
     This fraction is then used to scale the motion. Use
     - fixedValue on all moving patches
     - zeroFixedValue on stationary patches
     - slip on all slipping patches
+    Note that the fixedValue boundary conditions might be changed by this
+    solver to enforce consistency and a valid resulting mesh.
 
 SourceFiles
     medialAxisMeshMover.C
@@ -44,13 +47,15 @@ SourceFiles
 #include "externalDisplacementMeshMover.H"
 #include "motionSmootherAlgo.H"
 #include "autoLayerDriver.H"
+#include "fieldSmoother.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-class pointData;
+template <class DataType>
+class PointData;
 
 /*---------------------------------------------------------------------------*\
              Class medialAxisMeshMover Declaration
@@ -75,6 +80,9 @@ class medialAxisMeshMover
         //- Mesh mover algorithm
         motionSmootherAlgo meshMover_;
 
+        //- Field smoothing
+        fieldSmoother fieldSmoother_;
+
 
     // Pre-calculated medial axis information
 
@@ -95,45 +103,16 @@ class medialAxisMeshMover
 
     // Private Member Functions
 
-        //- Extract fixed-value patchfields
-        static labelList getFixedValueBCs(const pointVectorField&);
-
         //- Extract bc types. Replace fixedValue derivatives with fixedValue
         wordList getPatchFieldTypes(const pointVectorField& fld);
 
-        //- Construct patch on selected patches
-        static autoPtr<indirectPrimitivePatch> getPatch
-        (
-            const polyMesh&,
-            const labelList&
-        );
-
 
         // Calculation of medial axis information
 
-            //- Smooth normals on patch
-            void smoothPatchNormals
-            (
-                const label nSmoothDisp,
-                const PackedBoolList& isMasterPoint,
-                const PackedBoolList& isMasterEdge,
-                pointField& normals
-            ) const;
-
-            //- Smooth normals on interior
-            void smoothNormals
-            (
-                const label nSmoothDisp,
-                const PackedBoolList& isMasterPoint,
-                const PackedBoolList& isMasterEdge,
-                const labelList& fixedPoints,
-                pointVectorField& normals
-            ) const;
-
             //- Is mesh edge on a cusp of displacement
             bool isMaxEdge
             (
-                const List<pointData>& pointWallDist,
+                const List<PointData<vector> >& pointWallDist,
                 const label edgeI,
                 const scalar minCos
             ) const;
@@ -161,23 +140,6 @@ class medialAxisMeshMover
                 List<autoLayerDriver::extrudeMode>& extrudeStatus
             ) const;
 
-            void smoothLambdaMuDisplacement
-            (
-                const label nSmoothDisp,
-                const PackedBoolList& isMasterPoint,
-                const PackedBoolList& isMasterEdge,
-                vectorField& displacement
-            ) const;
-
-            void minSmoothField
-            (
-                const label nSmoothDisp,
-                const PackedBoolList& isMasterPoint,
-                const PackedBoolList& isMasterEdge,
-                const scalarField& fieldMin,
-                scalarField& field
-            ) const;
-
             //- Stop layer growth at feature edges
             void handleFeatureAngleLayerTerminations
             (
diff --git a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.C b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.C
index 5d96aafca974d709eb1310271ea6c757865b9152..7b77e35875e5d22bbdf48523e3822b951c969283 100644
--- a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.C
+++ b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -29,7 +29,6 @@ License
 #include "surfaceMesh.H"
 #include "syncTools.H"
 #include "Time.H"
-#include "refinementHistory.H"
 #include "refinementSurfaces.H"
 #include "refinementFeatures.H"
 #include "decompositionMethod.H"
@@ -56,6 +55,8 @@ License
 #include "treeBoundBox.H"
 #include "zeroGradientFvPatchFields.H"
 #include "fvMeshTools.H"
+#include "motionSmoother.H"
+#include "faceSet.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -176,7 +177,13 @@ void Foam::meshRefinement::calcNeighbourData
 
                 label own = faceCells[i];
                 label ownLevel = cellLevel[own];
-                label faceLevel = meshCutter_.getAnchorLevel(pp.start()+i);
+                label faceLevel = meshCutter_.faceLevel(pp.start()+i);
+                if (faceLevel < 0)
+                {
+                    // Due to e.g. face merging no longer a consistent
+                    // refinementlevel of face. Assume same as cell
+                    faceLevel = ownLevel;
+                }
 
                 // Normal distance from face centre to cell centre
                 scalar d = ((faceCentres[i] - cellCentres[own]) & fn);
@@ -284,6 +291,7 @@ void Foam::meshRefinement::updateIntersections(const labelList& changedFaces)
         labelList surfaceLevel;
         surfaces_.findHigherIntersection
         (
+            shells_,
             start,
             end,
             labelList(start.size(), -1),    // accept any intersection
@@ -495,6 +503,7 @@ void Foam::meshRefinement::checkData()
             labelList surfaceLevel;
             surfaces_.findHigherIntersection
             (
+                shells_,
                 start,
                 end,
                 labelList(start.size(), -1),    // accept any intersection
@@ -545,7 +554,13 @@ void Foam::meshRefinement::checkData()
                         << " current:" << surfaceHit[faceI]
                         << " ownCc:"
                         << mesh_.cellCentres()[mesh_.faceOwner()[faceI]]
+                        << " neiCc:"
+                        << neiCc[faceI-mesh_.nInternalFaces()]
                         << " end:" << end[faceI]
+                        << " ownLevel:"
+                        << meshCutter_.cellLevel()[mesh_.faceOwner()[faceI]]
+                        << " faceLevel:"
+                        << meshCutter_.faceLevel(faceI)
                         << endl;
                 }
             }
@@ -678,20 +693,19 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::doRemoveCells
 
 
 // Split faces
-Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitFaces
+void Foam::meshRefinement::doSplitFaces
 (
     const labelList& splitFaces,
-    const labelPairList& splits
-)
+    const labelPairList& splits,
+    //const List<Pair<point> >& splitPoints,
+    polyTopoChange& meshMod
+) const
 {
-    polyTopoChange meshMod(mesh_);
-
     forAll(splitFaces, i)
     {
         label faceI = splitFaces[i];
         const face& f = mesh_.faces()[faceI];
 
-
         // Split as start and end index in face
         const labelPair& split = splits[i];
 
@@ -744,9 +758,9 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitFaces
         }
 
 
-Pout<< "face:" << faceI << " verts:" << f
-    << " split into f0:" << f0
-    << " f1:" << f1 << endl;
+        //Pout<< "face:" << faceI << " verts:" << f
+        //    << " split into f0:" << f0
+        //    << " f1:" << f1 << endl;
 
         // Change/add faces
         meshMod.modifyFace
@@ -773,396 +787,394 @@ Pout<< "face:" << faceI << " verts:" << f
             zoneI,                      // zone for face
             zoneFlip                    // face flip in zone
         );
+
+
+        //// Move points
+        //meshMod.modifyPoint
+        //(
+        //    f[split[0]],
+        //    splitPoints[i][0],
+        //    -1,
+        //    true
+        //);
+        //meshMod.modifyPoint
+        //(
+        //    f[split[1]],
+        //    splitPoints[i][1],
+        //    -1,
+        //    true
+        //);
     }
+}
 
 
+Foam::label Foam::meshRefinement::splitFacesUndo
+(
+    const labelList& splitFaces,
+    const labelPairList& splits,
+    const dictionary& motionDict,
 
-    // Change the mesh (no inflation)
-    autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
+    labelList& duplicateFace,
+    List<labelPair>& baffles
+)
+{
+    label nSplit = returnReduce(splitFaces.size(), sumOp<label>());
+    Info<< nl
+        << "Splitting " << nSplit
+        << " faces across diagonals" << "." << nl << endl;
+
+    // To undo: store original faces
+    faceList originalFaces(UIndirectList<face>(mesh_.faces(), splitFaces));
+    labelPairList facePairs(splitFaces.size(), labelPair(-1, -1));
 
-    // Update fields
-    mesh_.updateMesh(map);
 
-    // Move mesh (since morphing might not do this)
-    if (map().hasMotionPoints())
-    {
-        mesh_.movePoints(map().preMotionPoints());
-    }
-    else
     {
-        // Delete mesh volumes. No other way to do this?
-        mesh_.clearOut();
+        polyTopoChange meshMod(mesh_);
+        meshMod.setCapacity
+        (
+            meshMod.points().size(),
+            meshMod.faces().size()+splitFaces.size(),
+            mesh_.nCells()
+        );
+
+        // Insert the mesh changes
+        doSplitFaces(splitFaces, splits, meshMod);
+
+        // Change the mesh (no inflation)
+        autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
+
+        // Update fields
+        mesh_.updateMesh(map);
+
+        // Move mesh (since morphing might not do this)
+        if (map().hasMotionPoints())
+        {
+            mesh_.movePoints(map().preMotionPoints());
+        }
+
+        // Reset the instance for if in overwrite mode
+        mesh_.setInstance(timeName());
+        setInstance(mesh_.facesInstance());
+
+
+        // Update local mesh data
+        // ~~~~~~~~~~~~~~~~~~~~~~
+
+        forAll(originalFaces, i)
+        {
+            inplaceRenumber(map().reversePointMap(), originalFaces[i]);
+        }
+
+        {
+            Map<label> splitFaceToIndex(2*splitFaces.size());
+            forAll(splitFaces, i)
+            {
+                splitFaceToIndex.insert(splitFaces[i], i);
+            }
+
+            forAll(map().faceMap(), faceI)
+            {
+                label oldFaceI = map().faceMap()[faceI];
+                Map<label>::iterator oldFaceFnd = splitFaceToIndex.find
+                (
+                    oldFaceI
+                );
+                if (oldFaceFnd != splitFaceToIndex.end())
+                {
+                    labelPair& twoFaces = facePairs[oldFaceFnd()];
+                    if (twoFaces[0] == -1)
+                    {
+                        twoFaces[0] = faceI;
+                    }
+                    else if (twoFaces[1] == -1)
+                    {
+                        twoFaces[1] = faceI;
+                    }
+                    else
+                    {
+                        FatalErrorIn("meshRefinement::splitFacesUndo()")
+                            << "problem: twoFaces:" << twoFaces
+                            << exit(FatalError);
+                    }
+                }
+            }
+        }
+
+
+        // Update baffle data
+        // ~~~~~~~~~~~~~~~~~~
+
+        if (duplicateFace.size())
+        {
+            meshRefinement::updateList
+            (
+                map().faceMap(),
+                label(-1),
+                duplicateFace
+            );
+        }
+
+        const labelList& oldToNewFaces = map().reverseFaceMap();
+        forAll(baffles, i)
+        {
+            labelPair& baffle = baffles[i];
+            baffle.first() = oldToNewFaces[baffle.first()];
+            baffle.second() = oldToNewFaces[baffle.second()];
+
+            if (baffle.first() == -1 || baffle.second() == -1)
+            {
+                FatalErrorIn("meshRefinement::splitFacesUndo()")
+                    << "Removed baffle : faces:" << baffle
+                    << exit(FatalError);
+            }
+        }
+
+
+        // Update insersections
+        // ~~~~~~~~~~~~~~~~~~~~
+
+        {
+            DynamicList<label> changedFaces(facePairs.size());
+            forAll(facePairs, i)
+            {
+                changedFaces.append(facePairs[i].first());
+                changedFaces.append(facePairs[i].second());
+            }
+
+            // Update intersections on changed faces
+            updateMesh(map, growFaceCellFace(changedFaces));
+        }
     }
 
-    // Reset the instance for if in overwrite mode
-    mesh_.setInstance(timeName());
-    setInstance(mesh_.facesInstance());
 
-    // Update local mesh data
-    const labelList& oldToNew = map().reverseFaceMap();
-    labelList newSplitFaces(renumber(oldToNew, splitFaces));
-    // Add added faces (every splitFaces becomes two faces)
-    label sz = newSplitFaces.size();
-    newSplitFaces.setSize(2*sz);
-    forAll(map().faceMap(), faceI)
+
+    // Undo loop
+    // ~~~~~~~~~
+    // Maintains two bits of information:
+    // facePairs     : two faces originating from the same face
+    // originalFaces : original face in current vertices
+
+
+    for (label iteration = 0; iteration < 100; iteration++)
     {
-        label oldFaceI = map().faceMap()[faceI];
-        if (oldToNew[oldFaceI] != faceI)
+        Info<< nl
+            << "Undo iteration " << iteration << nl
+            << "----------------" << endl;
+
+
+        // Check mesh for errors
+        // ~~~~~~~~~~~~~~~~~~~~~
+
+        faceSet errorFaces
+        (
+            mesh_,
+            "errorFaces",
+            mesh_.nFaces()-mesh_.nInternalFaces()
+        );
+        bool hasErrors = motionSmoother::checkMesh
+        (
+            false,  // report
+            mesh_,
+            motionDict,
+            errorFaces
+        );
+        if (!hasErrors)
         {
-            // Added face
-            newSplitFaces[sz++] = faceI;
+            break;
         }
-    }
 
-    updateMesh(map, newSplitFaces);
+        // Extend faces
+        {
+            const labelList grownFaces(growFaceCellFace(errorFaces));
+            errorFaces.clear();
+            errorFaces.insert(grownFaces);
+        }
 
-    return map;
-}
 
+        // Merge face pairs
+        // ~~~~~~~~~~~~~~~~
+        // (if one of the faces is in the errorFaces set)
 
-//// Determine for multi-processor regions the lowest numbered cell on the
-//// lowest numbered processor.
-//void Foam::meshRefinement::getCoupledRegionMaster
-//(
-//    const globalIndex& globalCells,
-//    const boolList& blockedFace,
-//    const regionSplit& globalRegion,
-//    Map<label>& regionToMaster
-//) const
-//{
-//    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
-//
-//    forAll(patches, patchI)
-//    {
-//        const polyPatch& pp = patches[patchI];
-//
-//        if (isA<processorPolyPatch>(pp))
-//        {
-//            forAll(pp, i)
-//            {
-//                label faceI = pp.start()+i;
-//
-//                if (!blockedFace[faceI])
-//                {
-//                    // Only if there is a connection to the neighbour
-//                    // will there be a multi-domain region; if not through
-//                    // this face then through another.
-//
-//                    label cellI = mesh_.faceOwner()[faceI];
-//                    label globalCellI = globalCells.toGlobal(cellI);
-//
-//                    Map<label>::iterator iter =
-//                        regionToMaster.find(globalRegion[cellI]);
-//
-//                    if (iter != regionToMaster.end())
-//                    {
-//                        label master = iter();
-//                        iter() = min(master, globalCellI);
-//                    }
-//                    else
-//                    {
-//                        regionToMaster.insert
-//                        (
-//                            globalRegion[cellI],
-//                            globalCellI
-//                        );
-//                    }
-//                }
-//            }
-//        }
-//    }
-//
-//    // Do reduction
-//    Pstream::mapCombineGather(regionToMaster, minEqOp<label>());
-//    Pstream::mapCombineScatter(regionToMaster);
-//}
-//
-//
-//void Foam::meshRefinement::calcLocalRegions
-//(
-//    const globalIndex& globalCells,
-//    const labelList& globalRegion,
-//    const Map<label>& coupledRegionToMaster,
-//    const scalarField& cellWeights,
-//
-//    Map<label>& globalToLocalRegion,
-//    pointField& localPoints,
-//    scalarField& localWeights
-//) const
-//{
-//    globalToLocalRegion.resize(globalRegion.size());
-//    DynamicList<point> localCc(globalRegion.size()/2);
-//    DynamicList<scalar> localWts(globalRegion.size()/2);
-//
-//    forAll(globalRegion, cellI)
-//    {
-//        Map<label>::const_iterator fndMaster =
-//            coupledRegionToMaster.find(globalRegion[cellI]);
-//
-//        if (fndMaster != coupledRegionToMaster.end())
-//        {
-//            // Multi-processor region.
-//            if (globalCells.toGlobal(cellI) == fndMaster())
-//            {
-//                // I am master. Allocate region for me.
-//                globalToLocalRegion.insert
-//                (
-//                    globalRegion[cellI],
-//                    localCc.size()
-//                );
-//                localCc.append(mesh_.cellCentres()[cellI]);
-//                localWts.append(cellWeights[cellI]);
-//            }
-//        }
-//        else
-//        {
-//            // Single processor region.
-//            if
-//            (
-//                globalToLocalRegion.insert
-//                (
-//                    globalRegion[cellI],
-//                    localCc.size()
-//                )
-//            )
-//            {
-//                localCc.append(mesh_.cellCentres()[cellI]);
-//                localWts.append(cellWeights[cellI]);
-//            }
-//        }
-//    }
-//
-//    localPoints.transfer(localCc);
-//    localWeights.transfer(localWts);
-//
-//    if (localPoints.size() != globalToLocalRegion.size())
-//    {
-//        FatalErrorIn("calcLocalRegions(..)")
-//            << "localPoints:" << localPoints.size()
-//            << " globalToLocalRegion:" << globalToLocalRegion.size()
-//            << exit(FatalError);
-//    }
-//}
-//
-//
-//Foam::label Foam::meshRefinement::getShiftedRegion
-//(
-//    const globalIndex& indexer,
-//    const Map<label>& globalToLocalRegion,
-//    const Map<label>& coupledRegionToShifted,
-//    const label globalRegion
-//)
-//{
-//    Map<label>::const_iterator iter =
-//        globalToLocalRegion.find(globalRegion);
-//
-//    if (iter != globalToLocalRegion.end())
-//    {
-//        // Region is 'owned' locally. Convert local region index into global.
-//        return indexer.toGlobal(iter());
-//    }
-//    else
-//    {
-//        return coupledRegionToShifted[globalRegion];
-//    }
-//}
-//
-//
-//// Add if not yet present
-//void Foam::meshRefinement::addUnique(const label elem, labelList& lst)
-//{
-//    if (findIndex(lst, elem) == -1)
-//    {
-//        label sz = lst.size();
-//        lst.setSize(sz+1);
-//        lst[sz] = elem;
-//    }
-//}
-//
-//
-//void Foam::meshRefinement::calcRegionRegions
-//(
-//    const labelList& globalRegion,
-//    const Map<label>& globalToLocalRegion,
-//    const Map<label>& coupledRegionToMaster,
-//    labelListList& regionRegions
-//) const
-//{
-//    // Global region indexing since we now know the shifted regions.
-//    globalIndex shiftIndexer(globalToLocalRegion.size());
-//
-//    // Redo the coupledRegionToMaster to be in shifted region indexing.
-//    Map<label> coupledRegionToShifted(coupledRegionToMaster.size());
-//    forAllConstIter(Map<label>, coupledRegionToMaster, iter)
-//    {
-//        label region = iter.key();
-//
-//        Map<label>::const_iterator fndRegion = globalToLocalRegion.find
-//        (region);
-//
-//        if (fndRegion != globalToLocalRegion.end())
-//        {
-//            // A local cell is master of this region. Get its shifted region.
-//            coupledRegionToShifted.insert
-//            (
-//                region,
-//                shiftIndexer.toGlobal(fndRegion())
-//            );
-//        }
-//        Pstream::mapCombineGather(coupledRegionToShifted, minEqOp<label>());
-//        Pstream::mapCombineScatter(coupledRegionToShifted);
-//    }
-//
-//
-//    // Determine region-region connectivity.
-//    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-//    // This is for all my regions (so my local ones or the ones I am master
-//    // of) the neighbouring region indices.
-//
-//
-//    // Transfer lists.
-//    PtrList<HashSet<edge, Hash<edge> > > regionConnectivity
-//    (Pstream::nProcs());
-//    forAll(regionConnectivity, procI)
-//    {
-//        if (procI != Pstream::myProcNo())
-//        {
-//            regionConnectivity.set
-//            (
-//                procI,
-//                new HashSet<edge, Hash<edge> >
-//                (
-//                    coupledRegionToShifted.size()
-//                  / Pstream::nProcs()
-//                )
-//            );
-//        }
-//    }
-//
-//
-//    // Connectivity. For all my local regions the connected regions.
-//    regionRegions.setSize(globalToLocalRegion.size());
-//
-//    // Add all local connectivity to regionRegions; add all non-local
-//    // to the transferlists.
-//    for (label faceI = 0; faceI < mesh_.nInternalFaces(); faceI++)
-//    {
-//        label ownRegion = globalRegion[mesh_.faceOwner()[faceI]];
-//        label neiRegion = globalRegion[mesh_.faceNeighbour()[faceI]];
-//
-//        if (ownRegion != neiRegion)
-//        {
-//            label shiftOwnRegion = getShiftedRegion
-//            (
-//                shiftIndexer,
-//                globalToLocalRegion,
-//                coupledRegionToShifted,
-//                ownRegion
-//            );
-//            label shiftNeiRegion = getShiftedRegion
-//            (
-//                shiftIndexer,
-//                globalToLocalRegion,
-//                coupledRegionToShifted,
-//                neiRegion
-//            );
-//
-//
-//            // Connection between two regions. Send to owner of region.
-//            // - is ownRegion 'owned' by me
-//            // - is neiRegion 'owned' by me
-//
-//            if (shiftIndexer.isLocal(shiftOwnRegion))
-//            {
-//                label localI = shiftIndexer.toLocal(shiftOwnRegion);
-//                addUnique(shiftNeiRegion, regionRegions[localI]);
-//            }
-//            else
-//            {
-//                label masterProc = shiftIndexer.whichProcID(shiftOwnRegion);
-//                regionConnectivity[masterProc].insert
-//                (
-//                    edge(shiftOwnRegion, shiftNeiRegion)
-//                );
-//            }
-//
-//            if (shiftIndexer.isLocal(shiftNeiRegion))
-//            {
-//                label localI = shiftIndexer.toLocal(shiftNeiRegion);
-//                addUnique(shiftOwnRegion, regionRegions[localI]);
-//            }
-//            else
-//            {
-//                label masterProc = shiftIndexer.whichProcID(shiftNeiRegion);
-//                regionConnectivity[masterProc].insert
-//                (
-//                    edge(shiftOwnRegion, shiftNeiRegion)
-//                );
-//            }
-//        }
-//    }
-//
-//
-//    // Send
-//    forAll(regionConnectivity, procI)
-//    {
-//        if (procI != Pstream::myProcNo())
-//        {
-//            OPstream str(Pstream::blocking, procI);
-//            str << regionConnectivity[procI];
-//        }
-//    }
-//    // Receive
-//    forAll(regionConnectivity, procI)
-//    {
-//        if (procI != Pstream::myProcNo())
-//        {
-//            IPstream str(Pstream::blocking, procI);
-//            str >> regionConnectivity[procI];
-//        }
-//    }
-//
-//    // Add to addressing.
-//    forAll(regionConnectivity, procI)
-//    {
-//        if (procI != Pstream::myProcNo())
-//        {
-//            for
-//            (
-//                HashSet<edge, Hash<edge> >::const_iterator iter =
-//                    regionConnectivity[procI].begin();
-//                iter != regionConnectivity[procI].end();
-//                ++iter
-//            )
-//            {
-//                const edge& e = iter.key();
-//
-//                bool someLocal = false;
-//                if (shiftIndexer.isLocal(e[0]))
-//                {
-//                    label localI = shiftIndexer.toLocal(e[0]);
-//                    addUnique(e[1], regionRegions[localI]);
-//                    someLocal = true;
-//                }
-//                if (shiftIndexer.isLocal(e[1]))
-//                {
-//                    label localI = shiftIndexer.toLocal(e[1]);
-//                    addUnique(e[0], regionRegions[localI]);
-//                    someLocal = true;
-//                }
-//
-//                if (!someLocal)
-//                {
-//                    FatalErrorIn("calcRegionRegions(..)")
-//                        << "Received from processor " << procI
-//                        << " connection " << e
-//                        << " where none of the elements is local to me."
-//                        << abort(FatalError);
-//                }
-//            }
-//        }
-//    }
-//}
+        polyTopoChange meshMod(mesh_);
+
+        // Indices (in facePairs) of merged faces
+        labelHashSet mergedIndices(facePairs.size());
+        forAll(facePairs, index)
+        {
+            const labelPair& twoFaces = facePairs[index];
+
+            if
+            (
+                errorFaces.found(twoFaces.first())
+             || errorFaces.found(twoFaces.second())
+            )
+            {
+                const face& originalFace = originalFaces[index];
+
+
+                // Determine face properties
+                label own = mesh_.faceOwner()[twoFaces[0]];
+                label nei = -1;
+                label patchI = -1;
+                if (twoFaces[0] >= mesh_.nInternalFaces())
+                {
+                    patchI = mesh_.boundaryMesh().whichPatch(twoFaces[0]);
+                }
+                else
+                {
+                    nei = mesh_.faceNeighbour()[twoFaces[0]];
+                }
+
+                label zoneI = mesh_.faceZones().whichZone(twoFaces[0]);
+                bool zoneFlip = false;
+                if (zoneI != -1)
+                {
+                    const faceZone& fz = mesh_.faceZones()[zoneI];
+                    zoneFlip = fz.flipMap()[fz.whichFace(twoFaces[0])];
+                }
+
+                // Modify first face
+                meshMod.modifyFace
+                (
+                    originalFace,               // modified face
+                    twoFaces[0],                // label of face
+                    own,                        // owner
+                    nei,                        // neighbour
+                    false,                      // face flip
+                    patchI,                     // patch for face
+                    zoneI,                      // zone for face
+                    zoneFlip                    // face flip in zone
+                );
+                // Merge second face into first
+                meshMod.removeFace(twoFaces[1], twoFaces[0]);
+
+                mergedIndices.insert(index);
+            }
+        }
+
+        label n = returnReduce(mergedIndices.size(), sumOp<label>());
+
+        Info<< "Detected " << n
+            << " split faces that will be restored to their original faces."
+            << nl << endl;
+
+        if (n == 0)
+        {
+            // Nothing to be restored
+            break;
+        }
+
+        nSplit -= n;
+
+
+        // Change the mesh (no inflation)
+        autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
+
+        // Update fields
+        mesh_.updateMesh(map);
+
+        // Move mesh (since morphing might not do this)
+        if (map().hasMotionPoints())
+        {
+            mesh_.movePoints(map().preMotionPoints());
+        }
+
+        // Reset the instance for if in overwrite mode
+        mesh_.setInstance(timeName());
+        setInstance(mesh_.facesInstance());
+
+
+        // Update local mesh data
+        // ~~~~~~~~~~~~~~~~~~~~~~
+
+        {
+            const labelList& oldToNewFaces = map().reverseFaceMap();
+            const labelList& oldToNewPoints = map().reversePointMap();
+
+            // Compact out merged faces
+            DynamicList<label> changedFaces(mergedIndices.size());
+
+            label newIndex = 0;
+            forAll(facePairs, index)
+            {
+                const labelPair& oldSplit = facePairs[index];
+                label new0 = oldToNewFaces[oldSplit[0]];
+                label new1 = oldToNewFaces[oldSplit[1]];
+
+                if (!mergedIndices.found(index))
+                {
+                    // Faces still split
+                    if (new0 < 0 || new1 < 0)
+                    {
+                        FatalErrorIn("meshRefinement::splitFacesUndo()")
+                            << "Problem: oldFaces:" << oldSplit
+                            << " newFaces:" << labelPair(new0, new1)
+                            << exit(FatalError);
+                    }
+
+                    facePairs[newIndex] = labelPair(new0, new1);
+                    originalFaces[newIndex] = renumber
+                    (
+                        oldToNewPoints,
+                        originalFaces[index]
+                    );
+                    newIndex++;
+                }
+                else
+                {
+                    // Merged face. Only new0 kept.
+                    if (new0 < 0 || new1 == -1)
+                    {
+                        FatalErrorIn("meshRefinement::splitFacesUndo()")
+                            << "Problem: oldFaces:" << oldSplit
+                            << " newFace:" << labelPair(new0, new1)
+                            << exit(FatalError);
+                    }
+                    changedFaces.append(new0);
+                }
+            }
+
+            facePairs.setSize(newIndex);
+            originalFaces.setSize(newIndex);
+
+
+            // Update intersections
+            updateMesh(map, growFaceCellFace(changedFaces));
+        }
+
+        // Update baffle data
+        // ~~~~~~~~~~~~~~~~~~
+        {
+            if (duplicateFace.size())
+            {
+                meshRefinement::updateList
+                (
+                    map().faceMap(),
+                    label(-1),
+                    duplicateFace
+                );
+            }
+
+            const labelList& reverseFaceMap = map().reverseFaceMap();
+            forAll(baffles, i)
+            {
+                labelPair& baffle = baffles[i];
+                baffle.first() = reverseFaceMap[baffle.first()];
+                baffle.second() = reverseFaceMap[baffle.second()];
+
+                if (baffle.first() == -1 || baffle.second() == -1)
+                {
+                    FatalErrorIn("meshRefinement::splitFacesUndo()")
+                        << "Removed baffle : faces:" << baffle
+                        << exit(FatalError);
+                }
+            }
+        }
+
+    }
+
+    return nSplit;
+}
 
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
@@ -1231,150 +1243,6 @@ Foam::label Foam::meshRefinement::countHits() const
 }
 
 
-//// Determine distribution to move connected regions onto one processor.
-//Foam::labelList Foam::meshRefinement::decomposeCombineRegions
-//(
-//    const scalarField& cellWeights,
-//    const boolList& blockedFace,
-//    const List<labelPair>& explicitConnections,
-//    decompositionMethod& decomposer
-//) const
-//{
-//    // Determine global regions, separated by blockedFaces
-//    regionSplit globalRegion(mesh_, blockedFace, explicitConnections);
-//
-//    // Now globalRegion has global region per cell. Problem is that
-//    // the region might span multiple domains so we want to get
-//    // a "region master" per domain. Note that multi-processor
-//    // regions can only occur on cells on coupled patches.
-//    // Note: since the number of regions does not change by this the
-//    // process can be seen as just a shift of a region onto a single
-//    // processor.
-//
-//
-//    // Global cell numbering engine
-//    globalIndex globalCells(mesh_.nCells());
-//
-//
-//    // Determine per coupled region the master cell (lowest numbered cell
-//    // on lowest numbered processor)
-//    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-//    // (does not determine master for non-coupled=fully-local regions)
-//
-//    Map<label> coupledRegionToMaster(mesh_.nFaces()-mesh_.nInternalFaces());
-//    getCoupledRegionMaster
-//    (
-//        globalCells,
-//        blockedFace,
-//        globalRegion,
-//        coupledRegionToMaster
-//    );
-//
-//    // Determine my regions
-//    // ~~~~~~~~~~~~~~~~~~~~
-//    // These are the fully local ones or the coupled ones of which I am master
-//
-//    Map<label> globalToLocalRegion;
-//    pointField localPoints;
-//    scalarField localWeights;
-//    calcLocalRegions
-//    (
-//        globalCells,
-//        globalRegion,
-//        coupledRegionToMaster,
-//        cellWeights,
-//
-//        globalToLocalRegion,
-//        localPoints,
-//        localWeights
-//    );
-//
-//
-//
-//    // Find distribution for regions
-//    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-//
-//    labelList regionDistribution;
-//
-//    if (isA<geomDecomp>(decomposer))
-//    {
-//        regionDistribution = decomposer.decompose(localPoints, localWeights);
-//    }
-//    else
-//    {
-//        labelListList regionRegions;
-//        calcRegionRegions
-//        (
-//            globalRegion,
-//            globalToLocalRegion,
-//            coupledRegionToMaster,
-//
-//            regionRegions
-//        );
-//
-//        regionDistribution = decomposer.decompose
-//        (
-//            regionRegions,
-//            localPoints,
-//            localWeights
-//        );
-//    }
-//
-//
-//
-//    // Convert region-based decomposition back to cell-based one
-//    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-//
-//    // Transfer destination processor back to all. Use global reduce for now.
-//    Map<label> regionToDist(coupledRegionToMaster.size());
-//    forAllConstIter(Map<label>, coupledRegionToMaster, iter)
-//    {
-//        label region = iter.key();
-//
-//        Map<label>::const_iterator regionFnd = globalToLocalRegion.find
-//        (region);
-//
-//        if (regionFnd != globalToLocalRegion.end())
-//        {
-//            // Master cell is local. Store my distribution.
-//            regionToDist.insert(iter.key(), regionDistribution[regionFnd()]);
-//        }
-//        else
-//        {
-//            // Master cell is not on this processor. Make sure it is
-//            // overridden.
-//            regionToDist.insert(iter.key(), labelMax);
-//        }
-//    }
-//    Pstream::mapCombineGather(regionToDist, minEqOp<label>());
-//    Pstream::mapCombineScatter(regionToDist);
-//
-//
-//    // Determine destination for all cells
-//    labelList distribution(mesh_.nCells());
-//
-//    forAll(globalRegion, cellI)
-//    {
-//        Map<label>::const_iterator fndRegion =
-//            regionToDist.find(globalRegion[cellI]);
-//
-//        if (fndRegion != regionToDist.end())
-//        {
-//            distribution[cellI] = fndRegion();
-//        }
-//        else
-//        {
-//            // region is local to the processor.
-//            label localRegionI = globalToLocalRegion[globalRegion[cellI]];
-//
-//            distribution[cellI] = regionDistribution[localRegionI];
-//        }
-//    }
-//
-//    return distribution;
-//}
-
-
 Foam::autoPtr<Foam::mapDistributePolyMesh> Foam::meshRefinement::balance
 (
     const bool keepZoneFaces,
@@ -1432,37 +1300,29 @@ Foam::autoPtr<Foam::mapDistributePolyMesh> Foam::meshRefinement::balance
                 // keep owner&neighbour of such a surface zone on the same
                 // processor.
 
-                const PtrList<surfaceZonesInfo>& surfZones =
-                    surfaces().surfZones();
                 const faceZoneMesh& fZones = mesh_.faceZones();
                 const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
 
                 // Get faces whose owner and neighbour should stay together,
                 // i.e. they are not 'blocked'.
 
-                forAll(surfZones, surfI)
+                forAll(fZones, zoneI)
                 {
-                    const word& fzName = surfZones[surfI].faceZoneName();
+                    const faceZone& fZone = fZones[zoneI];
 
-                    if (fzName.size())
+                    forAll(fZone, i)
                     {
-                        // Get zone
-                        const faceZone& fZone = fZones[fzName];
-
-                        forAll(fZone, i)
+                        label faceI = fZone[i];
+                        if (blockedFace[faceI])
                         {
-                            label faceI = fZone[i];
-                            if (blockedFace[faceI])
+                            if
+                            (
+                                mesh_.isInternalFace(faceI)
+                             || pbm[pbm.whichPatch(faceI)].coupled()
+                            )
                             {
-                                if
-                                (
-                                    mesh_.isInternalFace(faceI)
-                                 || pbm[pbm.whichPatch(faceI)].coupled()
-                                )
-                                {
-                                    blockedFace[faceI] = false;
-                                    nUnblocked++;
-                                }
+                                blockedFace[faceI] = false;
+                                nUnblocked++;
                             }
                         }
                     }
@@ -1559,52 +1419,7 @@ Foam::autoPtr<Foam::mapDistributePolyMesh> Foam::meshRefinement::balance
                 Info<< "Found " << nCouples << " baffles to keep together."
                     << endl;
             }
-
-            //if (nUnblocked > 0 || nCouples > 0)
-            //{
-            //    Info<< "Applying special decomposition to keep baffles"
-            //        << " and zoned faces together." << endl;
-            //
-            //    distribution = decomposeCombineRegions
-            //    (
-            //        cellWeights,
-            //        blockedFace,
-            //        couples,
-            //        decomposer
-            //    );
-            //
-            //    labelList nProcCells(distributor.countCells(distribution));
-            //    Pstream::listCombineGather(nProcCells, plusEqOp<label>());
-            //    Pstream::listCombineScatter(nProcCells);
-            //
-            //    Info<< "Calculated decomposition:" << endl;
-            //    forAll(nProcCells, procI)
-            //    {
-            //        Info<< "    " << procI << '\t' << nProcCells[procI]
-            //            << endl;
-            //    }
-            //    Info<< endl;
-            //}
-            //else
-            //{
-            //    // Normal decomposition
-            //    distribution = decomposer.decompose
-            //    (
-            //        mesh_,
-            //        mesh_.cellCentres(),
-            //        cellWeights
-            //    );
-            //}
-        }
-        //else
-        //{
-        //    // Normal decomposition
-        //    distribution = decomposer.decompose
-        //    (
-        //        mesh_,
-        //        cellWeights
-        //    );
-        //}
+        }
 
 
         // Make sure blockedFace not set on couples
@@ -1639,7 +1454,36 @@ Foam::autoPtr<Foam::mapDistributePolyMesh> Foam::meshRefinement::balance
                 Pout<< "    " << procI << '\t' << nProcCells[procI] << endl;
             }
             Pout<< endl;
+
+
+            if (keepZoneFaces)
+            {
+                const faceZoneMesh& fZones = mesh_.faceZones();
+                const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
+
+                // Check that faceZone faces are indeed internal
+                forAll(fZones, zoneI)
+                {
+                    const faceZone& fZone = fZones[zoneI];
+
+                    forAll(fZone, i)
+                    {
+                        label faceI = fZone[i];
+                        label patchI = pbm.whichPatch(faceI);
+
+                        if (patchI >= 0 && pbm[patchI].coupled())
+                        {
+                            WarningIn("meshRefinement::balance(..)")
+                                << "Face at " << mesh_.faceCentres()[faceI]
+                                << " on zone " << fZone.name()
+                                << " is on coupled patch " << pbm[patchI].name()
+                                << endl;
+                        }
+                    }
+                }
+            }
         }
+
         // Do actual sending/receiving of mesh
         map = distributor.distribute(distribution);
 
@@ -2226,6 +2070,9 @@ Foam::label Foam::meshRefinement::addMeshedPatch
         // Store
         meshedPatches_.append(name);
 
+        // Clear patch based addressing
+        faceToCoupledPatch_.clear();
+
         return patchI;
     }
 }
@@ -2257,6 +2104,59 @@ Foam::labelList Foam::meshRefinement::meshedPatches() const
 }
 
 
+Foam::label Foam::meshRefinement::addFaceZone
+(
+    const word& fzName,
+    const word& masterPatch,
+    const word& slavePatch,
+    const surfaceZonesInfo::faceZoneType& fzType
+)
+{
+    label zoneI = surfaceZonesInfo::addFaceZone
+    (
+        fzName,   //name
+        labelList(0),   //addressing
+        boolList(0),    //flipmap
+        mesh_
+    );
+
+    faceZoneToMasterPatch_.insert(fzName, masterPatch);
+    faceZoneToSlavePatch_.insert(fzName, slavePatch);
+    faceZoneToType_.insert(fzName, fzType);
+
+    return zoneI;
+}
+
+
+bool Foam::meshRefinement::getFaceZoneInfo
+(
+    const word& fzName,
+    label& masterPatchID,
+    label& slavePatchID,
+    surfaceZonesInfo::faceZoneType& fzType
+) const
+{
+    const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
+
+    if (!faceZoneToMasterPatch_.found(fzName))
+    {
+        return false;
+    }
+    else
+    {
+        const word& masterName = faceZoneToMasterPatch_[fzName];
+        masterPatchID = pbm.findPatchID(masterName);
+
+        const word& slaveName = faceZoneToSlavePatch_[fzName];
+        slavePatchID = pbm.findPatchID(slaveName);
+
+        fzType = faceZoneToType_[fzName];
+
+        return true;
+    }
+}
+
+
 void Foam::meshRefinement::selectSeparatedCoupledFaces(boolList& selected) const
 {
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
@@ -2292,7 +2192,22 @@ Foam::label Foam::meshRefinement::findRegion
 )
 {
     label regionI = -1;
+
+    // Force calculation of base points (needs to be synchronised)
+    (void)mesh.tetBasePtIs();
+
     label cellI = mesh.findCell(p);
+    //if (cellI != -1)
+    //{
+    //    Pout<< "findRegion : Found point:" << p << " in cell " << cellI
+    //        << " at:" << mesh.cellCentres()[cellI] << endl;
+    //}
+    //else
+    //{
+    //    Pout<< "findRegion : Found point:" << p << " in cell " << cellI
+    //        << endl;
+    //}
+
     if (cellI != -1)
     {
         regionI = cellToRegion[cellI];
@@ -2311,13 +2226,152 @@ Foam::label Foam::meshRefinement::findRegion
     }
     return regionI;
 }
+//XXXXXXXX
+//Foam::labelList Foam::meshRefinement::findRegion
+//(
+//    const polyMesh& mesh,
+//    const labelList& cellToRegion,
+//    const vector& perturbVec,
+//    const pointField& pts
+//)
+//{
+//    labelList regions(pts.size(), -1);
+//
+//    forAll(pts, i)
+//    {
+//        label cellI = mesh.findCell(pts[i]);
+//        if (cellI != -1)
+//        {
+//            regions[i] = cellToRegion[cellI];
+//        }
+//        reduce(regions[i], maxOp<label>());
+//
+//        if (regions[i] == -1)
+//        {
+//            // See if we can perturb a bit
+//            cellI = mesh.findCell(pts[i]+perturbVec);
+//            if (cellI != -1)
+//            {
+//                regions[i] = cellToRegion[cellI];
+//            }
+//            reduce(regions[i], maxOp<label>());
+//        }
+//    }
+//
+//    forAll(regions, i)
+//    {
+//        if (regions[i] == -1)
+//        {
+//            FatalErrorIn
+//            (
+//                "meshRefinement::findRegion"
+//                "(const polyMesh&, const labelList&, const vector&"
+//                  ", const pointField&)"
+//            )   << "Point " << pts[i]
+//                << " is not inside the mesh." << nl
+//                << "Bounding box of the mesh:" << mesh.bounds()
+//                //<< "All points " << pts
+//                //<< " with corresponding regions " << regions
+//                << exit(FatalError);
+//        }
+//    }
+//
+//    return regions;
+//}
+//XXXXXXXX
+
+
+// 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
+void Foam::meshRefinement::findRegions
+(
+    const polyMesh& mesh,
+    const vector& perturbVec,
+    const pointField& locationsInMesh,
+    const pointField& locationsOutsideMesh,
+    const label nRegions,
+    labelList& cellRegion
+)
+{
+    PackedBoolList insideCell(mesh.nCells());
+
+
+    // Mark all cells reachable from locationsInMesh
+    labelList insideRegions(locationsInMesh.size());
+    forAll(insideRegions, i)
+    {
+        // Find the region containing the point
+        label regionI = findRegion
+        (
+            mesh,
+            cellRegion,
+            perturbVec,
+            locationsInMesh[i]
+        );
+
+        insideRegions[i] = regionI;
+
+        // Mark all cells in the region as being inside
+        forAll(cellRegion, cellI)
+        {
+            if (cellRegion[cellI] == regionI)
+            {
+                insideCell[cellI] = true;
+            }
+        }
+    }
+
+
+
+    // Check that all the locations outside the
+    // mesh do not conflict with those inside
+    forAll(locationsOutsideMesh, i)
+    {
+        // Find the region containing the point
+        label regionI = findRegion
+        (
+            mesh,
+            cellRegion,
+            perturbVec,
+            locationsOutsideMesh[i]
+        );
+
+        if (regionI != -1)
+        {
+            // Do a quick check for locationsOutsideMesh overlapping with
+            // inside ones.
+            label index = findIndex(insideRegions, regionI);
+            if (index != -1)
+            {
+                FatalErrorIn("meshRefinement::findRegions(..)")
+                    << "Location in mesh " << locationsInMesh[index]
+                    << " is inside same mesh region " << regionI
+                    << " as location outside mesh "
+                    << locationsOutsideMesh[i]
+                    << exit(FatalError);
+            }
+        }
+    }
+
+
+    // Now update cellRegion to -1 for unreachable cells
+    forAll(insideCell, cellI)
+    {
+        if (!insideCell[cellI])
+        {
+            cellRegion[cellI] = -1;
+        }
+    }
+}
 
 
 Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMeshRegions
 (
     const labelList& globalToMasterPatch,
     const labelList& globalToSlavePatch,
-    const point& keepPoint
+    const pointField& locationsInMesh,
+    const pointField& locationsOutsideMesh
 )
 {
     // Force calculation of face decomposition (used in findCell)
@@ -2331,25 +2385,16 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMeshRegions
 
     regionSplit cellRegion(mesh_, blockedFace);
 
-    label regionI = findRegion
+    findRegions
     (
         mesh_,
-        cellRegion,
-        mergeDistance_*vector(1,1,1), // note:1,1,1 should really be normalised
-        keepPoint
+        mergeDistance_*vector(1,1,1),   // perturbVec
+        locationsInMesh,
+        locationsOutsideMesh,
+        cellRegion.nRegions(),
+        cellRegion
     );
 
-    if (regionI == -1)
-    {
-        FatalErrorIn
-        (
-            "meshRefinement::splitMeshRegions(const point&)"
-        )   << "Point " << keepPoint
-            << " is not inside the mesh." << nl
-            << "Bounding box of the mesh:" << mesh_.bounds()
-            << exit(FatalError);
-    }
-
     // Subset
     // ~~~~~~
 
@@ -2357,62 +2402,74 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMeshRegions
     DynamicList<label> cellsToRemove(mesh_.nCells());
     forAll(cellRegion, cellI)
     {
-        if (cellRegion[cellI] != regionI)
+        if (cellRegion[cellI] == -1)
         {
             cellsToRemove.append(cellI);
         }
     }
     cellsToRemove.shrink();
 
-    label nCellsToKeep = mesh_.nCells() - cellsToRemove.size();
-    reduce(nCellsToKeep, sumOp<label>());
+    label nTotCellsToRemove = returnReduce
+    (
+        cellsToRemove.size(),
+        sumOp<label>()
+    );
+
 
-    Info<< "Keeping all cells in region " << regionI
-        << " containing point " << keepPoint << endl
-        << "Selected for keeping : "
-        << nCellsToKeep
-        << " cells." << endl;
+    autoPtr<mapPolyMesh> mapPtr;
+    if (nTotCellsToRemove > 0)
+    {
+        label nCellsToKeep =
+            mesh_.globalData().nTotalCells()
+          - nTotCellsToRemove;
 
+        Info<< "Keeping all cells containing points " << locationsInMesh << endl
+            << "Selected for keeping : "
+            << nCellsToKeep
+            << " cells." << endl;
 
-    // Remove cells
-    removeCells cellRemover(mesh_);
 
-    labelList exposedFaces(cellRemover.getExposedFaces(cellsToRemove));
-    labelList exposedPatch;
+        // Remove cells
+        removeCells cellRemover(mesh_);
 
-    label nExposedFaces = returnReduce(exposedFaces.size(), sumOp<label>());
-    if (nExposedFaces)
-    {
-        //FatalErrorIn
-        //(
-        //    "meshRefinement::splitMeshRegions(const point&)"
-        //)   << "Removing non-reachable cells should only expose"
-        //    << " boundary faces" << nl
-        //    << "ExposedFaces:" << exposedFaces << abort(FatalError);
+        labelList exposedFaces(cellRemover.getExposedFaces(cellsToRemove));
+        labelList exposedPatch;
 
-        // Patch for exposed faces for lack of anything sensible.
-        label defaultPatch = 0;
-        if (globalToMasterPatch.size())
+        label nExposedFaces = returnReduce(exposedFaces.size(), sumOp<label>());
+        if (nExposedFaces)
         {
-            defaultPatch = globalToMasterPatch[0];
+            //FatalErrorIn
+            //(
+            //    "meshRefinement::splitMeshRegions(const point&)"
+            //)   << "Removing non-reachable cells should only expose"
+            //    << " boundary faces" << nl
+            //    << "ExposedFaces:" << exposedFaces << abort(FatalError);
+
+            // Patch for exposed faces for lack of anything sensible.
+            label defaultPatch = 0;
+            if (globalToMasterPatch.size())
+            {
+                defaultPatch = globalToMasterPatch[0];
+            }
+
+            WarningIn
+            (
+                "meshRefinement::splitMeshRegions(const point&)"
+            )   << "Removing non-reachable cells exposes "
+                << nExposedFaces << " internal or coupled faces." << endl
+                << "    These get put into patch " << defaultPatch << endl;
+            exposedPatch.setSize(exposedFaces.size(), defaultPatch);
         }
 
-        WarningIn
+        mapPtr = doRemoveCells
         (
-            "meshRefinement::splitMeshRegions(const point&)"
-        )   << "Removing non-reachable cells exposes "
-            << nExposedFaces << " internal or coupled faces." << endl
-            << "    These get put into patch " << defaultPatch << endl;
-        exposedPatch.setSize(exposedFaces.size(), defaultPatch);
+            cellsToRemove,
+            exposedFaces,
+            exposedPatch,
+            cellRemover
+        );
     }
-
-    return doRemoveCells
-    (
-        cellsToRemove,
-        exposedFaces,
-        exposedPatch,
-        cellRemover
-    );
+    return mapPtr;
 }
 
 
@@ -2428,6 +2485,10 @@ void Foam::meshRefinement::distribute(const mapDistributePolyMesh& map)
     // surfaceIndex is face data.
     map.distributeFaceData(surfaceIndex_);
 
+    // faceToPatch (baffles that were on coupled faces) is not maintained
+    // (since baffling also disconnects points)
+    faceToCoupledPatch_.clear();
+
     // maintainedFaces are indices of faces.
     forAll(userFaceData_, i)
     {
@@ -2526,6 +2587,22 @@ void Foam::meshRefinement::updateMesh
     // Update surfaceIndex
     updateList(map.faceMap(), label(-1), surfaceIndex_);
 
+    // Update faceToCoupledPatch_
+    {
+        Map<label> newFaceToPatch(faceToCoupledPatch_.size());
+        forAllConstIter(Map<label>, faceToCoupledPatch_, iter)
+        {
+            label newFaceI = map.reverseFaceMap()[iter.key()];
+
+            if (newFaceI >= 0)
+            {
+                newFaceToPatch.insert(newFaceI, iter());
+            }
+        }
+        faceToCoupledPatch_.transfer(newFaceToPatch);
+    }
+
+
     // Update cached intersection information
     updateIntersections(changedFaces);
 
diff --git a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.H b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.H
index 7281a3d520d41077564ffb7a7ae2c7a8a8f40e28..31ef9f2e1a286d2bbc2cc9fa7c35a15bef20ebef 100644
--- a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.H
+++ b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinement.H
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -51,6 +51,8 @@ SourceFiles
 #include "pointFieldsFwd.H"
 #include "Tuple2.H"
 #include "pointIndexHit.H"
+#include "wordPairHashTable.H"
+#include "surfaceZonesInfo.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -66,14 +68,11 @@ class refinementFeatures;
 class shellSurfaces;
 class removeCells;
 class fvMeshDistribute;
-class searchableSurface;
-class regionSplit;
-class globalIndex;
 class removePoints;
 class localPointRegion;
-
 class snapParameters;
 
+
 /*---------------------------------------------------------------------------*\
                            Class meshRefinement Declaration
 \*---------------------------------------------------------------------------*/
@@ -183,6 +182,14 @@ private:
         //- Per cc-cc vector the index of the surface hit
         labelIOList surfaceIndex_;
 
+
+        // For baffle merging
+
+            //- Original patch for baffle faces that used to be on
+            //  coupled patches
+            Map<label> faceToCoupledPatch_;
+
+
         //- User supplied face based data.
         List<Tuple2<mapType, labelList> > userFaceData_;
 
@@ -190,6 +197,15 @@ private:
         //  order changes.
         wordList meshedPatches_;
 
+        //- FaceZone to master patch name
+        HashTable<word, word> faceZoneToMasterPatch_;
+
+        //- FaceZone to slave patch name
+        HashTable<word, word> faceZoneToSlavePatch_;
+
+        //- FaceZone to method to handle faces
+        HashTable<surfaceZonesInfo::faceZoneType, word> faceZoneToType_;
+
 
     // Private Member Functions
 
@@ -216,9 +232,6 @@ private:
             pointField& neiCc
         ) const;
 
-        //- Find any intersection of surface. Store in surfaceIndex_.
-        void updateIntersections(const labelList& changedFaces);
-
         //- Remove cells. Put exposedFaces into exposedPatchIDs.
         autoPtr<mapPolyMesh> doRemoveCells
         (
@@ -369,10 +382,25 @@ private:
                 const labelList& globalToSlavePatch
             ) const;
 
+            //- Calculate intersections. Return per face -1 or the global
+            //  surface region
+            void getIntersections
+            (
+                const labelList& surfacesToTest,
+                const pointField& neiCc,
+                const labelList& testFaces,
+
+                labelList& globalRegion1,
+                labelList& globalRegion2
+            ) const;
+
             //- Determine patches for baffles
             void getBafflePatches
             (
                 const labelList& globalToMasterPatch,
+                const pointField& locationsInMesh,
+                const wordList& regionsInMesh,
+
                 const labelList& neiLevel,
                 const pointField& neiCc,
                 labelList& ownPatch,
@@ -454,7 +482,9 @@ private:
             labelList markFacesOnProblemCellsGeometric
             (
                 const snapParameters& snapParams,
-                const dictionary& motionDict
+                const dictionary& motionDict,
+                const labelList& globalToMasterPatch,
+                const labelList& globalToSlavePatch
             ) const;
 
 
@@ -494,6 +524,16 @@ private:
                 labelList& cellToZone
             ) const;
 
+            //- Finds zone per cell for cells inside region for which name
+            //  is specified.
+            void findCellZoneInsideWalk
+            (
+                const pointField& locationsInMesh,
+                const wordList& regionsInMesh,
+                const labelList& blockedFace, // per face -1 or some index >= 0
+                labelList& cellToZone
+            ) const;
+
             //- Determines cell zone from cell region information.
             bool calcRegionToZone
             (
@@ -508,7 +548,7 @@ private:
             //  marked in namedSurfaceIndex regarded as blocked.
             void findCellZoneTopo
             (
-                const point& keepPoint,
+                const pointField& locationsInMesh,
                 const labelList& namedSurfaceIndex,
                 const labelList& surfaceToCellZone,
                 labelList& cellToZone
@@ -520,6 +560,26 @@ private:
                 labelList& namedSurfaceIndex
             ) const;
 
+            //- Put cells into cellZone, faces into faceZone
+            void zonify
+            (
+                const PackedBoolList& isMasterFace,
+                const labelList& cellToZone,
+                const labelList& neiCellZone,
+                const labelList& faceToZone,
+                const boolList& meshFlipMap,
+                polyTopoChange& meshMod
+            ) const;
+
+            //- Allocate faceZoneName
+            void allocateInterRegionFaceZone
+            (
+                const label ownZone,
+                const label neiZone,
+                wordPairHashTable& zonesToFaceZone,
+                HashTable<word, labelPair, labelPair::Hash<> >&
+            ) const;
+
             //- Remove any loose standing cells
             void handleSnapProblems
             (
@@ -667,6 +727,13 @@ public:
                 return surfaceIndex_;
             }
 
+            //- For faces originating from processor faces store the original
+            //  patch
+            const Map<label>& faceToCoupledPatch() const
+            {
+                return faceToCoupledPatch_;
+            }
+
             //- Additional face data that is maintained across
             //  topo changes. Every entry is a list over all faces.
             //  Bit of a hack. Additional flag to say whether to maintain master
@@ -829,16 +896,29 @@ public:
                 const bool useTopologicalSnapDetection,
                 const bool removeEdgeConnectedCells,
                 const scalarField& perpendicularAngle,
+                const dictionary& motionDict,
+                Time& runTime,
+                const labelList& globalToMasterPatch,
+                const labelList& globalToSlavePatch,
+                const pointField& locationsInMesh,
+                const wordList& regionsInMesh,
+                const pointField& locationsOutsideMesh
+            );
 
-                // How to handle free-standing baffles
-                const bool mergeFreeStanding,
-                const scalar freeStandingAngle,
-
+            //- Merge free-standing baffles
+            void mergeFreeStandingBaffles
+            (
+                const snapParameters& snapParams,
+                const bool useTopologicalSnapDetection,
+                const bool removeEdgeConnectedCells,
+                const scalarField& perpendicularAngle,
+                const scalar planarAngle,
                 const dictionary& motionDict,
                 Time& runTime,
                 const labelList& globalToMasterPatch,
                 const labelList& globalToSlavePatch,
-                const point& keepPoint
+                const pointField& locationsInMesh,
+                const pointField& locationsOutsideMesh
             );
 
             //- Split off (with optional buffer layers) unreachable areas
@@ -848,7 +928,10 @@ public:
                 const label nBufferLayers,
                 const labelList& globalToMasterPatch,
                 const labelList& globalToSlavePatch,
-                const point& keepPoint
+
+                const pointField& locationsInMesh,
+                const wordList& regionsInMesh,
+                const pointField& locationsOutsideMesh
             );
 
             //- Find boundary points that connect to more than one cell
@@ -859,6 +942,16 @@ public:
             //  region and split them.
             autoPtr<mapPolyMesh> dupNonManifoldPoints();
 
+            //- Find boundary points that are on faceZones of type boundary
+            //  and duplicate them
+            autoPtr<mapPolyMesh> dupNonManifoldBoundaryPoints();
+
+            //- Merge duplicate points
+            autoPtr<mapPolyMesh> mergePoints
+            (
+                const labelList& pointToDuplicate
+            );
+
             //- Create baffle for every internal face where ownPatch != -1.
             //  External faces get repatched according to ownPatch (neiPatch
             //  should be -1 for these)
@@ -868,28 +961,55 @@ public:
                 const labelList& neiPatch
             );
 
-            //- Debug helper: check faceZones are not on processor patches
-            void checkZoneFaces() const;
+            //- Get zones of given type
+            labelList getZones
+            (
+                const List<surfaceZonesInfo::faceZoneType>& fzTypes
+            ) const;
 
-            //- Create baffles for faces straddling zoned surfaces. Return
-            //  baffles.
+            //- Subset baffles according to zones
+            static List<labelPair> subsetBaffles
+            (
+                const polyMesh& mesh,
+                const labelList& zoneIDs,
+                const List<labelPair>& baffles
+            );
+
+            //- Create baffles for faces on faceZones. Return created baffles
+            //  (= pairs of faces) and corresponding faceZone
             autoPtr<mapPolyMesh> createZoneBaffles
             (
-                const labelList& globalToMasterPatch,
-                const labelList& globalToSlavePatch,
-                List<labelPair>&
+                const labelList& zoneIDs,
+                List<labelPair>& baffles,
+                labelList& originatingFaceZone
             );
 
-            //- Merge baffles. Gets pairs of faces.
-            autoPtr<mapPolyMesh> mergeBaffles(const List<labelPair>&);
+            //- Merge baffles. Gets pairs of faces and boundary faces to move
+            //  onto (coupled) patches
+            autoPtr<mapPolyMesh> mergeBaffles
+            (
+                const List<labelPair>&,
+                const Map<label>& faceToPatch
+            );
+
+            //- Merge all baffles on faceZones
+            autoPtr<mapPolyMesh> mergeZoneBaffles
+            (
+                const bool doInternalZones,
+                const bool doBaffleZones
+            );
 
             //- Put faces/cells into zones according to surface specification.
-            //  Returns null if no zone surfaces present. Region containing
-            //  the keepPoint will not be put into a cellZone.
+            //  Returns null if no zone surfaces present. Regions containing
+            //  locationsInMesh/regionsInMesh will be put in corresponding
+            //  cellZone. keepPoints is for backwards compatibility and sets
+            //  all yet unassigned cells to be non-zoned (zone = -1)
             autoPtr<mapPolyMesh> zonify
             (
-                const point& keepPoint,
-                const bool allowFreeStandingZoneFaces
+                const bool allowFreeStandingZoneFaces,
+                const pointField& locationsInMesh,
+                const wordList& regionsInMesh,
+                wordPairHashTable& zonesToFaceZone
             );
 
 
@@ -914,9 +1034,32 @@ public:
             //- Get patchIDs for patches added in addMeshedPatch.
             labelList meshedPatches() const;
 
+            //- Add/lookup faceZone and update information. Return index of
+            //  faceZone
+            label addFaceZone
+            (
+                const word& fzName,
+                const word& masterPatch,
+                const word& slavePatch,
+                const surfaceZonesInfo::faceZoneType& fzType
+            );
+
+            //- Lookup faceZone information. Return false if no information
+            //  for faceZone
+            bool getFaceZoneInfo
+            (
+                const word& fzName,
+                label& masterPatchID,
+                label& slavePatchID,
+                surfaceZonesInfo::faceZoneType& fzType
+            ) 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);
+
             //- Find region point is in. Uses optional perturbation to re-test.
             static label findRegion
             (
@@ -926,19 +1069,44 @@ public:
                 const point& p
             );
 
-            //- Split mesh. Keep part containing point.
+            static void findRegions
+            (
+                const polyMesh&,
+                const vector& perturbVec,
+                const pointField& locationsInMesh,
+                const pointField& locationsOutsideMesh,
+                const label nRegions,
+                labelList& cellRegion
+            );
+
+            //- Split mesh. Keep part containing point. Return empty map if
+            //  no cells removed.
             autoPtr<mapPolyMesh> splitMeshRegions
             (
                 const labelList& globalToMasterPatch,
                 const labelList& globalToSlavePatch,
-                const point& keepPoint
+                const pointField& locationsInMesh,
+                const pointField& locationsOutsideMesh
             );
 
             //- Split faces into two
-            autoPtr<mapPolyMesh> splitFaces
+            void doSplitFaces
             (
                 const labelList& splitFaces,
-                const labelPairList& splits
+                const labelPairList& splits,
+                polyTopoChange& meshMod
+            ) const;
+
+            //- Split faces along diagonal. Maintain mesh quality. Return
+            //  total number of faces split.
+            label splitFacesUndo
+            (
+                const labelList& splitFaces,
+                const labelPairList& splits,
+                const dictionary& motionDict,
+
+                labelList& duplicateFace,
+                List<labelPair>& baffles
             );
 
             //- Update local numbering for mesh redistribution
@@ -986,6 +1154,16 @@ public:
 
             // Merging coplanar faces and edges
 
+                //- Merge coplanar faces if sets are of size mergeSize
+                //  (usually 4)
+                label mergePatchFaces
+                (
+                    const scalar minCos,
+                    const scalar concaveCos,
+                    const label mergeSize,
+                    const labelList& patchIDs
+                );
+
                 //- Merge coplanar faces. preserveFaces is != -1 for faces
                 //  to be preserved
                 label mergePatchFacesUndo
diff --git a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementBaffles.C b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementBaffles.C
index 8cf215729d9323fc7176a4d399b75f9fac2c18b8..e63c6a353ed37bb7e9dc0658a3f9769ce3f4bcc3 100644
--- a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementBaffles.C
+++ b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementBaffles.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,7 +40,6 @@ License
 #include "polyAddPoint.H"
 #include "localPointRegion.H"
 #include "duplicatePoints.H"
-#include "OFstream.H"
 #include "regionSplit.H"
 #include "removeCells.H"
 #include "unitConversion.H"
@@ -48,6 +47,9 @@ License
 #include "patchFaceOrientation.H"
 #include "PatchEdgeFaceWave.H"
 #include "patchEdgeFaceRegion.H"
+#include "polyMeshAdder.H"
+#include "IOmanip.H"
+#include "refinementParameters.H"
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
@@ -170,52 +172,40 @@ Foam::label Foam::meshRefinement::createBaffle
 //}
 
 
-// Determine patches for baffles on all intersected unnamed faces
-void Foam::meshRefinement::getBafflePatches
+void Foam::meshRefinement::getIntersections
 (
-    const labelList& globalToMasterPatch,
-    const labelList& neiLevel,
+    const labelList& surfacesToTest,
     const pointField& neiCc,
+    const labelList& testFaces,
 
-    labelList& ownPatch,
-    labelList& neiPatch
+    labelList& globalRegion1,
+    labelList& globalRegion2
 ) const
 {
-    autoPtr<OFstream> str;
-    label vertI = 0;
+    autoPtr<OBJstream> str;
     if (debug&OBJINTERSECTIONS)
     {
         mkDir(mesh_.time().path()/timeName());
         str.reset
         (
-            new OFstream
+            new OBJstream
             (
                 mesh_.time().path()/timeName()/"intersections.obj"
             )
         );
 
-        Pout<< "getBafflePatches : Writing surface intersections to file "
+        Pout<< "getIntersections : Writing surface intersections to file "
             << str().name() << nl << endl;
     }
 
     const pointField& cellCentres = mesh_.cellCentres();
 
-    // Surfaces that need to be baffled
-    const labelList surfacesToBaffle
-    (
-        surfaceZonesInfo::getUnnamedSurfaces(surfaces_.surfZones())
-    );
-
-    ownPatch.setSize(mesh_.nFaces());
-    ownPatch = -1;
-    neiPatch.setSize(mesh_.nFaces());
-    neiPatch = -1;
-
 
-    // Collect candidate faces
-    // ~~~~~~~~~~~~~~~~~~~~~~~
+    globalRegion1.setSize(mesh_.nFaces());
+    globalRegion1 = -1;
+    globalRegion2.setSize(mesh_.nFaces());
+    globalRegion2 = -1;
 
-    labelList testFaces(intersectedFaces());
 
     // Collect segments
     // ~~~~~~~~~~~~~~~~
@@ -259,7 +249,7 @@ void Foam::meshRefinement::getBafflePatches
     labelList region2;
     surfaces_.findNearestIntersection
     (
-        surfacesToBaffle,
+        surfacesToTest,
         start,
         end,
 
@@ -272,6 +262,7 @@ void Foam::meshRefinement::getBafflePatches
         region2
     );
 
+
     forAll(testFaces, i)
     {
         label faceI = testFaces[i];
@@ -280,36 +271,172 @@ void Foam::meshRefinement::getBafflePatches
         {
             if (str.valid())
             {
-                meshTools::writeOBJ(str(), start[i]);
-                vertI++;
-                meshTools::writeOBJ(str(), hit1[i].rawPoint());
-                vertI++;
-                meshTools::writeOBJ(str(), hit2[i].rawPoint());
-                vertI++;
-                meshTools::writeOBJ(str(), end[i]);
-                vertI++;
-                str()<< "l " << vertI-3 << ' ' << vertI-2 << nl;
-                str()<< "l " << vertI-2 << ' ' << vertI-1 << nl;
-                str()<< "l " << vertI-1 << ' ' << vertI << nl;
+                str().write(linePointRef(start[i], hit1[i].rawPoint()));
+                str().write
+                (
+                    linePointRef(hit1[i].rawPoint(), hit2[i].rawPoint())
+                );
+                str().write(linePointRef(hit2[i].rawPoint(), end[i]));
             }
 
             // Pick up the patches
-            ownPatch[faceI] = globalToMasterPatch
-            [
-                surfaces_.globalRegion(surface1[i], region1[i])
-            ];
-            neiPatch[faceI] = globalToMasterPatch
-            [
-                surfaces_.globalRegion(surface2[i], region2[i])
-            ];
-
-            if (ownPatch[faceI] == -1 || neiPatch[faceI] == -1)
-            {
-                FatalErrorIn("getBafflePatches(..)")
+            globalRegion1[faceI] =
+                surfaces_.globalRegion(surface1[i], region1[i]);
+            globalRegion2[faceI] =
+                surfaces_.globalRegion(surface2[i], region2[i]);
+
+            if (globalRegion1[faceI] == -1 || globalRegion2[faceI] == -1)
+            {
+                FatalErrorIn("getIntersections(..)")
                     << "problem." << abort(FatalError);
             }
         }
     }
+}
+
+
+// Determine patches for baffles on all intersected unnamed faces
+void Foam::meshRefinement::getBafflePatches
+(
+    const labelList& globalToMasterPatch,
+    const pointField& locationsInMesh,
+    const wordList& zonesInMesh,
+
+    const labelList& neiLevel,
+    const pointField& neiCc,
+
+    labelList& ownPatch,
+    labelList& neiPatch
+) const
+{
+    // Check all unnamed surfaces
+
+    {
+        const labelList testFaces(intersectedFaces());
+
+        labelList globalRegion1;
+        labelList globalRegion2;
+        getIntersections
+        (
+            surfaceZonesInfo::getUnnamedSurfaces(surfaces_.surfZones()),
+            neiCc,
+            testFaces,
+            globalRegion1,
+            globalRegion2
+        );
+
+        ownPatch.setSize(mesh_.nFaces());
+        ownPatch = -1;
+        neiPatch.setSize(mesh_.nFaces());
+        neiPatch = -1;
+        forAll(testFaces, i)
+        {
+            label faceI = testFaces[i];
+            if (globalRegion1[faceI] != -1)
+            {
+                ownPatch[faceI] = globalToMasterPatch[globalRegion1[faceI]];
+            }
+            if (globalRegion2[faceI] != -1)
+            {
+                neiPatch[faceI] = globalToMasterPatch[globalRegion2[faceI]];
+            }
+        }
+    }
+
+
+    if (locationsInMesh.size() > 1)
+    {
+        // Now we need to filter out any possible intersections between
+        // multiple regions. Only the faces on the outside of all
+        // regions are candidates for baffling.
+        // For this we need to go to per-cell information
+
+        labelList cellToZone(mesh_.nCells(), -2);
+
+
+        // Closed surfaces with cellZone specified.
+        const labelList closedNamedSurfaces
+        (
+            surfaceZonesInfo::getClosedNamedSurfaces
+            (
+                surfaces_.surfZones(),
+                surfaces_.geometry(),
+                surfaces_.surfaces()
+            )
+        );
+
+        if (closedNamedSurfaces.size())
+        {
+            Info<< "Found " << closedNamedSurfaces.size()
+                << " closed, named surfaces. Assigning cells in/outside"
+                << " these surfaces to the corresponding cellZone."
+                << nl << endl;
+
+            findCellZoneGeometric
+            (
+                neiCc,
+                closedNamedSurfaces,    // indices of closed surfaces
+                ownPatch,               // per face patch
+                labelList(mesh_.boundaryMesh().size(), 0),// cellZone per patch
+
+                cellToZone
+            );
+        }
+
+        // Locations in mesh - walk
+        findCellZoneInsideWalk
+        (
+            locationsInMesh,
+            zonesInMesh,
+            ownPatch,   // per face -1 or some index >= 0
+            cellToZone
+        );
+
+
+        // Now we will still have some -2 in cellToZone on parts that
+        // are unreachable from the locationsInMesh or named surfaces. We only
+        // want to keep the interfaces to these cellZones.
+
+
+        labelList neiCellZone;
+        syncTools::swapBoundaryCellList(mesh_, cellToZone, neiCellZone);
+
+        // Only keep baffles where one of the sides has an unset cellToZone (-2)
+        for (label faceI=0; faceI < mesh_.nInternalFaces(); faceI++)
+        {
+            if (ownPatch[faceI] != -1 || neiPatch[faceI] != -1)
+            {
+                label ownZone = cellToZone[mesh_.faceOwner()[faceI]];
+                label neiZone = cellToZone[mesh_.faceNeighbour()[faceI]];
+
+                if (ownZone != -2 && neiZone != -2)
+                {
+                    ownPatch[faceI] = -1;
+                    neiPatch[faceI] = -1;
+                }
+            }
+        }
+        for
+        (
+            label faceI = mesh_.nInternalFaces();
+            faceI < mesh_.nFaces();
+            faceI++
+        )
+        {
+            if (ownPatch[faceI] != -1 || neiPatch[faceI] != -1)
+            {
+                label ownZone = cellToZone[mesh_.faceOwner()[faceI]];
+                label neiZone = neiCellZone[faceI-mesh_.nInternalFaces()];
+
+                if (ownZone != -2 && neiZone != -2)
+                {
+                    ownPatch[faceI] = -1;
+                    neiPatch[faceI] = -1;
+                }
+            }
+        }
+    }
+
 
     // No need to parallel sync since intersection data (surfaceIndex_ etc.)
     // already guaranteed to be synced...
@@ -449,7 +576,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::createBaffles
 
     label nBaffles = 0;
 
-    forAll(ownPatch, faceI)
+    for (label faceI = 0; faceI < mesh_.nInternalFaces(); faceI++)
     {
         if (ownPatch[faceI] != -1)
         {
@@ -465,184 +592,318 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::createBaffles
             nBaffles++;
         }
     }
+    const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
 
-    // Change the mesh (no inflation, parallel sync)
-    autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
+    forAll(pbm, patchI)
+    {
+        const polyPatch& pp = pbm[patchI];
 
-    // Update fields
-    mesh_.updateMesh(map);
+        label coupledPatchI = -1;
+        if
+        (
+            pp.coupled()
+        && !refCast<const coupledPolyPatch>(pp).separated()
+        )
+        {
+            coupledPatchI = patchI;
+        }
 
-    // Move mesh if in inflation mode
-    if (map().hasMotionPoints())
-    {
-        mesh_.movePoints(map().preMotionPoints());
+        forAll(pp, i)
+        {
+            label faceI = pp.start()+i;
+
+            if (ownPatch[faceI] != -1)
+            {
+                createBaffle
+                (
+                    faceI,
+                    ownPatch[faceI],    // owner side patch
+                    neiPatch[faceI],    // neighbour side patch
+                    meshMod
+                );
+
+                if (coupledPatchI != -1)
+                {
+                    faceToCoupledPatch_.insert(faceI, coupledPatchI);
+                }
+
+                nBaffles++;
+            }
+        }
     }
-    else
+
+
+    autoPtr<mapPolyMesh> map;
+    if (returnReduce(nBaffles, sumOp<label>()))
     {
-        // Delete mesh volumes.
-        mesh_.clearOut();
-    }
+        // Change the mesh (no inflation, parallel sync)
+        map = meshMod.changeMesh(mesh_, false, true);
 
+        // Update fields
+        mesh_.updateMesh(map);
 
-    // Reset the instance for if in overwrite mode
-    mesh_.setInstance(timeName());
+        // Move mesh if in inflation mode
+        if (map().hasMotionPoints())
+        {
+            mesh_.movePoints(map().preMotionPoints());
+        }
+        else
+        {
+            // Delete mesh volumes.
+            mesh_.clearOut();
+        }
 
-    //- Redo the intersections on the newly create baffle faces. Note that
-    //  this changes also the cell centre positions.
-    faceSet baffledFacesSet(mesh_, "baffledFacesSet", 2*nBaffles);
 
-    const labelList& reverseFaceMap = map().reverseFaceMap();
-    const labelList& faceMap = map().faceMap();
+        // Reset the instance for if in overwrite mode
+        mesh_.setInstance(timeName());
 
-    // Pick up owner side of baffle
-    forAll(ownPatch, oldFaceI)
-    {
-        label faceI = reverseFaceMap[oldFaceI];
+        //- Redo the intersections on the newly create baffle faces. Note that
+        //  this changes also the cell centre positions.
+        faceSet baffledFacesSet(mesh_, "baffledFacesSet", 2*nBaffles);
 
-        if (ownPatch[oldFaceI] != -1 && faceI >= 0)
+        const labelList& reverseFaceMap = map().reverseFaceMap();
+        const labelList& faceMap = map().faceMap();
+
+        // Pick up owner side of baffle
+        forAll(ownPatch, oldFaceI)
         {
-            const cell& ownFaces = mesh_.cells()[mesh_.faceOwner()[faceI]];
+            label faceI = reverseFaceMap[oldFaceI];
 
-            forAll(ownFaces, i)
+            if (ownPatch[oldFaceI] != -1 && faceI >= 0)
             {
-                baffledFacesSet.insert(ownFaces[i]);
+                const cell& ownFaces = mesh_.cells()[mesh_.faceOwner()[faceI]];
+
+                forAll(ownFaces, i)
+                {
+                    baffledFacesSet.insert(ownFaces[i]);
+                }
             }
         }
-    }
-    // Pick up neighbour side of baffle (added faces)
-    forAll(faceMap, faceI)
-    {
-        label oldFaceI = faceMap[faceI];
-
-        if (oldFaceI >= 0 && reverseFaceMap[oldFaceI] != faceI)
+        // Pick up neighbour side of baffle (added faces)
+        forAll(faceMap, faceI)
         {
-            const cell& ownFaces = mesh_.cells()[mesh_.faceOwner()[faceI]];
+            label oldFaceI = faceMap[faceI];
 
-            forAll(ownFaces, i)
+            if (oldFaceI >= 0 && reverseFaceMap[oldFaceI] != faceI)
             {
-                baffledFacesSet.insert(ownFaces[i]);
+                const cell& ownFaces = mesh_.cells()[mesh_.faceOwner()[faceI]];
+
+                forAll(ownFaces, i)
+                {
+                    baffledFacesSet.insert(ownFaces[i]);
+                }
             }
         }
-    }
-    baffledFacesSet.sync(mesh_);
+        baffledFacesSet.sync(mesh_);
 
-    updateMesh(map, baffledFacesSet.toc());
+        updateMesh(map, baffledFacesSet.toc());
+    }
 
     return map;
 }
 
 
-void Foam::meshRefinement::checkZoneFaces() const
+Foam::labelList Foam::meshRefinement::getZones
+(
+    const List<surfaceZonesInfo::faceZoneType>& fzTypes
+) const
 {
-    const faceZoneMesh& fZones = mesh_.faceZones();
+    const faceZoneMesh& faceZones = mesh_.faceZones();
 
-    const polyBoundaryMesh& pbm = mesh_.boundaryMesh();
+    DynamicList<label> zoneIDs(faceZones.size());
 
-    forAll(pbm, patchI)
+    forAll(faceZones, zoneI)
     {
-        const polyPatch& pp = pbm[patchI];
+        const faceZone& fZone = faceZones[zoneI];
+
+        label mpI, spI;
+        surfaceZonesInfo::faceZoneType fzType;
+        bool hasInfo = getFaceZoneInfo(fZone.name(), mpI, spI, fzType);
 
-        if (isA<processorPolyPatch>(pp))
+        if (hasInfo && findIndex(fzTypes, fzType) != -1)
         {
-            forAll(pp, i)
-            {
-                label faceI = pp.start()+i;
-                label zoneI = fZones.whichZone(faceI);
+            zoneIDs.append(zoneI);
+        }
+    }
+    return zoneIDs;
+}
 
-                if (zoneI != -1)
-                {
-                    FatalErrorIn("meshRefinement::checkZoneFaces")
-                        << "face:" << faceI << " on patch " << pp.name()
-                        << " is in zone " << fZones[zoneI].name()
-                        << exit(FatalError);
-                }
-            }
+
+// Subset those baffles where both faces are on the same zone
+Foam::List<Foam::labelPair> Foam::meshRefinement::subsetBaffles
+(
+    const polyMesh& mesh,
+    const labelList& zoneIDs,
+    const List<labelPair>& baffles
+)
+{
+    const faceZoneMesh& faceZones = mesh.faceZones();
+
+    // Mark zone per face
+    labelList faceToZone(mesh.nFaces(), -1);
+
+    forAll(zoneIDs, i)
+    {
+        label zoneID = zoneIDs[i];
+        UIndirectList<label>(faceToZone, faceZones[zoneID]) = zoneID;
+    }
+
+
+    // Subset baffles
+    DynamicList<labelPair> newBaffles(baffles.size());
+    forAll(baffles, i)
+    {
+        const labelPair& p = baffles[i];
+        if (faceToZone[p[0]] != -1 && (faceToZone[p[0]] == faceToZone[p[1]]))
+        {
+            newBaffles.append(p);
         }
     }
+
+    return newBaffles;
 }
 
 
 Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::createZoneBaffles
 (
-    const labelList& globalToMasterPatch,
-    const labelList& globalToSlavePatch,
-    List<labelPair>& baffles
+    const labelList& zoneIDs,
+    List<labelPair>& baffles,
+    labelList& originatingFaceZone
 )
 {
-    const labelList zonedSurfaces
-    (
-        surfaceZonesInfo::getNamedSurfaces(surfaces_.surfZones())
-    );
-
     autoPtr<mapPolyMesh> map;
 
-    // No need to sync; all processors will have all same zonedSurfaces.
-    if (zonedSurfaces.size())
+    if (zoneIDs.size() > 0)
     {
+        const faceZoneMesh& faceZones = mesh_.faceZones();
+
         // Split internal faces on interface surfaces
         Info<< "Converting zoned faces into baffles ..." << endl;
 
-        // Get faces (internal only) to be baffled. Map from face to patch
-        // label.
-        Map<labelPair> faceToPatch
-        (
-            getZoneBafflePatches
-            (
-                false,
-                globalToMasterPatch,
-                globalToSlavePatch
-            )
-        );
+        // Per (internal) face the patch it should go into
+        labelList ownPatch(mesh_.nFaces(), -1);
+        labelList neiPatch(mesh_.nFaces(), -1);
+        labelList faceZoneID(mesh_.nFaces(), -1);
+
+        labelList nBaffles(zoneIDs.size(), 0);
+
+        forAll(zoneIDs, j)
+        {
+            label zoneI = zoneIDs[j];
+
+            const faceZone& fz = faceZones[zoneI];
+
+            const word& masterName = faceZoneToMasterPatch_[fz.name()];
+            label masterPatchI = mesh_.boundaryMesh().findPatchID(masterName);
+            const word& slaveName = faceZoneToSlavePatch_[fz.name()];
+            label slavePatchI = mesh_.boundaryMesh().findPatchID(slaveName);
+
+            if (masterPatchI == -1 || slavePatchI == -1)
+            {
+                FatalErrorIn("meshRefinement::createZoneBaffles(..)")
+                    << "Problem: masterPatchI:" << masterPatchI
+                    << " slavePatchI:" << slavePatchI << exit(FatalError);
+            }
 
-        label nZoneFaces = returnReduce(faceToPatch.size(), sumOp<label>());
-        if (nZoneFaces > 0)
+            forAll(fz, i)
+            {
+                label faceI = fz[i];
+                if (mesh_.isInternalFace(faceI))
+                {
+                    if (fz.flipMap()[i])
+                    {
+                        ownPatch[faceI] = slavePatchI;
+                        neiPatch[faceI] = masterPatchI;
+                    }
+                    else
+                    {
+                        ownPatch[faceI] = masterPatchI;
+                        neiPatch[faceI] = slavePatchI;
+                    }
+                    faceZoneID[faceI] = zoneI;
+
+                    nBaffles[j]++;
+                }
+            }
+        }
+
+        label nLocalBaffles = sum(nBaffles);
+
+
+        label nTotalBaffles = returnReduce(nLocalBaffles, sumOp<label>());
+
+        if (nTotalBaffles > 0)
         {
-            // Convert into labelLists
-            labelList ownPatch(mesh_.nFaces(), -1);
-            labelList neiPatch(mesh_.nFaces(), -1);
-            forAllConstIter(Map<labelPair>, faceToPatch, iter)
+            Pstream::listCombineGather(nBaffles, plusEqOp<label>());
+            Pstream::listCombineScatter(nBaffles);
+
+            Info<< nl
+                << setf(ios_base::left)
+                << setw(30) << "FaceZone"
+                << setw(10) << "FaceType"
+                << setw(10) << "nBaffles"
+                << nl
+                << setw(30) << "--------"
+                << setw(10) << "--------"
+                << setw(10) << "--------"
+                << endl;
+
+            forAll(zoneIDs, j)
             {
-                ownPatch[iter.key()] = iter().first();
-                neiPatch[iter.key()] = iter().second();
+                label zoneI = zoneIDs[j];
+                const faceZone& fz = faceZones[zoneI];
+
+                label mpI, spI;
+                surfaceZonesInfo::faceZoneType fzType;
+                bool hasInfo = getFaceZoneInfo(fz.name(), mpI, spI, fzType);
+
+                if (hasInfo)
+                {
+                    Info<< setf(ios_base::left)
+                        << setw(30) << fz.name()
+                        << setw(10)
+                        << surfaceZonesInfo::faceZoneTypeNames[fzType]
+                        << setw(10) << nBaffles[j]
+                        << nl;
+                }
             }
+            Info<< endl;
 
-            // Create baffles. both sides same patch.
+            // Create baffles.
             map = createBaffles(ownPatch, neiPatch);
 
             // Get pairs of faces created.
             // Just loop over faceMap and store baffle if we encounter a slave
             // face.
 
-            baffles.setSize(faceToPatch.size());
+            baffles.setSize(nLocalBaffles);
+            originatingFaceZone.setSize(nLocalBaffles);
             label baffleI = 0;
 
             const labelList& faceMap = map().faceMap();
             const labelList& reverseFaceMap = map().reverseFaceMap();
 
-            forAll(faceMap, faceI)
+            for
+            (
+                label faceI = mesh_.nInternalFaces();
+                faceI < mesh_.nFaces();
+                faceI++
+            )
             {
                 label oldFaceI = faceMap[faceI];
-
-                // Does face originate from face-to-patch
-                Map<labelPair>::const_iterator iter = faceToPatch.find
-                (
-                    oldFaceI
-                );
-
-                if (iter != faceToPatch.end())
+                label masterFaceI = reverseFaceMap[oldFaceI];
+                if (masterFaceI != faceI && ownPatch[oldFaceI] != -1)
                 {
-                    label masterFaceI = reverseFaceMap[oldFaceI];
-                    if (faceI != masterFaceI)
-                    {
-                        baffles[baffleI++] = labelPair(masterFaceI, faceI);
-                    }
+                    baffles[baffleI] = labelPair(masterFaceI, faceI);
+                    originatingFaceZone[baffleI] = faceZoneID[oldFaceI];
+                    baffleI++;
                 }
             }
 
-            if (baffleI != faceToPatch.size())
+            if (baffleI != baffles.size())
             {
                 FatalErrorIn("meshRefinement::createZoneBaffles(..)")
-                    << "Had " << faceToPatch.size() << " patches to create "
+                    << "Had " << baffles.size() << " baffles to create "
                     << " but encountered " << baffleI
                     << " slave faces originating from patcheable faces."
                     << abort(FatalError);
@@ -661,9 +922,15 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::createZoneBaffles
                 );
             }
         }
-        Info<< "Created " << nZoneFaces << " baffles in = "
+        Info<< "Created " << nTotalBaffles << " baffles in = "
             << mesh_.time().cpuTimeIncrement() << " s\n" << nl << endl;
     }
+    else
+    {
+        baffles.clear();
+        originatingFaceZone.clear();
+    }
+
     return map;
 }
 
@@ -878,14 +1145,19 @@ Foam::List<Foam::labelPair> Foam::meshRefinement::freeStandingBaffles
         {
             const labelPair& couple = filteredCouples[i];
 
+            // Note: for a baffle-surface we do not want to merge the baffle.
+            // We could either check for hitting the same triangle (but you
+            // might hit same point on neighbouring triangles due to tolerance
+            // issues) or better just to compare the hit point.
+            // This might still go wrong for a ray in the plane of the triangle
+            // which might hit two different points on the same triangle due
+            // to tolerances...
+
             if
             (
                 hit1[i].hit()
              && hit2[i].hit()
-             && (
-                    surface1[i] != surface2[i]
-                 || hit1[i].index() != hit2[i].index()
-                )
+             && mag(hit1[i].hitPoint()-hit2[i].hitPoint()) > mergeDistance_
             )
             {
                 // Two different hits. Check angle.
@@ -930,132 +1202,219 @@ Foam::List<Foam::labelPair> Foam::meshRefinement::freeStandingBaffles
 // Merge baffles. Gets pairs of faces.
 Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::mergeBaffles
 (
-    const List<labelPair>& couples
+    const List<labelPair>& couples,
+    const Map<label>& faceToPatch
 )
 {
-    // Mesh change engine
-    polyTopoChange meshMod(mesh_);
-
-    const faceList& faces = mesh_.faces();
-    const labelList& faceOwner = mesh_.faceOwner();
-    const faceZoneMesh& faceZones = mesh_.faceZones();
+    autoPtr<mapPolyMesh> map;
 
-    forAll(couples, i)
+    if (returnReduce(couples.size()+faceToPatch.size(), sumOp<label>()))
     {
-        label face0 = couples[i].first();
-        label face1 = couples[i].second();
-
-        // face1 < 0 signals a coupled face that has been converted to baffle.
+        // Mesh change engine
+        polyTopoChange meshMod(mesh_);
 
-        label own0 = faceOwner[face0];
-        label own1 = faceOwner[face1];
+        const faceList& faces = mesh_.faces();
+        const labelList& faceOwner = mesh_.faceOwner();
+        const faceZoneMesh& faceZones = mesh_.faceZones();
 
-        if (face1 < 0 || own0 < own1)
+        forAll(couples, i)
         {
-            // Use face0 as the new internal face.
-            label zoneID = faceZones.whichZone(face0);
-            bool zoneFlip = false;
+            label face0 = couples[i].first();
+            label face1 = couples[i].second();
 
-            if (zoneID >= 0)
+            // face1 < 0 signals a coupled face that has been converted to
+            // baffle
+
+            label own0 = faceOwner[face0];
+            label own1 = faceOwner[face1];
+
+            if (face1 < 0 || own0 < own1)
             {
-                const faceZone& fZone = faceZones[zoneID];
-                zoneFlip = fZone.flipMap()[fZone.whichFace(face0)];
+                // Use face0 as the new internal face.
+                label zoneID = faceZones.whichZone(face0);
+                bool zoneFlip = false;
+
+                if (zoneID >= 0)
+                {
+                    const faceZone& fZone = faceZones[zoneID];
+                    zoneFlip = fZone.flipMap()[fZone.whichFace(face0)];
+                }
+
+                label nei = (face1 < 0 ? -1 : own1);
+
+                meshMod.setAction(polyRemoveFace(face1));
+                meshMod.setAction
+                (
+                    polyModifyFace
+                    (
+                        faces[face0],           // modified face
+                        face0,                  // label of face being modified
+                        own0,                   // owner
+                        nei,                    // neighbour
+                        false,                  // face flip
+                        -1,                     // patch for face
+                        false,                  // remove from zone
+                        zoneID,                 // zone for face
+                        zoneFlip                // face flip in zone
+                    )
+                );
             }
+            else
+            {
+                // Use face1 as the new internal face.
+                label zoneID = faceZones.whichZone(face1);
+                bool zoneFlip = false;
 
-            label nei = (face1 < 0 ? -1 : own1);
+                if (zoneID >= 0)
+                {
+                    const faceZone& fZone = faceZones[zoneID];
+                    zoneFlip = fZone.flipMap()[fZone.whichFace(face1)];
+                }
 
-            meshMod.setAction(polyRemoveFace(face1));
-            meshMod.setAction
-            (
-                polyModifyFace
+                meshMod.setAction(polyRemoveFace(face0));
+                meshMod.setAction
                 (
-                    faces[face0],           // modified face
-                    face0,                  // label of face being modified
-                    own0,                   // owner
-                    nei,                    // neighbour
-                    false,                  // face flip
-                    -1,                     // patch for face
-                    false,                  // remove from zone
-                    zoneID,                 // zone for face
-                    zoneFlip                // face flip in zone
-                )
-            );
+                    polyModifyFace
+                    (
+                        faces[face1],           // modified face
+                        face1,                  // label of face being modified
+                        own1,                   // owner
+                        own0,                   // neighbour
+                        false,                  // face flip
+                        -1,                     // patch for face
+                        false,                  // remove from zone
+                        zoneID,                 // zone for face
+                        zoneFlip                // face flip in zone
+                    )
+                );
+            }
         }
-        else
+
+        forAllConstIter(Map<label>, faceToPatch, iter)
         {
-            // Use face1 as the new internal face.
-            label zoneID = faceZones.whichZone(face1);
+            label faceI = iter.key();
+            label patchI = iter();
+
+            if (!mesh_.isInternalFace(faceI))
+            {
+                FatalErrorIn("meshRefinement::mergeBaffles(..)")
+                    << "problem: face:" << faceI
+                    << " at:" << mesh_.faceCentres()[faceI]
+                    << "(wanted patch:" << patchI
+                    << ") is an internal face" << exit(FatalError);
+            }
+
+            label zoneID = faceZones.whichZone(faceI);
             bool zoneFlip = false;
 
             if (zoneID >= 0)
             {
                 const faceZone& fZone = faceZones[zoneID];
-                zoneFlip = fZone.flipMap()[fZone.whichFace(face1)];
+                zoneFlip = fZone.flipMap()[fZone.whichFace(faceI)];
             }
 
-            meshMod.setAction(polyRemoveFace(face0));
             meshMod.setAction
             (
                 polyModifyFace
                 (
-                    faces[face1],           // modified face
-                    face1,                  // label of face being modified
-                    own1,                   // owner
-                    own0,                   // neighbour
+                    faces[faceI],           // modified face
+                    faceI,                  // label of face being modified
+                    faceOwner[faceI],       // owner
+                    -1,                     // neighbour
                     false,                  // face flip
-                    -1,                     // patch for face
+                    patchI,                 // patch for face
                     false,                  // remove from zone
                     zoneID,                 // zone for face
                     zoneFlip                // face flip in zone
                 )
             );
         }
-    }
 
-    // Change the mesh (no inflation)
-    autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
 
-    // Update fields
-    mesh_.updateMesh(map);
+        // Change the mesh (no inflation)
+        map = meshMod.changeMesh(mesh_, false, true);
 
-    // Move mesh (since morphing does not do this)
-    if (map().hasMotionPoints())
-    {
-        mesh_.movePoints(map().preMotionPoints());
-    }
-    else
-    {
-        // Delete mesh volumes.
-        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(timeName());
+
+        // Update intersections. Recalculate intersections on merged faces since
+        // this seems to give problems? Note: should not be necessary since
+        // baffles preserve intersections from when they were created.
+        labelList newExposedFaces(2*couples.size());
+        label newI = 0;
+
+        forAll(couples, i)
+        {
+            label newFace0 = map().reverseFaceMap()[couples[i].first()];
+            if (newFace0 != -1)
+            {
+                newExposedFaces[newI++] = newFace0;
+            }
+
+            label newFace1 = map().reverseFaceMap()[couples[i].second()];
+            if (newFace1 != -1)
+            {
+                newExposedFaces[newI++] = newFace1;
+            }
+        }
+        newExposedFaces.setSize(newI);
+        updateMesh(map, newExposedFaces);
     }
 
-    // Reset the instance for if in overwrite mode
-    mesh_.setInstance(timeName());
+    return map;
+}
 
-    // Update intersections. Recalculate intersections on merged faces since
-    // this seems to give problems? Note: should not be necessary since
-    // baffles preserve intersections from when they were created.
-    labelList newExposedFaces(2*couples.size());
-    label newI = 0;
 
-    forAll(couples, i)
+Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::mergeZoneBaffles
+(
+    const bool doInternalZones,
+    const bool doBaffleZones
+)
+{
+    labelList zoneIDs;
     {
-        label newFace0 = map().reverseFaceMap()[couples[i].first()];
-        if (newFace0 != -1)
+        DynamicList<surfaceZonesInfo::faceZoneType> fzTypes;
+        if (doInternalZones)
         {
-            newExposedFaces[newI++] = newFace0;
+            fzTypes.append(surfaceZonesInfo::INTERNAL);
         }
-
-        label newFace1 = map().reverseFaceMap()[couples[i].second()];
-        if (newFace1 != -1)
+        if (doBaffleZones)
         {
-            newExposedFaces[newI++] = newFace1;
+            fzTypes.append(surfaceZonesInfo::BAFFLE);
         }
+        zoneIDs = getZones(fzTypes);
     }
-    newExposedFaces.setSize(newI);
-    updateMesh(map, newExposedFaces);
 
-    return map;
+    List<labelPair> zoneBaffles
+    (
+        subsetBaffles
+        (
+            mesh_,
+            zoneIDs,
+            localPointRegion::findDuplicateFacePairs(mesh_)
+        )
+    );
+
+    autoPtr<mapPolyMesh> mapPtr;
+    if (returnReduce(zoneBaffles.size(), sumOp<label>()))
+    {
+        mapPtr = mergeBaffles(zoneBaffles, Map<label>(0));
+    }
+    return mapPtr;
 }
 
 
@@ -1216,33 +1575,29 @@ void Foam::meshRefinement::findCellZoneGeometric
 
         if (namedSurfaceIndex[faceI] == -1 && (ownZone != neiZone))
         {
-            // Give face the zone of max cell zone
-            namedSurfaceIndex[faceI] = findIndex
-            (
-                surfaceToCellZone,
-                max(ownZone, neiZone)
-            );
-        }
-    }
-
-    labelList neiCellZone(mesh_.nFaces()-mesh_.nInternalFaces());
-    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
-
-    forAll(patches, patchI)
-    {
-        const polyPatch& pp = patches[patchI];
-
-        if (pp.coupled())
-        {
-            forAll(pp, i)
+            // Give face the zone of min cell zone
+            label minZone;
+            if (ownZone == -1)
             {
-                label faceI = pp.start()+i;
-                label ownZone = cellToZone[mesh_.faceOwner()[faceI]];
-                neiCellZone[faceI-mesh_.nInternalFaces()] = ownZone;
+                minZone = neiZone;
+            }
+            else if (neiZone == -1)
+            {
+                minZone = ownZone;
+            }
+            else
+            {
+                minZone = min(ownZone, neiZone);
             }
+
+            namedSurfaceIndex[faceI] = findIndex(surfaceToCellZone, minZone);
         }
     }
-    syncTools::swapBoundaryFaceList(mesh_, neiCellZone);
+
+    labelList neiCellZone;
+    syncTools::swapBoundaryCellList(mesh_, cellToZone, neiCellZone);
+
+    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
     forAll(patches, patchI)
     {
@@ -1258,11 +1613,25 @@ void Foam::meshRefinement::findCellZoneGeometric
 
                 if (namedSurfaceIndex[faceI] == -1 && (ownZone != neiZone))
                 {
-                    // Give face the max cell zone
+                    // Give face the min cell zone
+                    label minZone;
+                    if (ownZone == -1)
+                    {
+                        minZone = neiZone;
+                    }
+                    else if (neiZone == -1)
+                    {
+                        minZone = ownZone;
+                    }
+                    else
+                    {
+                        minZone = min(ownZone, neiZone);
+                    }
+
                     namedSurfaceIndex[faceI] = findIndex
                     (
                         surfaceToCellZone,
-                        max(ownZone, neiZone)
+                        minZone
                     );
                 }
             }
@@ -1317,10 +1686,6 @@ void Foam::meshRefinement::findCellZoneInsideWalk
 
         const point& insidePoint = surfZones[surfI].zoneInsidePoint();
 
-        Info<< "For surface " << surfaces_.names()[surfI]
-            << " finding inside point " << insidePoint
-            << endl;
-
         // Find the region containing the insidePoint
         label keepRegionI = findRegion
         (
@@ -1379,6 +1744,173 @@ void Foam::meshRefinement::findCellZoneInsideWalk
 }
 
 
+void Foam::meshRefinement::findCellZoneInsideWalk
+(
+    const pointField& locationsInMesh,
+    const wordList& zonesInMesh,
+    const labelList& faceToZone, // per face -1 or some index >= 0
+
+    labelList& cellToZone
+) const
+{
+    // Analyse regions. Reuse regionsplit
+    boolList blockedFace(mesh_.nFaces());
+    //selectSeparatedCoupledFaces(blockedFace);
+
+    forAll(blockedFace, faceI)
+    {
+        if (faceToZone[faceI] == -1)
+        {
+            blockedFace[faceI] = false;
+        }
+        else
+        {
+            blockedFace[faceI] = true;
+        }
+    }
+    // No need to sync since blockedFace already is synced
+
+    // Set region per cell based on walking
+    regionSplit cellRegion(mesh_, blockedFace);
+    blockedFace.clear();
+
+    // Mark off which regions correspond to a zone
+    // (note zone is -1 for the non-zoned bit so initialise to -2)
+    labelList regionToZone(cellRegion.nRegions(), -2);
+
+
+    // Force calculation of face decomposition (used in findCell)
+    (void)mesh_.tetBasePtIs();
+
+    // For all locationsInMesh find the cell
+    forAll(locationsInMesh, i)
+    {
+        // Get location and index of zone ("none" for cellZone -1)
+        const point& insidePoint = locationsInMesh[i];
+        label zoneID = mesh_.cellZones().findZoneID(zonesInMesh[i]);
+
+        // Find the region containing the insidePoint
+        label keepRegionI = findRegion
+        (
+            mesh_,
+            cellRegion,
+            mergeDistance_*vector(1,1,1),
+            insidePoint
+        );
+
+        Info<< "For cellZone " << zonesInMesh[i]
+            << " found point " << insidePoint
+            << " in global region " << keepRegionI
+            << " out of " << cellRegion.nRegions() << " regions." << endl;
+
+        if (keepRegionI == -1)
+        {
+            FatalErrorIn
+            (
+                "meshRefinement::findCellZoneInsideWalk"
+                "(const labelList&, const labelList&"
+                ", const labelList&, const labelList&)"
+            )   << "Point " << insidePoint
+                << " is not inside the mesh." << nl
+                << "Bounding box of the mesh:" << mesh_.bounds()
+                << exit(FatalError);
+        }
+
+
+        // Mark correspondence to zone
+        regionToZone[keepRegionI] = zoneID;
+
+
+        // Set all cells with this region to the zoneID
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+        label nWarnings = 0;
+
+        forAll(cellRegion, cellI)
+        {
+            if (cellRegion[cellI] == keepRegionI)
+            {
+                if (cellToZone[cellI] == -2)
+                {
+                    // First visit of cell
+                    cellToZone[cellI] = zoneID;
+                }
+                else if (cellToZone[cellI] != zoneID)
+                {
+                    if (nWarnings < 10)
+                    {
+                        WarningIn
+                        (
+                            "meshRefinement::findCellZoneInsideWalk"
+                            "(const labelList&, const labelList&"
+                            ", const labelList&, const labelList&)"
+                        )   << "Cell " << cellI
+                            << " at " << mesh_.cellCentres()[cellI]
+                            << " is inside cellZone " << zonesInMesh[i]
+                            << " from locationInMesh " << insidePoint
+                            << " but already marked as being in zone "
+                            << mesh_.cellZones()[cellToZone[cellI]].name()
+                            << endl
+                            << "This can happen if your surfaces are not"
+                            << " (sufficiently) closed."
+                            << endl;
+                        nWarnings++;
+                    }
+                }
+            }
+        }
+    }
+
+
+    // Check if any unassigned regions
+    label nUnzoned = 0;
+    forAll(regionToZone, regionI)
+    {
+        if (regionToZone[regionI] == -2)
+        {
+            // region that has not been assigned a cellZone
+            nUnzoned++;
+        }
+    }
+
+    if (nUnzoned > 0)
+    {
+        Info<< "Detected " << nUnzoned << " regions in the mesh that do"
+            << " not have a locationInMesh." << nl
+            << "Per unzoned region displaying a single cell centre:" << endl;
+
+        // Determine single location per unzoned region
+        pointField regionToLocation
+        (
+            regionToZone.size(),
+            point(GREAT, GREAT, GREAT)
+        );
+        forAll(cellRegion, cellI)
+        {
+            label regionI = cellRegion[cellI];
+            if (regionToZone[regionI] == -2)
+            {
+                const point& cc = mesh_.cellCentres()[cellI];
+                minMagSqrEqOp<point>()(regionToLocation[regionI], cc);
+            }
+        }
+
+        Pstream::listCombineGather(regionToLocation, minMagSqrEqOp<point>());
+        Pstream::listCombineScatter(regionToLocation);
+
+        forAll(regionToZone, regionI)
+        {
+            if (regionToZone[regionI] == -2)
+            {
+                Info<< '\t' << regionI
+                    << '\t' << regionToLocation[regionI] << endl;
+            }
+        }
+        Info<< endl;
+    }
+}
+
+
 bool Foam::meshRefinement::calcRegionToZone
 (
     const label surfZoneI,
@@ -1438,12 +1970,12 @@ bool Foam::meshRefinement::calcRegionToZone
 
 
 // Finds region per cell. Assumes:
-// - region containing keepPoint does not go into a cellZone
+// - locationsInMesh go into specified cellZone or non-zone
 // - all other regions can be found by crossing faces marked in
 //   namedSurfaceIndex.
 void Foam::meshRefinement::findCellZoneTopo
 (
-    const point& keepPoint,
+    const pointField& locationsInMesh,
     const labelList& namedSurfaceIndex,
     const labelList& surfaceToCellZone,
     labelList& cellToZone
@@ -1488,34 +2020,39 @@ void Foam::meshRefinement::findCellZoneTopo
 
 
     // Find the region containing the keepPoint
-    label keepRegionI = findRegion
-    (
-        mesh_,
-        cellRegion,
-        mergeDistance_*vector(1,1,1),
-        keepPoint
-    );
-
-    Info<< "Found point " << keepPoint
-        << " in global region " << keepRegionI
-        << " out of " << cellRegion.nRegions() << " regions." << endl;
-
-    if (keepRegionI == -1)
+    forAll(locationsInMesh, i)
     {
-        FatalErrorIn
+        const point& keepPoint = locationsInMesh[i];
+        label keepRegionI = findRegion
         (
-            "meshRefinement::findCellZoneTopo"
-            "(const point&, const labelList&, const labelList&, labelList&)"
-        )   << "Point " << keepPoint
-            << " is not inside the mesh." << nl
-            << "Bounding box of the mesh:" << mesh_.bounds()
-            << exit(FatalError);
-    }
+            mesh_,
+            cellRegion,
+            mergeDistance_*vector(1,1,1),
+            keepPoint
+        );
 
-    // Mark default region with zone -1.
-    if (regionToCellZone[keepRegionI] == -2)
-    {
-        regionToCellZone[keepRegionI] = -1;
+        Info<< "Found point " << keepPoint
+            << " in global region " << keepRegionI
+            << " out of " << cellRegion.nRegions() << " regions." << endl;
+
+        if (keepRegionI == -1)
+        {
+            FatalErrorIn
+            (
+                "meshRefinement::findCellZoneTopo"
+                "(const point&, const labelList&"
+                ", const labelList&, labelList&)"
+            )   << "Point " << keepPoint
+                << " is not inside the mesh." << nl
+                << "Bounding box of the mesh:" << mesh_.bounds()
+                << exit(FatalError);
+        }
+
+        // Mark default region with zone -1.
+        if (regionToCellZone[keepRegionI] == -2)
+        {
+            regionToCellZone[keepRegionI] = -1;
+        }
     }
 
 
@@ -1564,25 +2101,11 @@ void Foam::meshRefinement::findCellZoneTopo
         const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
         // Get coupled neighbour cellRegion
-        labelList neiCellRegion(mesh_.nFaces()-mesh_.nInternalFaces());
-        forAll(patches, patchI)
-        {
-            const polyPatch& pp = patches[patchI];
-
-            if (pp.coupled())
-            {
-                forAll(pp, i)
-                {
-                    label faceI = pp.start()+i;
-                    neiCellRegion[faceI-mesh_.nInternalFaces()] =
-                        cellRegion[mesh_.faceOwner()[faceI]];
-                }
-            }
-        }
-        syncTools::swapBoundaryFaceList(mesh_, neiCellRegion);
-
-        // Calculate region to zone from cellRegions on either side of coupled
-        // face.
+        labelList neiCellRegion;
+        syncTools::swapBoundaryCellList(mesh_, cellRegion, neiCellRegion);
+
+        // Calculate region to zone from cellRegions on either side of coupled
+        // face.
         forAll(patches, patchI)
         {
             const polyPatch& pp = patches[patchI];
@@ -1657,7 +2180,7 @@ void Foam::meshRefinement::findCellZoneTopo
 void Foam::meshRefinement::makeConsistentFaceIndex
 (
     const labelList& cellToZone,
-    labelList& namedSurfaceIndex
+    labelList& faceToZone
 ) const
 {
     const labelList& faceOwner = mesh_.faceOwner();
@@ -1668,11 +2191,11 @@ void Foam::meshRefinement::makeConsistentFaceIndex
         label ownZone = cellToZone[faceOwner[faceI]];
         label neiZone = cellToZone[faceNeighbour[faceI]];
 
-        if (ownZone == neiZone && namedSurfaceIndex[faceI] != -1)
+        if (ownZone == neiZone && faceToZone[faceI] != -1)
         {
-            namedSurfaceIndex[faceI] = -1;
+            faceToZone[faceI] = -1;
         }
-        else if (ownZone != neiZone && namedSurfaceIndex[faceI] == -1)
+        else if (ownZone != neiZone && faceToZone[faceI] == -1)
         {
             FatalErrorIn("meshRefinement::zonify()")
                 << "Different cell zones on either side of face " << faceI
@@ -1685,22 +2208,8 @@ void Foam::meshRefinement::makeConsistentFaceIndex
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
     // Get coupled neighbour cellZone
-    labelList neiCellZone(mesh_.nFaces()-mesh_.nInternalFaces());
-    forAll(patches, patchI)
-    {
-        const polyPatch& pp = patches[patchI];
-
-        if (pp.coupled())
-        {
-            forAll(pp, i)
-            {
-                label faceI = pp.start()+i;
-                neiCellZone[faceI-mesh_.nInternalFaces()] =
-                    cellToZone[mesh_.faceOwner()[faceI]];
-            }
-        }
-    }
-    syncTools::swapBoundaryFaceList(mesh_, neiCellZone);
+    labelList neiCellZone;
+    syncTools::swapBoundaryCellList(mesh_, cellToZone, neiCellZone);
 
     // Use coupled cellZone to do check
     forAll(patches, patchI)
@@ -1716,11 +2225,11 @@ void Foam::meshRefinement::makeConsistentFaceIndex
                 label ownZone = cellToZone[faceOwner[faceI]];
                 label neiZone = neiCellZone[faceI-mesh_.nInternalFaces()];
 
-                if (ownZone == neiZone && namedSurfaceIndex[faceI] != -1)
+                if (ownZone == neiZone && faceToZone[faceI] != -1)
                 {
-                    namedSurfaceIndex[faceI] = -1;
+                    faceToZone[faceI] = -1;
                 }
-                else if (ownZone != neiZone && namedSurfaceIndex[faceI] == -1)
+                else if (ownZone != neiZone && faceToZone[faceI] == -1)
                 {
                     FatalErrorIn("meshRefinement::zonify()")
                         << "Different cell zones on either side of face "
@@ -1736,7 +2245,7 @@ void Foam::meshRefinement::makeConsistentFaceIndex
             forAll(pp, i)
             {
                 label faceI = pp.start()+i;
-                namedSurfaceIndex[faceI] = -1;
+                faceToZone[faceI] = -1;
             }
         }
     }
@@ -1773,7 +2282,13 @@ void Foam::meshRefinement::handleSnapProblems
     }
     else
     {
-        facePatch = markFacesOnProblemCellsGeometric(snapParams, motionDict);
+        facePatch = markFacesOnProblemCellsGeometric
+        (
+            snapParams,
+            motionDict,
+            globalToMasterPatch,
+            globalToSlavePatch
+        );
     }
     Info<< "Analyzed problem cells in = "
         << runTime.cpuTimeIncrement() << " s\n" << nl << endl;
@@ -1833,7 +2348,7 @@ void Foam::meshRefinement::handleSnapProblems
 
 Foam::labelList Foam::meshRefinement::freeStandingBaffleFaces
 (
-    const labelList& namedSurfaceIndex,
+    const labelList& faceToZone,
     const labelList& cellToZone,
     const labelList& neiCellZone
 ) const
@@ -1843,17 +2358,30 @@ Foam::labelList Foam::meshRefinement::freeStandingBaffleFaces
     const labelList& faceNeighbour = mesh_.faceNeighbour();
 
 
-    DynamicList<label> faceLabels(mesh_.nFaces()/20);
+    // We want to pick up the faces to orient. These faces come in
+    // two variants:
+    // - faces originating from stand-alone faceZones
+    //   (these will most likely have no cellZone on either side so
+    //    ownZone and neiZone both -1)
+    // - sticky-up faces originating from a 'bulge' in a outside of
+    //   a cellZone. These will have the same cellZone on either side.
+    //   How to orient these is not really clearly defined so do them
+    //   same as stand-alone faceZone faces for now. (Normally these will
+    //   already have been removed by the 'allowFreeStandingZoneFaces=false'
+    //   default setting)
+
+    // Note that argument neiCellZone will have -1 on uncoupled boundaries.
+
+    DynamicList<label> faceLabels(mesh_.nFaces()/100);
 
     for (label faceI = 0; faceI < mesh_.nInternalFaces(); faceI++)
     {
-        label surfI = namedSurfaceIndex[faceI];
-        if (surfI != -1)
+        if (faceToZone[faceI] != -1)
         {
             // Free standing baffle?
             label ownZone = cellToZone[faceOwner[faceI]];
             label neiZone = cellToZone[faceNeighbour[faceI]];
-            if (max(ownZone, neiZone) == -1)
+            if (ownZone == neiZone)
             {
                 faceLabels.append(faceI);
             }
@@ -1866,13 +2394,12 @@ Foam::labelList Foam::meshRefinement::freeStandingBaffleFaces
         forAll(pp, i)
         {
             label faceI = pp.start()+i;
-            label surfI = namedSurfaceIndex[faceI];
-            if (surfI != -1)
+            if (faceToZone[faceI] != -1)
             {
                 // Free standing baffle?
                 label ownZone = cellToZone[faceOwner[faceI]];
                 label neiZone = neiCellZone[faceI-mesh_.nInternalFaces()];
-                if (max(ownZone, neiZone) == -1)
+                if (ownZone == neiZone)
                 {
                     faceLabels.append(faceI);
                 }
@@ -2324,6 +2851,211 @@ void Foam::meshRefinement::consistentOrientation
 }
 
 
+void Foam::meshRefinement::zonify
+(
+    // Get per face whether is it master (of a coupled set of faces)
+    const PackedBoolList& isMasterFace,
+    const labelList& cellToZone,
+    const labelList& neiCellZone,
+    const labelList& faceToZone,
+    const boolList& meshFlipMap,
+    polyTopoChange& meshMod
+) const
+{
+    const labelList& faceOwner = mesh_.faceOwner();
+    const labelList& faceNeighbour = mesh_.faceNeighbour();
+
+    for (label faceI = 0; faceI < mesh_.nInternalFaces(); faceI++)
+    {
+        label faceZoneI = faceToZone[faceI];
+
+        if (faceZoneI != -1)
+        {
+            // Orient face zone to have slave cells in min cell zone.
+            // Note: logic to use flipMap should be consistent with logic
+            //       to pick up the freeStandingBaffleFaces!
+
+            label ownZone = cellToZone[faceOwner[faceI]];
+            label neiZone = cellToZone[faceNeighbour[faceI]];
+
+            bool flip;
+
+            if (ownZone == neiZone)
+            {
+                // free-standing face. Use geometrically derived orientation
+                flip = meshFlipMap[faceI];
+            }
+            else
+            {
+                flip =
+                (
+                    ownZone == -1
+                 || (neiZone != -1 && ownZone > neiZone)
+                );
+            }
+
+            meshMod.setAction
+            (
+                polyModifyFace
+                (
+                    mesh_.faces()[faceI],           // modified face
+                    faceI,                          // label of face
+                    faceOwner[faceI],               // owner
+                    faceNeighbour[faceI],           // neighbour
+                    false,                          // face flip
+                    -1,                             // patch for face
+                    false,                          // remove from zone
+                    faceZoneI,                      // zone for face
+                    flip                            // face flip in zone
+                )
+            );
+        }
+    }
+
+
+    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+
+    // Set owner as no-flip
+    forAll(patches, patchI)
+    {
+        const polyPatch& pp = patches[patchI];
+
+        label faceI = pp.start();
+
+        forAll(pp, i)
+        {
+            label faceZoneI = faceToZone[faceI];
+
+            if (faceZoneI != -1)
+            {
+                label ownZone = cellToZone[faceOwner[faceI]];
+                label neiZone = neiCellZone[faceI-mesh_.nInternalFaces()];
+
+                bool flip;
+
+                if (ownZone == neiZone)
+                {
+                    // free-standing face. Use geometrically derived orientation
+                    flip = meshFlipMap[faceI];
+                }
+                else
+                {
+                    flip =
+                    (
+                        ownZone == -1
+                     || (neiZone != -1 && ownZone > neiZone)
+                    );
+                }
+
+                meshMod.setAction
+                (
+                    polyModifyFace
+                    (
+                        mesh_.faces()[faceI],           // modified face
+                        faceI,                          // label of face
+                        faceOwner[faceI],               // owner
+                        -1,                             // neighbour
+                        false,                          // face flip
+                        patchI,                         // patch for face
+                        false,                          // remove from zone
+                        faceZoneI,                      // zone for face
+                        flip                            // face flip in zone
+                    )
+                );
+            }
+            faceI++;
+        }
+    }
+
+
+    // Put the cells into the correct zone
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    forAll(cellToZone, cellI)
+    {
+        label zoneI = cellToZone[cellI];
+
+        if (zoneI >= 0)
+        {
+            meshMod.setAction
+            (
+                polyModifyCell
+                (
+                    cellI,
+                    false,          // removeFromZone
+                    zoneI
+                )
+            );
+        }
+    }
+}
+
+
+void Foam::meshRefinement::allocateInterRegionFaceZone
+(
+    const label ownZone,
+    const label neiZone,
+    wordPairHashTable& zonesToFaceZone,
+    HashTable<word, labelPair, labelPair::Hash<> >& zoneIDsToFaceZone
+) const
+{
+    const cellZoneMesh& cellZones = mesh_.cellZones();
+
+    if (ownZone != neiZone)
+    {
+        // Make sure lowest number cellZone is master. Non-cellZone
+        // areas are slave
+        bool swap =
+        (
+            ownZone == -1
+         || (neiZone != -1 && ownZone > neiZone)
+        );
+
+        // Quick check whether we already have pair of zones
+        labelPair key(ownZone, neiZone);
+        if (swap)
+        {
+            Swap(key.first(), key.second());
+        }
+
+        HashTable<word, labelPair, labelPair::Hash<> >::
+        const_iterator zoneFnd = zoneIDsToFaceZone.find
+        (
+            key
+        );
+
+        if (zoneFnd == zoneIDsToFaceZone.end())
+        {
+            // Not found. Allocate.
+            const word ownZoneName =
+            (
+                ownZone != -1
+              ? cellZones[ownZone].name()
+              : "none"
+            );
+            const word neiZoneName =
+            (
+                neiZone != -1
+              ? cellZones[neiZone].name()
+              : "none"
+            );
+
+            // Get lowest zone first
+            Pair<word> wordKey(ownZoneName, neiZoneName);
+            if (swap)
+            {
+                Swap(wordKey.first(), wordKey.second());
+            }
+
+            word fzName = wordKey.first() + "_to_" + wordKey.second();
+
+            zoneIDsToFaceZone.insert(key, fzName);
+            zonesToFaceZone.insert(wordKey, fzName);
+        }
+    }
+}
+
+
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 // Split off unreachable areas of mesh.
@@ -2334,13 +3066,14 @@ void Foam::meshRefinement::baffleAndSplitMesh
     const bool useTopologicalSnapDetection,
     const bool removeEdgeConnectedCells,
     const scalarField& perpendicularAngle,
-    const bool mergeFreeStanding,
-    const scalar planarAngle,
     const dictionary& motionDict,
     Time& runTime,
     const labelList& globalToMasterPatch,
     const labelList& globalToSlavePatch,
-    const point& keepPoint
+
+    const pointField& locationsInMesh,
+    const wordList& zonesInMesh,
+    const pointField& locationsOutsideMesh
 )
 {
     // Introduce baffles
@@ -2362,6 +3095,10 @@ void Foam::meshRefinement::baffleAndSplitMesh
     getBafflePatches
     (
         globalToMasterPatch,
+
+        locationsInMesh,
+        zonesInMesh,
+
         neiLevel,
         neiCc,
 
@@ -2415,6 +3152,38 @@ void Foam::meshRefinement::baffleAndSplitMesh
             globalToMasterPatch,
             globalToSlavePatch
         );
+
+        // Removing additional cells might have created disconnected bits
+        // so re-do the surface intersections
+        {
+            // Swap neighbouring cell centres and cell level
+            neiLevel.setSize(mesh_.nFaces()-mesh_.nInternalFaces());
+            neiCc.setSize(mesh_.nFaces()-mesh_.nInternalFaces());
+            calcNeighbourData(neiLevel, neiCc);
+
+            labelList ownPatch, neiPatch;
+            getBafflePatches
+            (
+                globalToMasterPatch,
+
+                locationsInMesh,
+                zonesInMesh,
+
+                neiLevel,
+                neiCc,
+
+                ownPatch,
+                neiPatch
+            );
+
+            createBaffles(ownPatch, neiPatch);
+        }
+
+        if (debug)
+        {
+            // Debug:test all is still synced across proc patches
+            checkData();
+        }
     }
 
 
@@ -2431,7 +3200,13 @@ void Foam::meshRefinement::baffleAndSplitMesh
         runTime++;
     }
 
-    splitMeshRegions(globalToMasterPatch, globalToSlavePatch, keepPoint);
+    splitMeshRegions
+    (
+        globalToMasterPatch,
+        globalToSlavePatch,
+        locationsInMesh,
+        locationsOutsideMesh
+    );
 
     if (debug)
     {
@@ -2456,74 +3231,111 @@ void Foam::meshRefinement::baffleAndSplitMesh
         Pout<< "Dumped debug data in = "
             << runTime.cpuTimeIncrement() << " s\n" << nl << endl;
     }
+}
 
 
+void Foam::meshRefinement::mergeFreeStandingBaffles
+(
+    const snapParameters& snapParams,
+    const bool useTopologicalSnapDetection,
+    const bool removeEdgeConnectedCells,
+    const scalarField& perpendicularAngle,
+    const scalar planarAngle,
+    const dictionary& motionDict,
+    Time& runTime,
+    const labelList& globalToMasterPatch,
+    const labelList& globalToSlavePatch,
+    const pointField& locationsInMesh,
+    const pointField& locationsOutsideMesh
+)
+{
     // Merge baffles
     // ~~~~~~~~~~~~~
 
-    if (mergeFreeStanding)
+    Info<< nl
+        << "Merge free-standing baffles" << nl
+        << "---------------------------" << nl
+        << endl;
+
+
+    // List of pairs of freestanding baffle faces.
+    List<labelPair> couples
+    (
+        freeStandingBaffles    // filter out freestanding baffles
+        (
+            localPointRegion::findDuplicateFacePairs(mesh_),
+            planarAngle
+        )
+    );
+
+    label nCouples = couples.size();
+    reduce(nCouples, sumOp<label>());
+
+    Info<< "Detected free-standing baffles : " << nCouples << endl;
+
+    if (nCouples > 0)
     {
+        // Actually merge baffles. Note: not exactly parallellized. Should
+        // convert baffle faces into processor faces if they resulted
+        // from them.
+        mergeBaffles(couples, Map<label>(0));
+
+        // Detect any problem cells resulting from merging of baffles
+        // and delete them
+        handleSnapProblems
+        (
+            snapParams,
+            useTopologicalSnapDetection,
+            removeEdgeConnectedCells,
+            perpendicularAngle,
+            motionDict,
+            runTime,
+            globalToMasterPatch,
+            globalToSlavePatch
+        );
+
+        // Very occasionally removing a problem cell might create a disconnected
+        // region so re-check
+
         Info<< nl
-            << "Merge free-standing baffles" << nl
-            << "---------------------------" << nl
+            << "Remove unreachable sections of mesh" << nl
+            << "-----------------------------------" << nl
             << endl;
 
+        if (debug)
+        {
+            runTime++;
+        }
 
-        // List of pairs of freestanding baffle faces.
-        List<labelPair> couples
+        splitMeshRegions
         (
-            freeStandingBaffles    // filter out freestanding baffles
-            (
-                localPointRegion::findDuplicateFacePairs(mesh_),
-                planarAngle
-            )
+            globalToMasterPatch,
+            globalToSlavePatch,
+            locationsInMesh,
+            locationsOutsideMesh
         );
 
-        label nCouples = couples.size();
-        reduce(nCouples, sumOp<label>());
 
-        Info<< "Detected free-standing baffles : " << nCouples << endl;
-
-        if (nCouples > 0)
+        if (debug)
         {
-            // Actually merge baffles. Note: not exactly parallellized. Should
-            // convert baffle faces into processor faces if they resulted
-            // from them.
-            mergeBaffles(couples);
-
-            // Detect any problem cells resulting from merging of baffles
-            // and delete them
-            handleSnapProblems
-            (
-                snapParams,
-                useTopologicalSnapDetection,
-                removeEdgeConnectedCells,
-                perpendicularAngle,
-                motionDict,
-                runTime,
-                globalToMasterPatch,
-                globalToSlavePatch
-            );
-
-            if (debug)
-            {
-                // Debug:test all is still synced across proc patches
-                checkData();
-            }
+            // Debug:test all is still synced across proc patches
+            checkData();
         }
-        Info<< "Merged free-standing baffles in = "
-            << runTime.cpuTimeIncrement() << " s\n" << nl << endl;
     }
+    Info<< "Merged free-standing baffles in = "
+        << runTime.cpuTimeIncrement() << " s\n" << nl << endl;
 }
 
 
-// Split off (with optional buffer layers) unreachable areas of mesh.
 Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
 (
     const label nBufferLayers,
     const labelList& globalToMasterPatch,
     const labelList& globalToSlavePatch,
-    const point& keepPoint
+
+    const pointField& locationsInMesh,
+    const wordList& zonesInMesh,
+    const pointField& locationsOutsideMesh
 )
 {
     // Determine patches to put intersections into
@@ -2534,10 +3346,15 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
     pointField neiCc(mesh_.nFaces()-mesh_.nInternalFaces());
     calcNeighbourData(neiLevel, neiCc);
 
+    // Find intersections with all unnamed(!) surfaces
     labelList ownPatch, neiPatch;
     getBafflePatches
     (
         globalToMasterPatch,
+
+        locationsInMesh,
+        zonesInMesh,
+
         neiLevel,
         neiCc,
 
@@ -2557,39 +3374,25 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
     }
     syncTools::syncFaceList(mesh_, blockedFace, orEqOp<bool>());
 
-    // Set region per cell based on walking
+
     regionSplit cellRegion(mesh_, blockedFace);
     blockedFace.clear();
 
-    // Find the region containing the keepPoint
-    const label keepRegionI = findRegion
+    // Set unreachable cells to -1
+    findRegions
     (
         mesh_,
-        cellRegion,
-        mergeDistance_*vector(1,1,1),
-        keepPoint
+        mergeDistance_*vector(1,1,1),   // perturbVec
+        locationsInMesh,
+        locationsOutsideMesh,
+        cellRegion.nRegions(),
+        cellRegion
     );
 
-    Info<< "Found point " << keepPoint
-        << " in global region " << keepRegionI
-        << " out of " << cellRegion.nRegions() << " regions." << endl;
 
-    if (keepRegionI == -1)
-    {
-        FatalErrorIn
-        (
-            "meshRefinement::splitMesh"
-            "(const label, const labelList&, const point&)"
-        )   << "Point " << keepPoint
-            << " is not inside the mesh." << nl
-            << "Bounding box of the mesh:" << mesh_.bounds()
-            << exit(FatalError);
-    }
-
-
-    // Walk out nBufferlayers from region split
+    // Walk out nBufferlayers from region boundary
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // (modifies cellRegion, ownPatch)
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // Takes over face patch onto points and then back to faces and cells
     // (so cell-face-point walk)
 
@@ -2616,7 +3419,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
             label ownRegion = cellRegion[faceOwner[faceI]];
             label neiRegion = cellRegion[faceNeighbour[faceI]];
 
-            if (ownRegion == keepRegionI && neiRegion != keepRegionI)
+            if (ownRegion == -1 && neiRegion != -1)
             {
                 // Note max(..) since possibly regionSplit might have split
                 // off extra unreachable parts of mesh. Note: or can this only
@@ -2626,7 +3429,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
                     pointBaffle[f[fp]] = max(defaultPatch, ownPatch[faceI]);
                 }
             }
-            else if (ownRegion != keepRegionI && neiRegion == keepRegionI)
+            else if (ownRegion != -1 && neiRegion == -1)
             {
                 label newPatchI = neiPatch[faceI];
                 if (newPatchI == -1)
@@ -2650,7 +3453,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
 
             label ownRegion = cellRegion[faceOwner[faceI]];
 
-            if (ownRegion == keepRegionI)
+            if (ownRegion == -1)
             {
                 forAll(f, fp)
                 {
@@ -2703,9 +3506,9 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
             {
                 label own = faceOwner[faceI];
 
-                if (cellRegion[own] != keepRegionI)
+                if (cellRegion[own] == -1)
                 {
-                    cellRegion[own] = keepRegionI;
+                    cellRegion[own] = labelMax;
 
                     const cell& ownFaces = mesh_.cells()[own];
                     forAll(ownFaces, j)
@@ -2720,9 +3523,9 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
                 {
                     label nei = faceNeighbour[faceI];
 
-                    if (cellRegion[nei] != keepRegionI)
+                    if (cellRegion[nei] == -1)
                     {
-                        cellRegion[nei] = keepRegionI;
+                        cellRegion[nei] = labelMax;
 
                         const cell& neiFaces = mesh_.cells()[nei];
                         forAll(neiFaces, j)
@@ -2751,7 +3554,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
     DynamicList<label> cellsToRemove(mesh_.nCells());
     forAll(cellRegion, cellI)
     {
-        if (cellRegion[cellI] != keepRegionI)
+        if (cellRegion[cellI] == -1)
         {
             cellsToRemove.append(cellI);
         }
@@ -2761,10 +3564,8 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
     label nCellsToKeep = mesh_.nCells() - cellsToRemove.size();
     reduce(nCellsToKeep, sumOp<label>());
 
-    Info<< "Keeping all cells in region " << keepRegionI
-        << " containing point " << keepPoint << endl
-        << "Selected for keeping : " << nCellsToKeep
-        << " cells." << endl;
+    Info<< "Keeping all cells containing points " << locationsInMesh << endl
+        << "Selected for keeping : " << nCellsToKeep << " cells." << endl;
 
 
     // Remove cells
@@ -2825,35 +3626,41 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::dupNonManifoldPoints
         << mesh_.globalData().nTotalPoints()
         << ')' << endl;
 
-    // Topo change engine
-    duplicatePoints pointDuplicator(mesh_);
 
-    // Insert changes into meshMod
-    pointDuplicator.setRefinement(regionSide, meshMod);
+    autoPtr<mapPolyMesh> map;
 
-    // Change the mesh (no inflation, parallel sync)
-    autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
+    if (nNonManifPoints)
+    {
+        // Topo change engine
+        duplicatePoints pointDuplicator(mesh_);
 
-    // Update fields
-    mesh_.updateMesh(map);
+        // Insert changes into meshMod
+        pointDuplicator.setRefinement(regionSide, meshMod);
 
-    // Move mesh if in inflation mode
-    if (map().hasMotionPoints())
-    {
-        mesh_.movePoints(map().preMotionPoints());
-    }
-    else
-    {
-        // Delete mesh volumes.
-        mesh_.clearOut();
-    }
+        // Change the mesh (no inflation, parallel sync)
+        map = meshMod.changeMesh(mesh_, false, true);
 
-    // Reset the instance for if in overwrite mode
-    mesh_.setInstance(timeName());
+        // Update fields
+        mesh_.updateMesh(map);
+
+        // Move mesh if in inflation mode
+        if (map().hasMotionPoints())
+        {
+            mesh_.movePoints(map().preMotionPoints());
+        }
+        else
+        {
+            // Delete mesh volumes.
+            mesh_.clearOut();
+        }
+
+        // Reset the instance for if in overwrite mode
+        mesh_.setInstance(timeName());
 
-    // Update intersections. Is mapping only (no faces created, positions stay
-    // same) so no need to recalculate intersections.
-    updateMesh(map, labelList(0));
+        // Update intersections. Is mapping only (no faces created, positions
+        // stay same) so no need to recalculate intersections.
+        updateMesh(map, labelList(0));
+    }
 
     return map;
 }
@@ -2870,45 +3677,181 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::dupNonManifoldPoints()
 }
 
 
-// Zoning
-Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
+Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::mergePoints
 (
-    const point& keepPoint,
-    const bool allowFreeStandingZoneFaces
+    const labelList& pointToDuplicate
 )
 {
-    const PtrList<surfaceZonesInfo>& surfZones = surfaces_.surfZones();
+    label nPointPairs = 0;
+    forAll(pointToDuplicate, pointI)
+    {
+        label otherPointI = pointToDuplicate[pointI];
+        if (otherPointI != -1)
+        {
+            nPointPairs++;
+        }
+    }
 
-    labelList namedSurfaces(surfaceZonesInfo::getNamedSurfaces(surfZones));
+    autoPtr<mapPolyMesh> map;
 
-    forAll(namedSurfaces, i)
+    if (returnReduce(nPointPairs, sumOp<label>()))
     {
-        label surfI = namedSurfaces[i];
+        Map<label> pointToMaster(2*nPointPairs);
+        forAll(pointToDuplicate, pointI)
+        {
+            label otherPointI = pointToDuplicate[pointI];
+            if (otherPointI != -1)
+            {
+                // Slave point
+                pointToMaster.insert(pointI, otherPointI);
+            }
+        }
+
+        // Topochange container
+        polyTopoChange meshMod(mesh_);
+
+        // Insert changes
+        polyMeshAdder::mergePoints(mesh_, pointToMaster, meshMod);
 
-        Info<< "Surface : " << surfaces_.names()[surfI] << nl
-            << "    faceZone : " << surfZones[surfI].faceZoneName() << nl
-            << "    cellZone : " << surfZones[surfI].cellZoneName() << endl;
+        // Change the mesh (no inflation, parallel sync)
+        map = meshMod.changeMesh(mesh_, false, true);
+
+        // Update fields
+        mesh_.updateMesh(map);
+
+        // Move mesh if in inflation mode
+        if (map().hasMotionPoints())
+        {
+            mesh_.movePoints(map().preMotionPoints());
+        }
+        else
+        {
+            // Delete mesh volumes.
+            mesh_.clearOut();
+        }
+
+        // Reset the instance for if in overwrite mode
+        mesh_.setInstance(timeName());
+
+        // Update intersections. Is mapping only (no faces created, positions
+        // stay same) so no need to recalculate intersections.
+        updateMesh(map, labelList(0));
     }
 
+    return map;
+}
 
-    // Add zones to mesh
-    labelList surfaceToFaceZone =
-        surfaceZonesInfo::addFaceZonesToMesh
-        (
-            surfZones,
-            namedSurfaces,
-            mesh_
-        );
 
-    labelList surfaceToCellZone =
-        surfaceZonesInfo::addCellZonesToMesh
+// Duplicate points on 'boundary' zones. Do not duplicate points on
+// 'internal' or 'baffle' zone. Whether points are on normal patches does
+// not matter
+Foam::autoPtr<Foam::mapPolyMesh>
+Foam::meshRefinement::dupNonManifoldBoundaryPoints()
+{
+    const labelList boundaryFaceZones
+    (
+        getZones
         (
-            surfZones,
-            namedSurfaces,
-            mesh_
-        );
+            List<surfaceZonesInfo::faceZoneType>
+            (
+                1,
+                surfaceZonesInfo::BOUNDARY
+            )
+        )
+    );
+    labelList internalOrBaffleFaceZones;
+    {
+        List<surfaceZonesInfo::faceZoneType> fzTypes(2);
+        fzTypes[0] = surfaceZonesInfo::INTERNAL;
+        fzTypes[1] = surfaceZonesInfo::BAFFLE;
+        internalOrBaffleFaceZones = getZones(fzTypes);
+    }
+
+
 
+    // 0 : point used by normal, unzoned boundary faces
+    // 1 : point used by 'boundary' zone
+    // 2 : point used by internal/baffle zone
+    PackedList<2> pointStatus(mesh_.nPoints(), 0u);
 
+    forAll(boundaryFaceZones, j)
+    {
+        const faceZone& fZone = mesh_.faceZones()[boundaryFaceZones[j]];
+        forAll(fZone, i)
+        {
+            const face& f = mesh_.faces()[fZone[i]];
+            forAll(f, fp)
+            {
+                pointStatus[f[fp]] = max(pointStatus[f[fp]], 1u);
+            }
+        }
+    }
+    forAll(internalOrBaffleFaceZones, j)
+    {
+        const faceZone& fZone = mesh_.faceZones()[internalOrBaffleFaceZones[j]];
+        forAll(fZone, i)
+        {
+            const face& f = mesh_.faces()[fZone[i]];
+            forAll(f, fp)
+            {
+                pointStatus[f[fp]] = max(pointStatus[f[fp]], 2u);
+            }
+        }
+    }
+
+    syncTools::syncPointList
+    (
+        mesh_,
+        pointStatus,
+        maxEqOp<unsigned int>(),    // combine op
+        0u                          // null value
+    );
+
+    // Pick up points on boundary zones that are not on internal/baffle zones
+    label n = 0;
+    forAll(pointStatus, pointI)
+    {
+        if (pointStatus[pointI] == 1u)
+        {
+            n++;
+        }
+    }
+
+    label globalNPoints = returnReduce(n, sumOp<label>());
+    Info<< "Duplicating " << globalNPoints << " points on"
+        << " faceZones of type "
+        << surfaceZonesInfo::faceZoneTypeNames[surfaceZonesInfo::BOUNDARY]
+        << endl;
+
+    autoPtr<mapPolyMesh> map;
+
+    if (globalNPoints)
+    {
+        labelList candidatePoints(n);
+        n = 0;
+        forAll(pointStatus, pointI)
+        {
+            if (pointStatus[pointI] == 1u)
+            {
+                candidatePoints[n++] = pointI;
+            }
+        }
+        localPointRegion regionSide(mesh_, candidatePoints);
+        map = dupNonManifoldPoints(regionSide);
+    }
+    return map;
+}
+
+
+// Zoning
+Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
+(
+    const bool allowFreeStandingZoneFaces,
+    const pointField& locationsInMesh,
+    const wordList& zonesInMesh,
+    wordPairHashTable& zonesToFaceZone
+)
+{
     const pointField& cellCentres = mesh_.cellCentres();
     const labelList& faceOwner = mesh_.faceOwner();
     const labelList& faceNeighbour = mesh_.faceNeighbour();
@@ -2921,279 +3864,570 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
     calcNeighbourData(neiLevel, neiCc);
 
 
-    // Mark faces intersecting zoned surfaces
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+    // Put the cells into the correct zone
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Zone per cell:
+    // -2 : unset
+    // -1 : not in any zone (zone 'none')
+    // >=0: zoneID
+    labelList cellToZone(mesh_.nCells(), -2);
 
-    // Like surfaceIndex_ but only for named surfaces.
-    labelList namedSurfaceIndex(mesh_.nFaces(), -1);
-    PackedBoolList posOrientation(mesh_.nFaces());
 
+    // Add from locationsInMesh
+    // ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    // Filter out keepPoints
+    labelList zonedIndices(refinementParameters::zonedLocations(zonesInMesh));
+
+    if (zonedIndices.size())
     {
-        // Statistics: number of faces per faceZone
-        labelList nSurfFaces(surfZones.size(), 0);
+        // Explicitly provided locations and their cellZone. Determine all
+        // blocked faces and do walking. Updates cellToZone.
 
-        // Note: for all internal faces? internal + coupled?
-        // Since zonify is run after baffling the surfaceIndex_ on baffles is
-        // not synchronised across both baffle faces. Fortunately we don't
-        // do zonify baffle faces anyway (they are normal boundary faces).
+        Info<< "Setting cellZones according to locationsInMesh:" << endl;
+        //forAll(locationsInMesh, regionI)
+        forAll(zonedIndices, j)
+        {
+            label regionI = zonedIndices[j];
+            Info<< "Location : " << locationsInMesh[regionI] << nl
+                << "    cellZone : " << zonesInMesh[regionI] << endl;
+        }
+        Info<< endl;
 
-        // Collect candidate faces
-        // ~~~~~~~~~~~~~~~~~~~~~~~
+        // Test all (unnamed & named) surfaces
+        labelList globalRegion1;
+        labelList globalRegion2;
+        getIntersections
+        (
+            identity(surfaces_.surfaces().size()),  // surfacesToTest,
+            neiCc,
+            intersectedFaces(),     // testFaces
+            globalRegion1,
+            globalRegion2
+        );
 
-        labelList testFaces(intersectedFaces());
+        // Assign cellZone according to seed points
+        findCellZoneInsideWalk
+        (
+            pointField(locationsInMesh, zonedIndices),       // locations
+            UIndirectList<word>(zonesInMesh, zonedIndices)(),// name of region
+            globalRegion1,          // per face -1 (unblocked) or >= 0 (blocked)
+            cellToZone
+        );
+    }
 
-        // Collect segments
-        // ~~~~~~~~~~~~~~~~
 
-        pointField start(testFaces.size());
-        pointField end(testFaces.size());
 
-        forAll(testFaces, i)
-        {
-            label faceI = testFaces[i];
+    // Mark faces intersecting zoned surfaces
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-            if (mesh_.isInternalFace(faceI))
-            {
-                start[i] = cellCentres[faceOwner[faceI]];
-                end[i] = cellCentres[faceNeighbour[faceI]];
-            }
-            else
-            {
-                start[i] = cellCentres[faceOwner[faceI]];
-                end[i] = neiCc[faceI-mesh_.nInternalFaces()];
-            }
-        }
+    const PtrList<surfaceZonesInfo>& surfZones = surfaces_.surfZones();
 
-        // Extend segments a bit
+    labelList namedSurfaces(surfaceZonesInfo::getNamedSurfaces(surfZones));
+
+
+    //- Face face orientation of zone (only from those intersecting named
+    //  surfaces)
+    PackedBoolList posOrientation(mesh_.nFaces());
+    //- Per face index of faceZone or -1
+    labelList faceToZone(mesh_.nFaces(), -1);
+
+    if (namedSurfaces.size())
+    {
+        Info<< "Setting cellZones according to named surfaces:" << endl;
+        forAll(namedSurfaces, i)
         {
-            const vectorField smallVec(Foam::sqrt(SMALL)*(end-start));
-            start -= smallVec;
-            end += smallVec;
+            label surfI = namedSurfaces[i];
+
+            Info<< "Surface : " << surfaces_.names()[surfI] << nl
+                << "    faceZone : " << surfZones[surfI].faceZoneName() << nl
+                << "    cellZone : " << surfZones[surfI].cellZoneName() << endl;
         }
+        Info<< endl;
 
 
-        // Do test for intersections
-        // ~~~~~~~~~~~~~~~~~~~~~~~~~
-        // Note that we intersect all intersected faces again. Could reuse
-        // the information already in surfaceIndex_.
+        // Add zones to mesh
+        labelList surfaceToFaceZone =
+            surfaceZonesInfo::addFaceZonesToMesh
+            (
+                surfZones,
+                namedSurfaces,
+                mesh_
+            );
 
-        labelList surface1;
-        List<pointIndexHit> hit1;
-        vectorField normal1;
-        labelList surface2;
-        List<pointIndexHit> hit2;
-        vectorField normal2;
-        {
-            labelList region1;
-            labelList region2;
-            surfaces_.findNearestIntersection
+        labelList surfaceToCellZone =
+            surfaceZonesInfo::addCellZonesToMesh
             (
+                surfZones,
                 namedSurfaces,
-                start,
-                end,
-
-                surface1,
-                hit1,
-                region1,
-                normal1,
-
-                surface2,
-                hit2,
-                region2,
-                normal2
+                mesh_
             );
-        }
 
-        forAll(testFaces, i)
+
+
+
+        // Like surfaceIndex_ but only for named surfaces.
+        labelList namedSurfaceIndex(mesh_.nFaces(), -1);
+
         {
-            label faceI = testFaces[i];
-            const vector& area = mesh_.faceAreas()[faceI];
+            // Statistics: number of faces per faceZone
+            labelList nSurfFaces(surfZones.size(), 0);
 
-            if (surface1[i] != -1)
+            // Note: for all internal faces? internal + coupled?
+            // Since zonify is run after baffling the surfaceIndex_ on baffles
+            // is
+            // not synchronised across both baffle faces. Fortunately we don't
+            // do zonify baffle faces anyway (they are normal boundary faces).
+
+            // Collect candidate faces
+            // ~~~~~~~~~~~~~~~~~~~~~~~
+
+            labelList testFaces(intersectedFaces());
+
+            // Collect segments
+            // ~~~~~~~~~~~~~~~~
+
+            pointField start(testFaces.size());
+            pointField end(testFaces.size());
+
+            forAll(testFaces, i)
             {
-                // If both hit should probably choose 'nearest'
-                if
-                (
-                    surface2[i] != -1
-                 && (
-                        magSqr(hit2[i].hitPoint())
-                      < magSqr(hit1[i].hitPoint())
-                    )
-                )
+                label faceI = testFaces[i];
+
+                if (mesh_.isInternalFace(faceI))
                 {
-                    namedSurfaceIndex[faceI] = surface2[i];
-                    posOrientation[faceI] = ((area&normal2[i]) > 0);
-                    nSurfFaces[surface2[i]]++;
+                    start[i] = cellCentres[faceOwner[faceI]];
+                    end[i] = cellCentres[faceNeighbour[faceI]];
                 }
                 else
                 {
-                    namedSurfaceIndex[faceI] = surface1[i];
-                    posOrientation[faceI] = ((area&normal1[i]) > 0);
-                    nSurfFaces[surface1[i]]++;
+                    start[i] = cellCentres[faceOwner[faceI]];
+                    end[i] = neiCc[faceI-mesh_.nInternalFaces()];
                 }
             }
-            else if (surface2[i] != -1)
+
+            // Extend segments a bit
             {
-                namedSurfaceIndex[faceI] = surface2[i];
-                posOrientation[faceI] = ((area&normal2[i]) > 0);
-                nSurfFaces[surface2[i]]++;
+                const vectorField smallVec(Foam::sqrt(SMALL)*(end-start));
+                start -= smallVec;
+                end += smallVec;
             }
-        }
 
 
-        // surfaceIndex might have different surfaces on both sides if
-        // there happen to be a (obviously thin) surface with different
-        // regions between the cell centres. If one is on a named surface
-        // and the other is not this might give problems so sync.
-        syncTools::syncFaceList
-        (
-            mesh_,
-            namedSurfaceIndex,
-            maxEqOp<label>()
-        );
+            // Do test for intersections
+            // ~~~~~~~~~~~~~~~~~~~~~~~~~
+            // Note that we intersect all intersected faces again. Could reuse
+            // the information already in surfaceIndex_.
 
-        // Print a bit
-        if (debug)
-        {
-            forAll(nSurfFaces, surfI)
+            labelList surface1;
+            List<pointIndexHit> hit1;
+            vectorField normal1;
+            labelList surface2;
+            List<pointIndexHit> hit2;
+            vectorField normal2;
             {
-                Pout<< "Surface:"
-                    << surfaces_.names()[surfI]
-                    << "  nZoneFaces:" << nSurfFaces[surfI] << nl;
+                labelList region1;
+                labelList region2;
+                surfaces_.findNearestIntersection
+                (
+                    namedSurfaces,
+                    start,
+                    end,
+
+                    surface1,
+                    hit1,
+                    region1,
+                    normal1,
+
+                    surface2,
+                    hit2,
+                    region2,
+                    normal2
+                );
             }
-            Pout<< endl;
-        }
-    }
 
+            forAll(testFaces, i)
+            {
+                label faceI = testFaces[i];
+                const vector& area = mesh_.faceAreas()[faceI];
 
-    // Put the cells into the correct zone
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                if (surface1[i] != -1)
+                {
+                    // If both hit should probably choose 'nearest'
+                    if
+                    (
+                        surface2[i] != -1
+                     && (
+                            magSqr(hit2[i].hitPoint())
+                          < magSqr(hit1[i].hitPoint())
+                        )
+                    )
+                    {
+                        namedSurfaceIndex[faceI] = surface2[i];
+                        posOrientation[faceI] = ((area&normal2[i]) > 0);
+                        nSurfFaces[surface2[i]]++;
+                    }
+                    else
+                    {
+                        namedSurfaceIndex[faceI] = surface1[i];
+                        posOrientation[faceI] = ((area&normal1[i]) > 0);
+                        nSurfFaces[surface1[i]]++;
+                    }
+                }
+                else if (surface2[i] != -1)
+                {
+                    namedSurfaceIndex[faceI] = surface2[i];
+                    posOrientation[faceI] = ((area&normal2[i]) > 0);
+                    nSurfFaces[surface2[i]]++;
+                }
+            }
 
-    // Zone per cell:
-    // -2 : unset
-    // -1 : not in any zone
-    // >=0: zoneID
-    labelList cellToZone(mesh_.nCells(), -2);
 
+            // surfaceIndex might have different surfaces on both sides if
+            // there happen to be a (obviously thin) surface with different
+            // regions between the cell centres. If one is on a named surface
+            // and the other is not this might give problems so sync.
+            syncTools::syncFaceList
+            (
+                mesh_,
+                namedSurfaceIndex,
+                maxEqOp<label>()
+            );
 
-    // Set using geometric test
-    // ~~~~~~~~~~~~~~~~~~~~~~~~
+            // Print a bit
+            if (debug)
+            {
+                forAll(nSurfFaces, surfI)
+                {
+                    Pout<< "Surface:"
+                        << surfaces_.names()[surfI]
+                        << "  nZoneFaces:" << nSurfFaces[surfI] << nl;
+                }
+                Pout<< endl;
+            }
+        }
 
-    // Closed surfaces with cellZone specified.
-    labelList closedNamedSurfaces
-    (
-        surfaceZonesInfo::getClosedNamedSurfaces
-        (
-            surfZones,
-            surfaces_.geometry(),
-            surfaces_.surfaces()
-        )
-    );
 
-    if (closedNamedSurfaces.size())
-    {
-        Info<< "Found " << closedNamedSurfaces.size()
-            << " closed, named surfaces. Assigning cells in/outside"
-            << " these surfaces to the corresponding cellZone."
-            << nl << endl;
+        // Now we have for all faces the intersection with a named surfaces (or
+        // -1). Use this information to set zone of cells
 
-        findCellZoneGeometric
-        (
-            neiCc,
-            closedNamedSurfaces,    // indices of closed surfaces
-            namedSurfaceIndex,      // per face index of named surface
-            surfaceToCellZone,      // cell zone index per surface
+        // Set using geometric test
+        // ~~~~~~~~~~~~~~~~~~~~~~~~
 
-            cellToZone
+        // Closed surfaces with cellZone specified.
+        labelList closedNamedSurfaces
+        (
+            surfaceZonesInfo::getClosedNamedSurfaces
+            (
+                surfZones,
+                surfaces_.geometry(),
+                surfaces_.surfaces()
+            )
         );
-    }
 
+        if (closedNamedSurfaces.size())
+        {
+            Info<< "Found " << closedNamedSurfaces.size()
+                << " closed, named surfaces. Assigning cells in/outside"
+                << " these surfaces to the corresponding cellZone."
+                << nl << endl;
+
+            findCellZoneGeometric
+            (
+                neiCc,
+                closedNamedSurfaces,    // indices of closed surfaces
+                namedSurfaceIndex,      // per face index of named surface
+                surfaceToCellZone,      // cell zone index per surface
 
-    // Set using provided locations
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                cellToZone
+            );
+        }
 
-    labelList locationSurfaces
-    (
-        surfaceZonesInfo::getInsidePointNamedSurfaces(surfZones)
-    );
 
-    if (locationSurfaces.size())
-    {
-        Info<< "Found " << locationSurfaces.size()
-            << " named surfaces with a provided inside point."
-            << " Assigning cells inside these surfaces"
-            << " to the corresponding cellZone."
-            << nl << endl;
+        // Set using provided locations
+        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-        findCellZoneInsideWalk
+        labelList locationSurfaces
         (
-            locationSurfaces,       // indices of closed surfaces
-            namedSurfaceIndex,      // per face index of named surface
-            surfaceToCellZone,      // cell zone index per surface
-
-            cellToZone
+            surfaceZonesInfo::getInsidePointNamedSurfaces(surfZones)
         );
-    }
 
+        if (locationSurfaces.size())
+        {
+            Info<< "Found " << locationSurfaces.size()
+                << " named surfaces with a provided inside point."
+                << " Assigning cells inside these surfaces"
+                << " to the corresponding cellZone."
+                << nl << endl;
 
-    // Set using walking
-    // ~~~~~~~~~~~~~~~~~
+            findCellZoneInsideWalk
+            (
+                locationSurfaces,       // indices of closed surfaces
+                namedSurfaceIndex,      // per face index of named surface
+                surfaceToCellZone,      // cell zone index per surface
 
-    {
-        Info<< "Walking from location-in-mesh " << keepPoint
+                cellToZone
+            );
+        }
+
+
+        // Set using walking
+        // ~~~~~~~~~~~~~~~~~
+
+        Info<< "Walking from " << locationsInMesh.size()
+            << " locations-in-mesh "
             << " to assign cellZones "
             << "- crossing a faceZone face changes cellZone" << nl << endl;
 
-        // Topological walk
+        labelList zoneIDs(refinementParameters::unzonedLocations(zonesInMesh));
+
         findCellZoneTopo
         (
-            keepPoint,
+            pointField(locationsInMesh, zoneIDs),
             namedSurfaceIndex,
             surfaceToCellZone,
 
             cellToZone
         );
+
+
+        // Make sure namedSurfaceIndex is unset inbetween same cell zones.
+        if (!allowFreeStandingZoneFaces)
+        {
+            Info<< "Only keeping zone faces inbetween different cellZones."
+                << nl << endl;
+
+            makeConsistentFaceIndex(cellToZone, namedSurfaceIndex);
+        }
+
+
+        // Convert namedSurfaceIndex (index of named surfaces) to
+        // actual faceZone index
+
+        forAll(namedSurfaceIndex, faceI)
+        {
+            label surfI = namedSurfaceIndex[faceI];
+            if (surfI != -1)
+            {
+                faceToZone[faceI] = surfaceToFaceZone[surfI];
+            }
+        }
     }
 
 
-    // Make sure namedSurfaceIndex is unset inbetween same cell cell zones.
-    if (!allowFreeStandingZoneFaces)
+
+    //if (debug&MESH)
+    //{
+    //    const_cast<Time&>(mesh_.time())++;
+    //    Pout<< "Writing mesh to time " << timeName() << endl;
+    //    write
+    //    (
+    //        debugType(debug),
+    //        writeType(writeLevel() | WRITEMESH),
+    //        mesh_.time().path()/"cellToZone"
+    //    );
+    //    volScalarField volCellToZone
+    //    (
+    //        IOobject
+    //        (
+    //            "cellToZone",
+    //            mesh_.time().timeName(),
+    //            mesh_,
+    //            IOobject::NO_READ,
+    //            IOobject::AUTO_WRITE,
+    //            false
+    //        ),
+    //        mesh_,
+    //        dimensionedScalar("zero", dimless, 0),
+    //        zeroGradientFvPatchScalarField::typeName
+    //    );
+    //
+    //    forAll(cellToZone, cellI)
+    //    {
+    //        volCellToZone[cellI] = cellToZone[cellI];
+    //    }
+    //    volCellToZone.write();
+    //}
+
+
+
+    // Allocate and assign faceZones from cellZones
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
     {
-        Info<< "Only keeping zone faces inbetween different cellZones."
-            << nl << endl;
+        // 1. Detect inter-region face and allocate names
+
+        HashTable<word, labelPair, labelPair::Hash<> > zoneIDsToFaceZone;
 
-        makeConsistentFaceIndex(cellToZone, namedSurfaceIndex);
+        for (label faceI = 0; faceI < mesh_.nInternalFaces(); faceI++)
+        {
+            if (faceToZone[faceI] == -1)
+            {
+                // Face not yet in a faceZone. (it might already have been
+                // done so by a 'named' surface). Check if inbetween different
+                // cellZones
+                allocateInterRegionFaceZone
+                (
+                    cellToZone[mesh_.faceOwner()[faceI]],
+                    cellToZone[mesh_.faceNeighbour()[faceI]],
+                    zonesToFaceZone,
+                    zoneIDsToFaceZone
+                );
+            }
+        }
+
+        labelList neiCellZone;
+        syncTools::swapBoundaryCellList(mesh_, cellToZone, neiCellZone);
+
+        forAll(neiCellZone, bFaceI)
+        {
+            label faceI = bFaceI + mesh_.nInternalFaces();
+            if (faceToZone[faceI] == -1)
+            {
+                allocateInterRegionFaceZone
+                (
+                    cellToZone[mesh_.faceOwner()[faceI]],
+                    neiCellZone[bFaceI],
+                    zonesToFaceZone,
+                    zoneIDsToFaceZone
+                );
+            }
+        }
+
+
+        // 2. Combine faceZoneNames allocated on different processors
+
+        Pstream::mapCombineGather(zonesToFaceZone, eqOp<word>());
+        Pstream::mapCombineScatter(zonesToFaceZone);
+
+
+        // 3. Allocate faceZones from (now synchronised) faceZoneNames
+        //    Note: the faceZoneNames contain the same data but in different
+        //          order. We could sort the contents but instead just loop
+        //          in sortedToc order.
+
+        Info<< "Setting faceZones according to neighbouring cellZones:"
+            << endl;
+
+        // From cellZone indices to faceZone index
+        HashTable<label, labelPair, labelPair::Hash<> > fZoneLookup
+        (
+            zonesToFaceZone.size()
+        );
+
+        const cellZoneMesh& cellZones = mesh_.cellZones();
+
+        {
+            List<Pair<word> > czs(zonesToFaceZone.sortedToc());
+
+            forAll(czs, i)
+            {
+                const Pair<word>& cz = czs[i];
+                const word& fzName = zonesToFaceZone[cz];
+
+                Info<< indent<< "cellZones : "
+                    << cz[0] << ' ' << cz[1] << nl
+                    << "    faceZone : " << fzName << endl;
+
+                label faceZoneI = surfaceZonesInfo::addFaceZone
+                (
+                    fzName,                 // name
+                    labelList(0),           // addressing
+                    boolList(0),            // flipMap
+                    mesh_
+                );
+
+                label cz0 = cellZones.findZoneID(cz[0]);
+                label cz1 = cellZones.findZoneID(cz[1]);
+
+                fZoneLookup.insert(labelPair(cz0, cz1), faceZoneI);
+            }
+        }
+
+
+        // 4. Set faceToZone with new faceZones
+
+
+        for (label faceI = 0; faceI < mesh_.nInternalFaces(); faceI++)
+        {
+            if (faceToZone[faceI] == -1)
+            {
+                // Face not yet in a faceZone. (it might already have been
+                // done so by a 'named' surface). Check if inbetween different
+                // cellZones
+
+                label ownZone = cellToZone[mesh_.faceOwner()[faceI]];
+                label neiZone = cellToZone[mesh_.faceNeighbour()[faceI]];
+                if (ownZone != neiZone)
+                {
+                    bool swap =
+                    (
+                        ownZone == -1
+                     || (neiZone != -1 && ownZone > neiZone)
+                    );
+                    labelPair key(ownZone, neiZone);
+                    if (swap)
+                    {
+                        Swap(key.first(), key.second());
+                    }
+                    faceToZone[faceI] = fZoneLookup[key];
+                }
+            }
+        }
+        forAll(neiCellZone, bFaceI)
+        {
+            label faceI = bFaceI + mesh_.nInternalFaces();
+            if (faceToZone[faceI] == -1)
+            {
+                label ownZone = cellToZone[mesh_.faceOwner()[faceI]];
+                label neiZone = neiCellZone[bFaceI];
+                if (ownZone != neiZone)
+                {
+                    bool swap =
+                    (
+                        ownZone == -1
+                     || (neiZone != -1 && ownZone > neiZone)
+                    );
+                    labelPair key(ownZone, neiZone);
+                    if (swap)
+                    {
+                        Swap(key.first(), key.second());
+                    }
+                    faceToZone[faceI] = fZoneLookup[key];
+                }
+            }
+        }
+        Info<< endl;
     }
 
-    // Topochange container
-    polyTopoChange meshMod(mesh_);
 
 
 
     // Get coupled neighbour cellZone. Set to -1 on non-coupled patches.
-    labelList neiCellZone(mesh_.nFaces()-mesh_.nInternalFaces(), -1);
+    labelList neiCellZone;
+    syncTools::swapBoundaryCellList(mesh_, cellToZone, neiCellZone);
     forAll(patches, patchI)
     {
         const polyPatch& pp = patches[patchI];
 
-        if (pp.coupled())
+        if (!pp.coupled())
         {
+            label bFaceI = pp.start()-mesh_.nInternalFaces();
             forAll(pp, i)
             {
-                label faceI = pp.start()+i;
-                neiCellZone[faceI-mesh_.nInternalFaces()] =
-                    cellToZone[mesh_.faceOwner()[faceI]];
+                neiCellZone[bFaceI++] = -1;
             }
         }
     }
-    syncTools::swapBoundaryFaceList(mesh_, neiCellZone);
+
+
 
     // Get per face whether is it master (of a coupled set of faces)
     const PackedBoolList isMasterFace(syncTools::getMasterFaces(mesh_));
 
 
-
     // faceZones
     // ~~~~~~~~~
     // Faces on faceZones come in two variants:
@@ -3214,7 +4448,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
                 mesh_.faces(),
                 freeStandingBaffleFaces
                 (
-                    namedSurfaceIndex,
+                    faceToZone,
                     cellToZone,
                     neiCellZone
                 )
@@ -3228,18 +4462,25 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
             Info<< "Detected " << nFreeStanding << " free-standing zone faces"
                 << endl;
 
+            if (debug)
+            {
+                OBJstream str(mesh_.time().path()/"freeStanding.obj");
+                str.write(patch.localFaces(), patch.localPoints(), false);
+            }
+
+
             // Detect non-manifold edges
             labelList nMasterFacesPerEdge;
             calcPatchNumMasterFaces(isMasterFace, patch, nMasterFacesPerEdge);
 
             // Mark zones. Even a single original surface might create multiple
             // disconnected/non-manifold-connected zones
-            labelList faceToZone;
+            labelList faceToConnectedZone;
             const label nZones = markPatchZones
             (
                 patch,
                 nMasterFacesPerEdge,
-                faceToZone
+                faceToConnectedZone
             );
 
             Map<label> nPosOrientation(2*nZones);
@@ -3256,7 +4497,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
                 isMasterFace,
                 patch,
                 nMasterFacesPerEdge,
-                faceToZone,
+                faceToConnectedZone,
                 nPosOrientation,
 
                 meshFlipMap
@@ -3280,7 +4521,7 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
                         n = -1;
                     }
 
-                    nPosOrientation.find(faceToZone[faceI])() += n;
+                    nPosOrientation.find(faceToConnectedZone[faceI])() += n;
                 }
             }
             Pstream::mapCombineGather(nPosOrientation, plusEqOp<label>());
@@ -3300,14 +4541,14 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
             Info<< endl;
 
 
-            // Reapply with new counts (in nPosOrientation). This will cause
+            // Re-apply with new counts (in nPosOrientation). This will cause
             // zones with a negative count to be flipped.
             consistentOrientation
             (
                 isMasterFace,
                 patch,
                 nMasterFacesPerEdge,
-                faceToZone,
+                faceToConnectedZone,
                 nPosOrientation,
 
                 meshFlipMap
@@ -3316,137 +4557,21 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
     }
 
 
-    // Put the faces into the correct zone
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    for (label faceI = 0; faceI < mesh_.nInternalFaces(); faceI++)
-    {
-        label surfI = namedSurfaceIndex[faceI];
-
-        if (surfI != -1)
-        {
-            // Orient face zone to have slave cells in max cell zone.
-            label ownZone = cellToZone[faceOwner[faceI]];
-            label neiZone = cellToZone[faceNeighbour[faceI]];
-
-            bool flip;
-
-            label maxZone = max(ownZone, neiZone);
-
-            if (maxZone == -1)
-            {
-                // free-standing face. Use geometrically derived orientation
-                flip = meshFlipMap[faceI];
-            }
-            else if (ownZone == maxZone)
-            {
-                flip = false;
-            }
-            else
-            {
-                flip = true;
-            }
-
-            meshMod.setAction
-            (
-                polyModifyFace
-                (
-                    mesh_.faces()[faceI],           // modified face
-                    faceI,                          // label of face
-                    faceOwner[faceI],               // owner
-                    faceNeighbour[faceI],           // neighbour
-                    false,                          // face flip
-                    -1,                             // patch for face
-                    false,                          // remove from zone
-                    surfaceToFaceZone[surfI],       // zone for face
-                    flip                            // face flip in zone
-                )
-            );
-        }
-    }
-
-
-    // Set owner as no-flip
-    forAll(patches, patchI)
-    {
-        const polyPatch& pp = patches[patchI];
-
-        label faceI = pp.start();
 
-        forAll(pp, i)
-        {
-            label surfI = namedSurfaceIndex[faceI];
 
-            if (surfI != -1)
-            {
-                label ownZone = cellToZone[faceOwner[faceI]];
-                label neiZone = neiCellZone[faceI-mesh_.nInternalFaces()];
-
-                bool flip;
-
-                label maxZone = max(ownZone, neiZone);
-
-                if (maxZone == -1)
-                {
-                    // free-standing face. Use geometrically derived orientation
-                    flip = meshFlipMap[faceI];
-                }
-                else if (ownZone == neiZone)
-                {
-                    // Free-standing zone face or coupled boundary. Keep master
-                    // face unflipped.
-                    flip = !isMasterFace[faceI];
-                }
-                else if (neiZone == maxZone)
-                {
-                    flip = true;
-                }
-                else
-                {
-                    flip = false;
-                }
-
-                meshMod.setAction
-                (
-                    polyModifyFace
-                    (
-                        mesh_.faces()[faceI],           // modified face
-                        faceI,                          // label of face
-                        faceOwner[faceI],               // owner
-                        -1,                             // neighbour
-                        false,                          // face flip
-                        patchI,                         // patch for face
-                        false,                          // remove from zone
-                        surfaceToFaceZone[surfI],       // zone for face
-                        flip                            // face flip in zone
-                    )
-                );
-            }
-            faceI++;
-        }
-    }
-
-
-    // Put the cells into the correct zone
-    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    forAll(cellToZone, cellI)
-    {
-        label zoneI = cellToZone[cellI];
+    // Topochange container
+    polyTopoChange meshMod(mesh_);
 
-        if (zoneI >= 0)
-        {
-            meshMod.setAction
-            (
-                polyModifyCell
-                (
-                    cellI,
-                    false,          // removeFromZone
-                    zoneI
-                )
-            );
-        }
-    }
+    // Insert changes to put cells and faces into zone
+    zonify
+    (
+        isMasterFace,
+        cellToZone,
+        neiCellZone,
+        faceToZone,
+        meshFlipMap,
+        meshMod
+    );
 
     // Change the mesh (no inflation, parallel sync)
     autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
diff --git a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementMerge.C b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementMerge.C
index 4fc1b400771c030c59ed7b3a83dd230e2e601f94..5e4864d23dc3aea5f7dbbf451dd29a0e9b6c3311 100644
--- a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementMerge.C
+++ b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementMerge.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -34,110 +34,115 @@ License
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-//// Merge faces that are in-line.
-//Foam::label Foam::meshRefinement::mergePatchFaces
-//(
-//    const scalar minCos,
-//    const scalar concaveCos,
-//    const labelList& patchIDs
-//)
-//{
-//    // Patch face merging engine
-//    combineFaces faceCombiner(mesh_);
-//
-//    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
-//
-//    // Pick up all candidate cells on boundary
-//    labelHashSet boundaryCells(mesh_.nFaces()-mesh_.nInternalFaces());
-//
-//    forAll(patchIDs, i)
-//    {
-//        label patchI = patchIDs[i];
-//
-//        const polyPatch& patch = patches[patchI];
-//
-//        if (!patch.coupled())
-//        {
-//            forAll(patch, i)
-//            {
-//                boundaryCells.insert(mesh_.faceOwner()[patch.start()+i]);
-//            }
-//        }
-//    }
-//
-//    // Get all sets of faces that can be merged
-//    labelListList mergeSets
-//    (
-//        faceCombiner.getMergeSets
-//        (
-//            minCos,
-//            concaveCos,
-//            boundaryCells
-//        )
-//    );
-//
-//    label nFaceSets = returnReduce(mergeSets.size(), sumOp<label>());
-//
-//    Info<< "mergePatchFaces : Merging " << nFaceSets
-//        << " sets of faces." << endl;
-//
-//    if (nFaceSets > 0)
-//    {
-//        // Topology changes container
-//        polyTopoChange meshMod(mesh_);
-//
-//        // Merge all faces of a set into the first face of the set. Remove
-//        // unused points.
-//        faceCombiner.setRefinement(mergeSets, meshMod);
-//
-//        // Change the mesh (no inflation)
-//        autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
-//
-//        // Update fields
-//        mesh_.updateMesh(map);
-//
-//        // Move mesh (since morphing does not do this)
-//        if (map().hasMotionPoints())
-//        {
-//            mesh_.movePoints(map().preMotionPoints());
-//        }
-//        else
-//        {
-//            // Delete mesh volumes. No other way to do this?
-//            mesh_.clearOut();
-//        }
-//
-//
-//        // Reset the instance for if in overwrite mode
-//        mesh_.setInstance(timeName());
-//
-//        faceCombiner.updateMesh(map);
-//
-//        // Get the kept faces that need to be recalculated.
-//        // Merging two boundary faces might shift the cell centre
-//        // (unless the faces are absolutely planar)
-//        labelHashSet retestFaces(6*mergeSets.size());
-//
-//        forAll(mergeSets, setI)
-//        {
-//            label oldMasterI = mergeSets[setI][0];
-//
-//            label faceI = map().reverseFaceMap()[oldMasterI];
-//
-//            // faceI is always uncoupled boundary face
-//            const cell& cFaces = mesh_.cells()[mesh_.faceOwner()[faceI]];
-//
-//            forAll(cFaces, i)
-//            {
-//                retestFaces.insert(cFaces[i]);
-//            }
-//        }
-//        updateMesh(map, retestFaces.toc());
-//    }
-//
-//
-//    return nFaceSets;
-//}
+// Merge faces that are in-line.
+Foam::label Foam::meshRefinement::mergePatchFaces
+(
+    const scalar minCos,
+    const scalar concaveCos,
+    const label mergeSize,
+    const labelList& patchIDs
+)
+{
+    // Patch face merging engine
+    combineFaces faceCombiner(mesh_, false);
+
+    const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+
+    // Pick up all candidate cells on boundary
+    labelHashSet boundaryCells(mesh_.nFaces()-mesh_.nInternalFaces());
+
+    forAll(patchIDs, i)
+    {
+        label patchI = patchIDs[i];
+
+        const polyPatch& patch = patches[patchI];
+
+        if (!patch.coupled())
+        {
+            forAll(patch, i)
+            {
+                boundaryCells.insert(mesh_.faceOwner()[patch.start()+i]);
+            }
+        }
+    }
+
+    // Get all sets of faces that can be merged
+    labelListList mergeSets
+    (
+        faceCombiner.getMergeSets
+        (
+            minCos,
+            concaveCos,
+            boundaryCells
+        )
+    );
+
+    if (mergeSize != -1)
+    {
+        // Keep only those that are mergeSize faces
+        label compactI = 0;
+        forAll(mergeSets, setI)
+        {
+            if (mergeSets[setI].size() == mergeSize)
+            {
+                mergeSets[compactI++] = mergeSets[setI];
+            }
+        }
+        mergeSets.setSize(compactI);
+    }
+
+
+    label nFaceSets = returnReduce(mergeSets.size(), sumOp<label>());
+
+    Info<< "Merging " << nFaceSets << " sets of faces." << nl << endl;
+
+    if (nFaceSets > 0)
+    {
+        // Topology changes container
+        polyTopoChange meshMod(mesh_);
+
+        // Merge all faces of a set into the first face of the set. Remove
+        // unused points.
+        faceCombiner.setRefinement(mergeSets, meshMod);
+
+        // Change the mesh (no inflation)
+        autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
+
+        // Update fields
+        mesh_.updateMesh(map);
+
+        // Move mesh (since morphing does not do this)
+        if (map().hasMotionPoints())
+        {
+            mesh_.movePoints(map().preMotionPoints());
+        }
+        else
+        {
+            // Delete mesh volumes. No other way to do this?
+            mesh_.clearOut();
+        }
+
+        // Reset the instance for if in overwrite mode
+        mesh_.setInstance(timeName());
+
+        faceCombiner.updateMesh(map);
+
+        // Get the kept faces that need to be recalculated.
+        // Merging two boundary faces might shift the cell centre
+        // (unless the faces are absolutely planar)
+        labelHashSet retestFaces(2*mergeSets.size());
+
+        forAll(mergeSets, setI)
+        {
+            label oldMasterI = mergeSets[setI][0];
+            retestFaces.insert(map().reverseFaceMap()[oldMasterI]);
+        }
+        updateMesh(map, growFaceCellFace(retestFaces));
+    }
+
+    return nFaceSets;
+}
+
 //
 //
 //// Remove points not used by any face or points used by only two faces where
diff --git a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementProblemCells.C b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementProblemCells.C
index 871c7f8a6c6eaa4d0993f955d546c85d9ab72a60..a32ee117ce37834d376178f3d4246d83b25e498d 100644
--- a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementProblemCells.C
+++ b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementProblemCells.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -1085,7 +1085,9 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCells
 Foam::labelList Foam::meshRefinement::markFacesOnProblemCellsGeometric
 (
     const snapParameters& snapParams,
-    const dictionary& motionDict
+    const dictionary& motionDict,
+    const labelList& globalToMasterPatch,
+    const labelList& globalToSlavePatch
 ) const
 {
     pointField oldPoints(mesh_.points());
@@ -1162,7 +1164,10 @@ Foam::labelList Foam::meshRefinement::markFacesOnProblemCellsGeometric
         (
             autoSnapDriver::calcNearestSurface
             (
+                snapParams.strictRegionSnap(),
                 *this,
+                globalToMasterPatch,
+                globalToSlavePatch,
                 snapDist,   // attraction
                 pp,
                 nearestPoint,
diff --git a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementRefine.C b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementRefine.C
index 3ccaa72835058e87bf1f48196b84e842f542d500..05f89fb5dd51396b8c56b822d1dffd87e83a3acc 100644
--- a/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementRefine.C
+++ b/src/mesh/autoMesh/autoHexMesh/meshRefinement/meshRefinementRefine.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2015 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -980,6 +980,7 @@ Foam::label Foam::meshRefinement::markSurfaceRefinement
     labelList surfaceMinLevel;
     surfaces_.findHigherIntersection
     (
+        shells_,
         start,
         end,
         minLevel,
diff --git a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.C b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.C
index ea43fb6f88824cb874ec63d227bcd6498392fbd3..7d47a612c9dc35a655594fdfe6a449b7fb490264 100644
--- a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.C
+++ b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.C
@@ -3,7 +3,7 @@
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
     \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
-     \\/     M anipulation  |
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -33,6 +33,91 @@ License
 #include "UPtrList.H"
 #include "volumeType.H"
 
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::labelList Foam::refinementSurfaces::findHigherLevel
+(
+    const searchableSurface& geom,
+    const shellSurfaces& shells,
+    const List<pointIndexHit>& intersectionInfo,
+    const labelList& surfaceLevel       // current level
+) const
+{
+    // See if a cached level field available
+    labelList minLevelField;
+    geom.getField(intersectionInfo, minLevelField);
+
+
+    // Detect any uncached values and do proper search
+    labelList localLevel(surfaceLevel);
+    {
+        // Check hits:
+        // 1. cached value == -1 : store for re-testing
+        // 2. cached value != -1 : use
+        // 3. uncached : use region 0 value
+
+        DynamicList<label> retestSet;
+        label nHits = 0;
+
+        forAll(intersectionInfo, i)
+        {
+            if (intersectionInfo[i].hit())
+            {
+                nHits++;
+
+                // Check if minLevelField for this surface.
+                if (minLevelField.size())
+                {
+                    if (minLevelField[i] == -1)
+                    {
+                        retestSet.append(i);
+                    }
+                    else
+                    {
+                        localLevel[i] = max(localLevel[i], minLevelField[i]);
+                    }
+                }
+                else
+                {
+                    retestSet.append(i);
+                }
+            }
+        }
+
+        label nRetest = returnReduce(retestSet.size(), sumOp<label>());
+        if (nRetest > 0)
+        {
+            reduce(nHits, sumOp<label>());
+
+            //Info<< "Retesting " << nRetest
+            //    << " out of " << nHits
+            //    << " intersections on uncached elements on geometry "
+            //    << geom.name() << endl;
+
+            pointField samples(retestSet.size());
+            forAll(retestSet, i)
+            {
+                samples[i] = intersectionInfo[retestSet[i]].hitPoint();
+            }
+            labelList shellLevel;
+            shells.findHigherLevel
+            (
+                samples,
+                UIndirectList<label>(surfaceLevel, retestSet)(),
+                shellLevel
+            );
+            forAll(retestSet, i)
+            {
+                label sampleI = retestSet[i];
+                localLevel[sampleI] = max(localLevel[sampleI], shellLevel[i]);
+            }
+        }
+    }
+
+    return localLevel;
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::refinementSurfaces::refinementSurfaces
@@ -408,19 +493,19 @@ void Foam::refinementSurfaces::setMinLevelFields
     {
         const searchableSurface& geom = allGeometry_[surfaces_[surfI]];
 
-        // Precalculation only makes sense if there are different regions
-        // (so different refinement levels possible) and there are some
+        // Cache the refinement level (max of surface level and shell level)
+        // on a per-element basis. Only makes sense if there are lots of
         // elements. Possibly should have 'enough' elements to have fine
         // enough resolution but for now just make sure we don't catch e.g.
         // searchableBox (size=6)
-        if (geom.regions().size() > 1 && geom.globalSize() > 10)
+        if (geom.globalSize() > 10)
         {
             // Representative local coordinates and bounding sphere
             pointField ctrs;
             scalarField radiusSqr;
             geom.boundingSpheres(ctrs, radiusSqr);
 
-            labelList minLevelField(ctrs.size(), -1);
+            labelList minLevelField(ctrs.size(), 0);
             {
                 // Get the element index in a roundabout way. Problem is e.g.
                 // distributed surface where local indices differ from global
@@ -447,9 +532,78 @@ void Foam::refinementSurfaces::setMinLevelFields
             labelList shellLevel;
             shells.findHigherLevel(ctrs, minLevelField, shellLevel);
 
+
+            // In case of triangulated surfaces only cache value if triangle
+            // centre and vertices are in same shell
+            if (isA<triSurface>(geom))
+            {
+                label nUncached = 0;
+
+                // Check if points differing from ctr level
+
+                const triSurface& ts = refCast<const triSurface>(geom);
+                const pointField& points = ts.points();
+
+                // Determine minimum expected level to avoid having to
+                // test lots of points
+                labelList minPointLevel(points.size(), labelMax);
+                forAll(shellLevel, triI)
+                {
+                    const labelledTri& t = ts[triI];
+                    label level = shellLevel[triI];
+                    forAll(t, tI)
+                    {
+                        minPointLevel[t[tI]] = min(minPointLevel[t[tI]], level);
+                    }
+                }
+
+
+                // See if inside any shells with higher refinement level
+                labelList pointLevel;
+                shells.findHigherLevel(points, minPointLevel, pointLevel);
+
+
+                // See if triangle centre values differ from triangle points
+                forAll(shellLevel, triI)
+                {
+                    const labelledTri& t = ts[triI];
+                    label fLevel = shellLevel[triI];
+                    if
+                    (
+                        (pointLevel[t[0]] != fLevel)
+                     || (pointLevel[t[1]] != fLevel)
+                     || (pointLevel[t[2]] != fLevel)
+                    )
+                    {
+                        //Pout<< "Detected triangle " << t.tri(ts.points())
+                        //    << " partially inside/partially outside" << endl;
+
+                        // Mark as uncached
+                        shellLevel[triI] = -1;
+                        nUncached++;
+                    }
+                }
+
+                Info<< "For geometry " << geom.name()
+                    << " detected " << returnReduce(nUncached, sumOp<label>())
+                    << " uncached triangles out of " << geom.globalSize()
+                    << endl;
+            }
+
+
+            // Combine overall level field with current shell level. Make sure
+            // to preserve -1 (from triSurfaceMeshes with triangles partly
+            // inside/outside
             forAll(minLevelField, i)
             {
-                minLevelField[i] = max(minLevelField[i], shellLevel[i]);
+                if (min(minLevelField[i], shellLevel[i]) < 0)
+                {
+                    minLevelField[i] = -1;
+                }
+                else
+                {
+                    minLevelField[i] = max(minLevelField[i], shellLevel[i]);
+                }
             }
 
             // Store minLevelField on surface
@@ -463,6 +617,8 @@ void Foam::refinementSurfaces::setMinLevelFields
 // number.
 void Foam::refinementSurfaces::findHigherIntersection
 (
+    const shellSurfaces& shells,
+
     const pointField& start,
     const pointField& end,
     const labelList& currentLevel,   // current cell refinement level
@@ -494,41 +650,48 @@ void Foam::refinementSurfaces::findHigherIntersection
         List<pointIndexHit> intersectionInfo(start.size());
         geom.findLineAny(start, end, intersectionInfo);
 
-        // See if a cached level field available
-        labelList minLevelField;
-        geom.getField(intersectionInfo, minLevelField);
-        bool haveLevelField =
-        (
-            returnReduce(minLevelField.size(), sumOp<label>())
-          > 0
-        );
 
-        if (!haveLevelField && geom.regions().size() == 1)
+        // Surface-based refinement level
+        labelList surfaceOnlyLevel(start.size(), -1);
         {
-            minLevelField = labelList
-            (
-                intersectionInfo.size(),
-                minLevel(surfI, 0)
-            );
-            haveLevelField = true;
-        }
+            // Get per intersection the region
+            labelList region;
+            geom.getRegion(intersectionInfo, region);
 
-        if (haveLevelField)
-        {
             forAll(intersectionInfo, i)
             {
-                if
-                (
-                    intersectionInfo[i].hit()
-                 && minLevelField[i] > currentLevel[i]
-                )
+                if (intersectionInfo[i].hit())
                 {
-                    surfaces[i] = surfI;    // index of surface
-                    surfaceLevel[i] = minLevelField[i];
+                    surfaceOnlyLevel[i] = minLevel(surfI, region[i]);
                 }
             }
-            return;
         }
+
+
+        // Get shell refinement level if higher
+        const labelList localLevel
+        (
+            findHigherLevel
+            (
+                geom,
+                shells,
+                intersectionInfo,
+                surfaceOnlyLevel // starting level
+            )
+        );
+
+
+        // Combine localLevel with current level
+        forAll(localLevel, i)
+        {
+            if (localLevel[i] > currentLevel[i])
+            {
+                surfaces[i] = surfI;    // index of surface
+                surfaceLevel[i] = localLevel[i];
+            }
+        }
+
+        return;
     }
 
 
@@ -546,40 +709,48 @@ void Foam::refinementSurfaces::findHigherIntersection
         // Do intersection test
         geom.findLineAny(p0, p1, intersectionInfo);
 
-        // See if a cached level field available
-        labelList minLevelField;
-        geom.getField(intersectionInfo, minLevelField);
 
-        // Copy all hits into arguments, In-place compact misses.
-        label missI = 0;
-        forAll(intersectionInfo, i)
+        // Surface-based refinement level
+        labelList surfaceOnlyLevel(intersectionInfo.size(), -1);
         {
-            // Get the minLevel for the point
-            label minLocalLevel = -1;
+            // Get per intersection the region
+            labelList region;
+            geom.getRegion(intersectionInfo, region);
 
-            if (intersectionInfo[i].hit())
+            forAll(intersectionInfo, i)
             {
-                // Check if minLevelField for this surface.
-                if (minLevelField.size())
-                {
-                    minLocalLevel = minLevelField[i];
-                }
-                else
+                if (intersectionInfo[i].hit())
                 {
-                    // Use the min level for the surface instead. Assume
-                    // single region 0.
-                    minLocalLevel = minLevel(surfI, 0);
+                    surfaceOnlyLevel[i] = minLevel(surfI, region[i]);
                 }
             }
+        }
+
+
+        // Get shell refinement level if higher
+        const labelList localLevel
+        (
+            findHigherLevel
+            (
+                geom,
+                shells,
+                intersectionInfo,
+                surfaceOnlyLevel
+            )
+        );
 
 
+        // Combine localLevel with current level
+        label missI = 0;
+        forAll(localLevel, i)
+        {
             label pointI = intersectionToPoint[i];
 
-            if (minLocalLevel > currentLevel[pointI])
+            if (localLevel[i] > currentLevel[pointI])
             {
                 // Mark point for refinement
                 surfaces[pointI] = surfI;
-                surfaceLevel[pointI] = minLocalLevel;
+                surfaceLevel[pointI] = localLevel[i];
             }
             else
             {
@@ -590,6 +761,7 @@ void Foam::refinementSurfaces::findHigherIntersection
             }
         }
 
+
         // All done? Note that this decision should be synchronised
         if (returnReduce(missI, sumOp<label>()) == 0)
         {
@@ -1061,7 +1233,7 @@ void Foam::refinementSurfaces::findNearest
 (
     const labelList& surfacesToTest,
     const pointField& samples,
-    const  scalarField& nearestDistSqr,
+    const scalarField& nearestDistSqr,
     labelList& hitSurface,
     List<pointIndexHit>& hitInfo
 ) const
@@ -1337,4 +1509,121 @@ void Foam::refinementSurfaces::findInside
 }
 
 
+void Foam::refinementSurfaces::findNearest
+(
+    const labelList& surfacesToTest,
+    const labelListList& regions,
+
+    const pointField& samples,
+    const scalarField& nearestDistSqr,
+
+    labelList& hitSurface,
+    List<pointIndexHit>& hitInfo
+) const
+{
+    labelList geometries(UIndirectList<label>(surfaces_, surfacesToTest));
+
+    // Do the tests. Note that findNearest returns index in geometries.
+    searchableSurfacesQueries::findNearest
+    (
+        allGeometry_,
+        geometries,
+        regions,
+        samples,
+        nearestDistSqr,
+        hitSurface,
+        hitInfo
+    );
+
+    // Rework the hitSurface to be surface (i.e. index into surfaces_)
+    forAll(hitSurface, pointI)
+    {
+        if (hitSurface[pointI] != -1)
+        {
+            hitSurface[pointI] = surfacesToTest[hitSurface[pointI]];
+        }
+    }
+}
+
+
+void Foam::refinementSurfaces::findNearestRegion
+(
+    const labelList& surfacesToTest,
+    const labelListList& regions,
+
+    const pointField& samples,
+    const scalarField& nearestDistSqr,
+
+    labelList& hitSurface,
+    List<pointIndexHit>& hitInfo,
+    labelList& hitRegion,
+    vectorField& hitNormal
+) const
+{
+    labelList geometries(UIndirectList<label>(surfaces_, surfacesToTest));
+
+    // Do the tests. Note that findNearest returns index in geometries.
+    searchableSurfacesQueries::findNearest
+    (
+        allGeometry_,
+        geometries,
+        regions,
+        samples,
+        nearestDistSqr,
+        hitSurface,
+        hitInfo
+    );
+
+    // Rework the hitSurface to be surface (i.e. index into surfaces_)
+    forAll(hitSurface, pointI)
+    {
+        if (hitSurface[pointI] != -1)
+        {
+            hitSurface[pointI] = surfacesToTest[hitSurface[pointI]];
+        }
+    }
+
+    // Collect the region
+    hitRegion.setSize(hitSurface.size());
+    hitRegion = -1;
+    hitNormal.setSize(hitSurface.size());
+    hitNormal = vector::zero;
+
+    forAll(surfacesToTest, i)
+    {
+        label surfI = surfacesToTest[i];
+
+        // Collect hits for surfI
+        const labelList localIndices(findIndices(hitSurface, surfI));
+
+        List<pointIndexHit> localHits
+        (
+            UIndirectList<pointIndexHit>
+            (
+                hitInfo,
+                localIndices
+            )
+        );
+
+        // Region
+        labelList localRegion;
+        allGeometry_[surfaces_[surfI]].getRegion(localHits, localRegion);
+
+        forAll(localIndices, i)
+        {
+            hitRegion[localIndices[i]] = localRegion[i];
+        }
+
+        // Normal
+        vectorField localNormal;
+        allGeometry_[surfaces_[surfI]].getNormal(localHits, localNormal);
+
+        forAll(localIndices, i)
+        {
+            hitNormal[localIndices[i]] = localNormal[i];
+        }
+    }
+}
+
+
 // ************************************************************************* //
diff --git a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.H b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.H
index 7f777daf334a51f4755e6cc8b94d4f8a38623b42..fadb9aaa2694e6b1d1b3dfa8c02e138a1c642307 100644
--- a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.H
+++ b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/refinementSurfaces.H
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2011-2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2011-2014 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -42,6 +42,7 @@ SourceFiles
 #include "vectorList.H"
 #include "pointIndexHit.H"
 #include "surfaceZonesInfo.H"
+#include "pointList.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -52,8 +53,6 @@ class searchableSurfaces;
 class shellSurfaces;
 class triSurfaceMesh;
 
-typedef List<point> pointList;
-
 /*---------------------------------------------------------------------------*\
                            Class refinementSurfaces Declaration
 \*---------------------------------------------------------------------------*/
@@ -95,6 +94,16 @@ class refinementSurfaces
 
     // Private Member Functions
 
+        //- Given intersection results with geom detect local shell refinement
+        //  level (possibly cached on triangles of geom)
+        labelList findHigherLevel
+        (
+            const searchableSurface& geom,
+            const shellSurfaces& shells,
+            const List<pointIndexHit>& intersectionInfo,
+            const labelList& surfaceLevel
+        ) const;
+
         //- Disallow default bitwise copy construct
         refinementSurfaces(const refinementSurfaces&);
 
@@ -234,6 +243,8 @@ public:
             //  Return surface number and level.
             void findHigherIntersection
             (
+                const shellSurfaces& shells,
+
                 const pointField& start,
                 const pointField& end,
                 const labelList& currentLevel,  // current cell refinement level
@@ -354,6 +365,37 @@ public:
                 const pointField& pt,
                 labelList& insideSurfaces
             ) const;
+
+            // Region wise searching
+
+                //- Find nearest point on selected regions of surfaces.
+                void findNearest
+                (
+                    const labelList& surfacesToTest,
+                    const labelListList& regions,
+
+                    const pointField& samples,
+                    const scalarField& nearestDistSqr,
+
+                    labelList& hitSurface,
+                    List<pointIndexHit>& hitInfo
+                ) const;
+
+                //- Find nearest point on selected regions of surfaces.
+                void findNearestRegion
+                (
+                    const labelList& surfacesToTest,
+                    const labelListList& regions,
+
+                    const pointField& samples,
+                    const scalarField& nearestDistSqr,
+
+                    labelList& hitSurface,
+                    List<pointIndexHit>& hitInfo,
+                    labelList& hitRegion,
+                    vectorField& hitNormal
+                ) const;
+
 };
 
 
diff --git a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.C b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.C
index 69c3f7777b95ea4b1a02a1d3d817673d55a7779b..771f826d8b5afeae064ccc25892b9eae8762c6b8 100644
--- a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.C
+++ b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.C
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2014 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -341,6 +341,37 @@ Foam::labelList Foam::surfaceZonesInfo::getInsidePointNamedSurfaces
 }
 
 
+Foam::label Foam::surfaceZonesInfo::addCellZone
+(
+    const word& name,
+    const labelList& addressing,
+    polyMesh& mesh
+)
+{
+    cellZoneMesh& cellZones = mesh.cellZones();
+
+    label zoneI = cellZones.findZoneID(name);
+
+    if (zoneI == -1)
+    {
+        zoneI = cellZones.size();
+        cellZones.setSize(zoneI+1);
+        cellZones.set
+        (
+            zoneI,
+            new cellZone
+            (
+                name,           // name
+                addressing,     // addressing
+                zoneI,          // index
+                cellZones       // cellZoneMesh
+            )
+        );
+    }
+    return zoneI;
+}
+
+
 Foam::labelList Foam::surfaceZonesInfo::addCellZonesToMesh
 (
     const PtrList<surfaceZonesInfo>& surfList,
@@ -350,8 +381,6 @@ Foam::labelList Foam::surfaceZonesInfo::addCellZonesToMesh
 {
     labelList surfaceToCellZone(surfList.size(), -1);
 
-    cellZoneMesh& cellZones = mesh.cellZones();
-
     forAll(namedSurfaces, i)
     {
         label surfI = namedSurfaces[i];
@@ -360,24 +389,12 @@ Foam::labelList Foam::surfaceZonesInfo::addCellZonesToMesh
 
         if (cellZoneName != word::null)
         {
-            label zoneI = cellZones.findZoneID(cellZoneName);
-
-            if (zoneI == -1)
-            {
-                zoneI = cellZones.size();
-                cellZones.setSize(zoneI+1);
-                cellZones.set
-                (
-                    zoneI,
-                    new cellZone
-                    (
-                        cellZoneName,   //name
-                        labelList(0),   //addressing
-                        zoneI,          //index
-                        cellZones       //cellZoneMesh
-                    )
-                );
-            }
+            label zoneI = addCellZone
+            (
+                cellZoneName,
+                labelList(0),   // addressing
+                mesh
+            );
 
             surfaceToCellZone[surfI] = zoneI;
         }
@@ -385,7 +402,7 @@ Foam::labelList Foam::surfaceZonesInfo::addCellZonesToMesh
 
     // Check they are synced
     List<wordList> allCellZones(Pstream::nProcs());
-    allCellZones[Pstream::myProcNo()] = cellZones.names();
+    allCellZones[Pstream::myProcNo()] = mesh.cellZones().names();
     Pstream::gatherList(allCellZones);
     Pstream::scatterList(allCellZones);
 
@@ -409,6 +426,40 @@ Foam::labelList Foam::surfaceZonesInfo::addCellZonesToMesh
 }
 
 
+
+Foam::label Foam::surfaceZonesInfo::addFaceZone
+(
+    const word& name,
+    const labelList& addressing,
+    const boolList& flipMap,
+    polyMesh& mesh
+)
+{
+    faceZoneMesh& faceZones = mesh.faceZones();
+
+    label zoneI = faceZones.findZoneID(name);
+
+    if (zoneI == -1)
+    {
+        zoneI = faceZones.size();
+        faceZones.setSize(zoneI+1);
+        faceZones.set
+        (
+            zoneI,
+            new faceZone
+            (
+                name,           // name
+                addressing,     // addressing
+                flipMap,        // flipMap
+                zoneI,          // index
+                faceZones       // faceZoneMesh
+            )
+        );
+    }
+    return zoneI;
+}
+
+
 Foam::labelList Foam::surfaceZonesInfo::addFaceZonesToMesh
 (
     const PtrList<surfaceZonesInfo>& surfList,
@@ -426,25 +477,13 @@ Foam::labelList Foam::surfaceZonesInfo::addFaceZonesToMesh
 
         const word& faceZoneName = surfList[surfI].faceZoneName();
 
-        label zoneI = faceZones.findZoneID(faceZoneName);
-
-        if (zoneI == -1)
-        {
-            zoneI = faceZones.size();
-            faceZones.setSize(zoneI+1);
-            faceZones.set
-            (
-                zoneI,
-                new faceZone
-                (
-                    faceZoneName,   //name
-                    labelList(0),   //addressing
-                    boolList(0),    //flipmap
-                    zoneI,          //index
-                    faceZones       //faceZoneMesh
-                )
-            );
-        }
+        label zoneI = addFaceZone
+        (
+            faceZoneName,   //name
+            labelList(0),   //addressing
+            boolList(0),    //flipmap
+            mesh
+        );
 
         surfaceToFaceZone[surfI] = zoneI;
     }
diff --git a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.H b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.H
index eb59aad34fab72539af60b17a02d55a2c05051e2..3b5a73c3f5c3c5778b2fa17565eaeca5f34f2444 100644
--- a/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.H
+++ b/src/mesh/autoMesh/autoHexMesh/refinementSurfaces/surfaceZonesInfo.H
@@ -2,8 +2,8 @@
   =========                 |
   \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
    \\    /   O peration     |
-    \\  /    A nd           | Copyright (C) 2013 OpenFOAM Foundation
-     \\/     M anipulation  |
+    \\  /    A nd           | Copyright (C) 2014 OpenFOAM Foundation
+     \\/     M anipulation  | Copyright (C) 2015 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -39,6 +39,7 @@ SourceFiles
 #include "word.H"
 #include "PtrList.H"
 #include "labelList.H"
+#include "boolList.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -222,6 +223,13 @@ public:
                 const PtrList<surfaceZonesInfo>& surfList
             );
 
+            static label addCellZone
+            (
+                const word& name,
+                const labelList& addressing,
+                polyMesh& mesh
+            );
+
             static labelList addCellZonesToMesh
             (
                 const PtrList<surfaceZonesInfo>& surfList,
@@ -229,6 +237,14 @@ public:
                 polyMesh& mesh
             );
 
+            static label addFaceZone
+            (
+                const word& name,
+                const labelList& addressing,
+                const boolList& flipMap,
+                polyMesh& mesh
+            );
+
             static labelList addFaceZonesToMesh
             (
                 const PtrList<surfaceZonesInfo>& surfList,
diff --git a/tutorials/mesh/snappyHexMesh/Allrun b/tutorials/mesh/snappyHexMesh/Allrun
index a035a98d0a073da131e6af6c40fa88bd41cbcc91..d37c93a33db0fd865f4840fc415a4cd87b45e334 100755
--- a/tutorials/mesh/snappyHexMesh/Allrun
+++ b/tutorials/mesh/snappyHexMesh/Allrun
@@ -7,6 +7,11 @@ cd ${0%/*} || exit 1    # Run from this directory
     ./Allrun
 )
 
+(
+    cd addLayersToFaceZone || exit
+    ./Allrun
+)
+
 exit 0
 
 # These cases are links to solver test cases and are run when the Allrun