diff --git a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
index f2fc385cb6..137e3ecb34 100644
--- a/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeParcelCloudFunctionObjects.H
@@ -39,9 +39,9 @@ License
#include "ParticleTrap.H"
#include "ParticleZoneInfo.H"
#include "ParticleHistogram.H"
+#include "ParticlePostProcessing.H"
#include "PatchCollisionDensity.H"
#include "PatchInteractionFields.H"
-#include "PatchPostProcessing.H"
#include "RemoveParcels.H"
#include "VoidFraction.H"
#include "KinematicReynoldsNumber.H"
@@ -61,9 +61,9 @@ License
makeCloudFunctionObjectType(ParticleTrap, CloudType); \
makeCloudFunctionObjectType(ParticleZoneInfo, CloudType); \
makeCloudFunctionObjectType(ParticleHistogram, CloudType); \
+ makeCloudFunctionObjectType(ParticlePostProcessing, CloudType); \
makeCloudFunctionObjectType(PatchCollisionDensity, CloudType); \
makeCloudFunctionObjectType(PatchInteractionFields, CloudType); \
- makeCloudFunctionObjectType(PatchPostProcessing, CloudType); \
makeCloudFunctionObjectType(RemoveParcels, CloudType); \
makeCloudFunctionObjectType(VoidFraction, CloudType); \
makeCloudFunctionObjectType(KinematicReynoldsNumber, CloudType); \
diff --git a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
index e620ceb4c1..30bfada4de 100644
--- a/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeReactingParcelCloudFunctionObjects.H
@@ -39,9 +39,9 @@ License
#include "ParticleTrap.H"
#include "ParticleZoneInfo.H"
#include "ParticleHistogram.H"
+#include "ParticlePostProcessing.H"
#include "PatchCollisionDensity.H"
#include "PatchInteractionFields.H"
-#include "PatchPostProcessing.H"
#include "RemoveParcels.H"
#include "VoidFraction.H"
#include "NusseltNumber.H"
@@ -64,9 +64,9 @@ License
makeCloudFunctionObjectType(ParticleTrap, CloudType); \
makeCloudFunctionObjectType(ParticleZoneInfo, CloudType); \
makeCloudFunctionObjectType(ParticleHistogram, CloudType); \
+ makeCloudFunctionObjectType(ParticlePostProcessing, CloudType); \
makeCloudFunctionObjectType(PatchCollisionDensity, CloudType); \
makeCloudFunctionObjectType(PatchInteractionFields, CloudType); \
- makeCloudFunctionObjectType(PatchPostProcessing, CloudType); \
makeCloudFunctionObjectType(RemoveParcels, CloudType); \
makeCloudFunctionObjectType(VoidFraction, CloudType); \
makeCloudFunctionObjectType(NusseltNumber, CloudType); \
diff --git a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
index 4a8a37f4fc..f43ae83b60 100644
--- a/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
+++ b/src/lagrangian/intermediate/parcels/include/makeThermoParcelCloudFunctionObjects.H
@@ -38,9 +38,9 @@ License
#include "ParticleTrap.H"
#include "ParticleZoneInfo.H"
#include "ParticleHistogram.H"
+#include "ParticlePostProcessing.H"
#include "PatchCollisionDensity.H"
#include "PatchInteractionFields.H"
-#include "PatchPostProcessing.H"
#include "RemoveParcels.H"
#include "VoidFraction.H"
#include "NusseltNumber.H"
@@ -62,9 +62,9 @@ License
makeCloudFunctionObjectType(ParticleTrap, CloudType); \
makeCloudFunctionObjectType(ParticleZoneInfo, CloudType); \
makeCloudFunctionObjectType(ParticleHistogram, CloudType); \
+ makeCloudFunctionObjectType(ParticlePostProcessing, CloudType); \
makeCloudFunctionObjectType(PatchCollisionDensity, CloudType); \
makeCloudFunctionObjectType(PatchInteractionFields, CloudType); \
- makeCloudFunctionObjectType(PatchPostProcessing, CloudType); \
makeCloudFunctionObjectType(RemoveParcels, CloudType); \
makeCloudFunctionObjectType(VoidFraction, CloudType); \
makeCloudFunctionObjectType(NusseltNumber, CloudType); \
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticlePostProcessing/ParticlePostProcessing.C b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticlePostProcessing/ParticlePostProcessing.C
new file mode 100644
index 0000000000..32175a1fee
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticlePostProcessing/ParticlePostProcessing.C
@@ -0,0 +1,269 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2011-2017 OpenFOAM Foundation
+ Copyright (C) 2019-2023 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 "ParticlePostProcessing.H"
+#include "Pstream.H"
+#include "stringListOps.H"
+#include "ListOps.H"
+#include "ListListOps.H"
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+template
+void Foam::ParticlePostProcessing::writeFileHeader(Ostream& os) const
+{
+ this->writeCommented(os, "Time");
+ os << ' ' << "currentProc";
+
+ if (!header_.empty())
+ {
+ os << ' ' << header_;
+ }
+
+ os << endl;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+template
+Foam::ParticlePostProcessing::ParticlePostProcessing
+(
+ const dictionary& dict,
+ CloudType& owner,
+ const word& modelName
+)
+:
+ CloudFunctionObject(dict, owner, modelName, typeName),
+ functionObjects::writeFile
+ (
+ owner,
+ this->localPath(),
+ typeName
+ ),
+ collector_(this->coeffDict(), owner.mesh()),
+ maxStoredParcels_(this->coeffDict().getScalar("maxStoredParcels")),
+ header_(),
+ fields_(),
+ times_(),
+ data_()
+{
+ writeFile::read(this->coeffDict());
+
+ this->coeffDict().readIfPresent("fields", fields_);
+
+ if (maxStoredParcels_ <= 0)
+ {
+ FatalIOErrorInFunction(this->coeffDict())
+ << "maxStoredParcels = " << maxStoredParcels_
+ << ", cannot be equal to or less than zero"
+ << exit(FatalIOError);
+ }
+
+ const label sz = collector_.size();
+ times_.resize(sz);
+ data_.resize(sz);
+}
+
+
+template
+Foam::ParticlePostProcessing::ParticlePostProcessing
+(
+ const ParticlePostProcessing& ppp
+)
+:
+ CloudFunctionObject(ppp),
+ writeFile(ppp),
+ collector_(ppp.collector_),
+ maxStoredParcels_(ppp.maxStoredParcels_),
+ header_(ppp.header_),
+ fields_(ppp.fields_),
+ times_(ppp.times_),
+ data_(ppp.data_)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+template
+void Foam::ParticlePostProcessing::postPatch
+(
+ const parcelType& p,
+ const polyPatch& pp,
+ bool&
+)
+{
+ if (!collector_.isPatch())
+ {
+ return;
+ }
+
+ const label patchi = pp.index();
+ const label localPatchi = collector_.IDs().find(patchi);
+
+ if (header_.empty())
+ {
+ OStringStream data;
+ p.writeProperties(data, fields_, " ", true);
+ header_ = data.str();
+ }
+
+ if (localPatchi != -1 && data_[localPatchi].size() < maxStoredParcels_)
+ {
+ times_[localPatchi].append(this->owner().time().value());
+
+ OStringStream data;
+ data<< Pstream::myProcNo();
+ p.writeProperties(data, fields_, " ", false);
+
+ data_[localPatchi].append(data.str());
+ }
+}
+
+
+template
+void Foam::ParticlePostProcessing::postFace
+(
+ const parcelType& p,
+ bool&
+)
+{
+ if (collector_.isPatch())
+ {
+ return;
+ }
+
+ const labelList& IDs = collector_.IDs();
+ const List& BBs = collector_.BBs();
+ const faceZoneMesh& fzm = this->owner().mesh().faceZones();
+
+ if (header_.empty())
+ {
+ OStringStream data;
+ p.writeProperties(data, fields_, " ", true);
+ header_ = data.str();
+ }
+
+ forAll(IDs, i)
+ {
+ if (!BBs[i].contains(p.position()))
+ {
+ // Quick reject if the particle is not in the face zone bound box
+ continue;
+ }
+
+ const label zonei = IDs[i];
+ const label localFacei = fzm[zonei].find(p.face());
+
+ if (localFacei != -1 && data_[localFacei].size() < maxStoredParcels_)
+ {
+ times_[i].append(this->owner().time().value());
+
+ OStringStream data;
+ data<< Pstream::myProcNo();
+ p.writeProperties(data, fields_, " ", false);
+
+ data_[i].append(data.str());
+ }
+ }
+}
+
+
+template
+void Foam::ParticlePostProcessing::write()
+{
+ const wordList& names = collector_.names();
+
+ forAll(names, i)
+ {
+ List procTimes(Pstream::nProcs());
+ procTimes[Pstream::myProcNo()] = times_[i];
+ Pstream::gatherList(procTimes);
+
+ List> procData(Pstream::nProcs());
+ procData[Pstream::myProcNo()] = data_[i];
+ Pstream::gatherList(procData);
+
+ Pstream::combineReduce
+ (
+ header_,
+ [](string& x, const string& y)
+ {
+ if (y.size() > x.size())
+ {
+ x = y;
+ }
+ }
+ );
+
+ if (Pstream::master())
+ {
+ List globalData;
+ globalData = ListListOps::combine>
+ (
+ procData,
+ accessOp>()
+ );
+
+ scalarList globalTimes;
+ globalTimes = ListListOps::combine
+ (
+ procTimes,
+ accessOp()
+ );
+
+ if (this->writeToFile())
+ {
+ autoPtr osPtr = this->newFileAtTime
+ (
+ names[i],
+ this->owner().time().value()
+ );
+ OFstream& os = osPtr.ref();
+
+ writeFileHeader(os);
+
+ const labelList indices(sortedOrder(globalTimes));
+ forAll(globalTimes, j)
+ {
+ const label datai = indices[j];
+
+ os << globalTimes[datai] << tab
+ << globalData[datai].c_str()
+ << nl;
+ }
+ }
+ }
+
+ times_[i].clearStorage();
+ data_[i].clearStorage();
+ }
+}
+
+
+// ************************************************************************* //
diff --git a/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticlePostProcessing/ParticlePostProcessing.H b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticlePostProcessing/ParticlePostProcessing.H
new file mode 100644
index 0000000000..20150c5210
--- /dev/null
+++ b/src/lagrangian/intermediate/submodels/CloudFunctionObjects/ParticlePostProcessing/ParticlePostProcessing.H
@@ -0,0 +1,226 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2011-2017 OpenFOAM Foundation
+ Copyright (C) 2019-2023 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 .
+
+Class
+ Foam::ParticlePostProcessing
+
+Description
+ Writes out various standard Lagrangian data elements of
+ particles hitting on a given list of patches or face zones.
+
+ Operands:
+ \table
+ Operand | Type | Location
+ input | - | -
+ output file | dat | \/postProcessing/\/\