diff --git a/src/parallel/reconstruct/faReconstruct/Make/files b/src/parallel/reconstruct/faReconstruct/Make/files
index 4d7028d13c..c0f15e7ecd 100644
--- a/src/parallel/reconstruct/faReconstruct/Make/files
+++ b/src/parallel/reconstruct/faReconstruct/Make/files
@@ -1,4 +1,5 @@
processorFaMeshes.C
faFieldReconstructor.C
+faMeshReconstructor.C
LIB = $(FOAM_LIBBIN)/libfaReconstruct
diff --git a/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.C b/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.C
new file mode 100644
index 0000000000..0f85a81d5e
--- /dev/null
+++ b/src/parallel/reconstruct/faReconstruct/faMeshReconstructor.C
@@ -0,0 +1,637 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "faMeshReconstructor.H"
+#include "globalIndex.H"
+#include "globalMeshData.H"
+#include "edgeHashes.H"
+#include "Time.H"
+#include "PstreamCombineReduceOps.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+int Foam::faMeshReconstructor::debug = 0;
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+void Foam::faMeshReconstructor::calcAddressing
+(
+ const labelUList& fvFaceProcAddr
+)
+{
+ const globalIndex globalFaceNum(procMesh_.nFaces());
+
+ // ----------------------
+ // boundaryProcAddressing
+ //
+ // Trivial since non-processor boundary ordering is identical
+
+ const label nPatches = procMesh_.boundary().size();
+
+ faBoundaryProcAddr_ = identity(nPatches);
+
+ // Mark processor patches
+ for
+ (
+ label patchi = procMesh_.boundary().nNonProcessor();
+ patchi < nPatches;
+ ++patchi
+ )
+ {
+ faBoundaryProcAddr_[patchi] = -1;
+ }
+
+
+ // ------------------
+ // faceProcAddressing
+ //
+ // Transcribe/rewrite based on finiteVolume faceProcAddressing
+
+ faFaceProcAddr_ = procMesh_.faceLabels();
+
+ // From local faceLabels to proc values
+ for (label& facei : faFaceProcAddr_)
+ {
+ // Use finiteVolume info, ignoring face flips
+ facei = mag(fvFaceProcAddr[facei] - 1);
+ }
+
+
+ // Make global consistent.
+ // Starting at '0', without gaps in numbering etc.
+ // - the sorted order is the obvious solution
+ {
+ globalFaceNum.gather(faFaceProcAddr_, singlePatchFaceLabels_);
+
+ labelList order(Foam::sortedOrder(singlePatchFaceLabels_));
+
+ singlePatchFaceLabels_ = labelList(singlePatchFaceLabels_, order);
+
+ globalFaceNum.scatter(order, faFaceProcAddr_);
+ }
+
+ // Broadcast the same information everywhere
+ Pstream::scatter(singlePatchFaceLabels_);
+
+
+ // ------------------
+ // edgeProcAddressing
+ //
+ // This is more complicated.
+
+ // Create a single (serial) patch of the finiteArea mesh without a
+ // corresponding volume mesh, otherwise it would be the same as
+ // reconstructPar on the volume mesh (big, slow, etc).
+ //
+ // Do manual point-merge and relabeling to avoid dependency on
+ // finiteVolume pointProcAddressing
+
+ faPointProcAddr_.clear(); // Final size == procMesh_.nPoints()
+
+ // 1.
+ // - Topological point merge of the area meshes
+ // - use the local patch faces and points
+
+ // 2.
+ // - build a single (serial) patch of the finiteArea mesh only
+ // without any point support from the volume mesh
+ // - it may be possible to skip this step, but not obvious how
+
+ // The collected faces are contiguous per processor, which gives us
+ // the possibility to easily identify their source by using the
+ // global face indexing (if needed).
+ // The ultimate face locations are handled with a separate ordering
+ // list.
+
+ const uindirectPrimitivePatch& procPatch = procMesh_.patch();
+
+
+ {
+ faceList singlePatchProcFaces; // [proc0faces, proc1faces ...]
+ labelList uniqueMeshPointLabels;
+
+ // Local face points to compact merged point index
+ labelList pointToGlobal;
+
+ autoPtr globalPointsPtr =
+ procMesh_.mesh().globalData().mergePoints
+ (
+ procPatch.meshPoints(),
+ procPatch.meshPointMap(), // unused
+ pointToGlobal,
+ uniqueMeshPointLabels
+ );
+
+ // Gather faces, renumbered for the *merged* points
+ faceList tmpFaces(globalFaceNum.localSize());
+
+ forAll(tmpFaces, facei)
+ {
+ tmpFaces[facei] =
+ face(pointToGlobal, procPatch.localFaces()[facei]);
+ }
+
+ globalFaceNum.gather
+ (
+ tmpFaces,
+ singlePatchProcFaces,
+ UPstream::msgType(),
+ Pstream::commsTypes::scheduled
+ );
+
+ globalPointsPtr().gather
+ (
+ UIndirectList
+ (
+ procPatch.points(), // NB: mesh points (non-local)
+ uniqueMeshPointLabels
+ ),
+ singlePatchPoints_
+ );
+
+ // Transcribe into final assembled order
+ singlePatchFaces_.resize(singlePatchProcFaces.size());
+
+ // Target face ordering
+ labelList singlePatchProcAddr;
+ globalFaceNum.gather(faFaceProcAddr_, singlePatchProcAddr);
+
+ forAll(singlePatchProcAddr, facei)
+ {
+ const label targetFacei = singlePatchProcAddr[facei];
+ singlePatchFaces_[targetFacei] = singlePatchProcFaces[facei];
+ }
+
+ // Use initial equivalent "global" (serial) patch
+ // to establish the correct point and face walking order
+ //
+ // - only meaningful on master
+ const primitivePatch initialPatch
+ (
+ SubList(singlePatchFaces_),
+ singlePatchPoints_
+ );
+
+ // Grab the faces/points in local point ordering
+ tmpFaces = initialPatch.localFaces();
+ pointField tmpPoints(initialPatch.localPoints());
+
+ // The meshPointMap is contiguous, so flatten as linear list
+ /// Map