diff --git a/src/meshTools/Make/files b/src/meshTools/Make/files
index 5f3a5d7d25..4f974d5d2f 100644
--- a/src/meshTools/Make/files
+++ b/src/meshTools/Make/files
@@ -224,6 +224,7 @@ $(faceZoneSources)/setToFaceZone/setToFaceZone.C
$(faceZoneSources)/setAndNormalToFaceZone/setAndNormalToFaceZone.C
$(faceZoneSources)/searchableSurfaceToFaceZone/searchableSurfaceToFaceZone.C
$(faceZoneSources)/planeToFaceZone/planeToFaceZone.C
+$(faceZoneSources)/cellToFaceZone/cellToFaceZone.C
cellZoneSources = topoSet/cellZoneSources
$(cellZoneSources)/topoSetCellZoneSource/topoSetCellZoneSource.C
diff --git a/src/meshTools/topoSet/faceZoneSources/cellToFaceZone/cellToFaceZone.C b/src/meshTools/topoSet/faceZoneSources/cellToFaceZone/cellToFaceZone.C
new file mode 100644
index 0000000000..ed6a4c3041
--- /dev/null
+++ b/src/meshTools/topoSet/faceZoneSources/cellToFaceZone/cellToFaceZone.C
@@ -0,0 +1,287 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "cellToFaceZone.H"
+#include "polyMesh.H"
+#include "faceZoneSet.H"
+#include "cellSet.H"
+#include "syncTools.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+ defineTypeNameAndDebug(cellToFaceZone, 0);
+ addToRunTimeSelectionTable(topoSetSource, cellToFaceZone, word);
+ addToRunTimeSelectionTable(topoSetSource, cellToFaceZone, istream);
+
+ addToRunTimeSelectionTable(topoSetFaceZoneSource, cellToFaceZone, word);
+ addToRunTimeSelectionTable(topoSetFaceZoneSource, cellToFaceZone, istream);
+}
+
+
+Foam::topoSetSource::addToUsageTable Foam::cellToFaceZone::usage_
+(
+ cellToFaceZone::typeName,
+ "\n Usage: cellToFaceZone \n\n"
+ " Select all outside faces in the cellSet."
+ " Orientated so slave side is in cellSet.\n\n"
+);
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+void Foam::cellToFaceZone::selectFaces
+(
+ const cellSet& cSet,
+ bitSet& selectedFace,
+ bitSet& doFlip
+) const
+{
+ selectedFace.setSize(mesh_.nFaces());
+ selectedFace = false;
+
+ doFlip.setSize(mesh_.nFaces());
+ doFlip = false;
+
+
+ // Add all faces whose both neighbours are in set.
+
+ const label nInt = mesh_.nInternalFaces();
+ const labelList& own = mesh_.faceOwner();
+ const labelList& nei = mesh_.faceNeighbour();
+ const polyBoundaryMesh& patches = mesh_.boundaryMesh();
+
+
+ // Check all internal faces
+ for (label facei = 0; facei < nInt; ++facei)
+ {
+ const bool ownFound = cSet.found(own[facei]);
+ const bool neiFound = cSet.found(nei[facei]);
+
+ if (ownFound && !neiFound)
+ {
+ selectedFace.set(facei);
+ doFlip.set(facei, flip_);
+ }
+ else if (!ownFound && neiFound)
+ {
+ selectedFace.set(facei);
+ doFlip.set(facei, !flip_);
+ }
+ }
+
+ // Get coupled cell status
+ boolList neiInSet(mesh_.nBoundaryFaces(), false);
+
+ for (const polyPatch& pp : patches)
+ {
+ if (pp.coupled())
+ {
+ label facei = pp.start();
+ forAll(pp, i)
+ {
+ neiInSet[facei-nInt] = cSet.found(own[facei]);
+ ++facei;
+ }
+ }
+ }
+ syncTools::swapBoundaryFaceList(mesh_, neiInSet);
+
+
+ // Check all boundary faces
+ for (const polyPatch& pp : patches)
+ {
+ label facei = pp.start();
+ forAll(pp, i)
+ {
+ const bool ownFound = cSet.found(own[facei]);
+ const bool neiFound = neiInSet[facei-nInt];
+
+ if (ownFound && !neiFound)
+ {
+ selectedFace.set(facei);
+ doFlip.set(facei, flip_);
+ }
+ else if (!ownFound && neiFound)
+ {
+ selectedFace.set(facei);
+ doFlip.set(facei, !flip_);
+ }
+ ++facei;
+ }
+ }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::cellToFaceZone::cellToFaceZone
+(
+ const polyMesh& mesh,
+ const word& setName,
+ const bool flip
+)
+:
+ topoSetFaceZoneSource(mesh),
+ names_(one{}, setName),
+ flip_(flip)
+{}
+
+
+Foam::cellToFaceZone::cellToFaceZone
+(
+ const polyMesh& mesh,
+ const dictionary& dict
+)
+:
+ topoSetFaceZoneSource(mesh),
+ names_(),
+ flip_(dict.getOrDefault("flip", false))
+{
+ // Look for 'sets' or 'set'
+ if (!dict.readIfPresent("sets", names_))
+ {
+ names_.resize(1);
+ dict.readEntry("set", names_.first());
+ }
+}
+
+
+Foam::cellToFaceZone::cellToFaceZone
+(
+ const polyMesh& mesh,
+ Istream& is
+)
+:
+ topoSetFaceZoneSource(mesh),
+ names_(one{}, word(checkIs(is))),
+ flip_(false)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+void Foam::cellToFaceZone::applyToSet
+(
+ const topoSetSource::setAction action,
+ topoSet& set
+) const
+{
+ if (!isA(set))
+ {
+ WarningInFunction
+ << "Operation only allowed on a faceZoneSet." << endl;
+ return;
+ }
+ else
+ {
+ faceZoneSet& zoneSet = refCast(set);
+
+ if (action == topoSetSource::ADD || action == topoSetSource::NEW)
+ {
+ if (verbose_)
+ {
+ if (flip_)
+ {
+ Info<< " Adding all faces on outside of cellSet "
+ << flatOutput(names_)
+ << "; orientation pointing into cellSet" << endl;
+ }
+ else
+ {
+ Info<< " Adding all faces on outside of cellSet "
+ << flatOutput(names_)
+ << "; orientation pointing away from cellSet" << endl;
+ }
+ }
+
+ bitSet selectedFace(mesh_.nFaces());
+ bitSet doFlip(mesh_.nFaces());
+ for (const word& setName : names_)
+ {
+ // Load the sets
+ cellSet cSet(mesh_, setName);
+ // Select outside faces
+ selectFaces(cSet, selectedFace, doFlip);
+ }
+
+ // Start off from copy
+ DynamicList