diff --git a/src/functionObjects/utilities/Make/files b/src/functionObjects/utilities/Make/files
index bc759df937..2e3e9c84f4 100644
--- a/src/functionObjects/utilities/Make/files
+++ b/src/functionObjects/utilities/Make/files
@@ -1,5 +1,7 @@
abort/abort.C
+caseInfo/caseInfo.C
+
codedFunctionObject/codedFunctionObject.C
areaWrite/areaWrite.C
diff --git a/src/functionObjects/utilities/caseInfo/caseInfo.C b/src/functionObjects/utilities/caseInfo/caseInfo.C
new file mode 100644
index 0000000000..cee60e20b0
--- /dev/null
+++ b/src/functionObjects/utilities/caseInfo/caseInfo.C
@@ -0,0 +1,529 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 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 "caseInfo.H"
+#include "OFstream.H"
+#include "fvMesh.H"
+#include "cloud.H"
+#include "globalMeshData.H"
+#include "volFields.H"
+#include "surfaceFields.H"
+#include "processorFvPatch.H"
+#include "JSONformatter.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionObjects
+{
+ defineTypeNameAndDebug(caseInfo, 0);
+
+ addToRunTimeSelectionTable
+ (
+ functionObject,
+ caseInfo,
+ dictionary
+ );
+}
+
+const Enum
+Foam::functionObjects::caseInfo::writeFormatNames_
+{
+ { writeFormat::dict, "dictionary" },
+ { writeFormat::json, "json" },
+};
+
+const Enum
+Foam::functionObjects::caseInfo::lookupModeNames_
+{
+ { lookupMode::none, "none" },
+ { lookupMode::warn, "warn" },
+ { lookupMode::error, "error" },
+};
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+void Foam::functionObjects::caseInfo::report(const string& str) const
+{
+ switch (lookupMode_)
+ {
+ case lookupMode::warn:
+ {
+ Warning << str.c_str() << endl;
+ break;
+ }
+ case lookupMode::error:
+ {
+ FatalError << str.c_str() << exit(FatalError);
+ break;
+ }
+ case lookupMode::none:
+ {
+ break;
+ }
+ }
+}
+
+
+void Foam::functionObjects::caseInfo::processDict
+(
+ dictionary& dict,
+ const dictionary& targetDict,
+ const entry* includePtr,
+ const entry* excludePtr
+) const
+{
+ auto sanitise = [](const wordRe& w){
+ string str = w;
+ str.replaceAll("/", "_").replaceAll(".", "_");
+
+ // Strip any leading "_"
+ while (str.starts_with('_'))
+ {
+ str = str.substr(1);
+ }
+ return str;
+ };
+
+ if (includePtr)
+ {
+ const wordRes includeEntryNames(includePtr->stream());
+ for (const auto& nameRegex : includeEntryNames)
+ {
+ const auto* e = targetDict.findScoped(nameRegex, keyType::REGEX);
+ if (e)
+ {
+ if (nameRegex.contains('/') || nameRegex.contains('.'))
+ {
+ auto copyPtr = e->clone();
+ copyPtr->keyword() = sanitise(nameRegex);
+ dict.add(copyPtr.ptr());
+ }
+ else
+ {
+ dict.add(*e);
+ }
+ }
+ else
+ {
+ report
+ (
+ "Unable to find entry "
+ + nameRegex
+ + " in dictionary "
+ + targetDict.name()
+ );
+ }
+ }
+ }
+ else
+ {
+ if (excludePtr)
+ {
+ dictionary allData(targetDict);
+
+ const wordRes excludeEntryNames(excludePtr->stream());
+
+ for (const auto& nameRegex : excludeEntryNames)
+ {
+ const auto* e = allData.findScoped(nameRegex, keyType::REGEX);
+ if (e)
+ {
+ allData.remove(e->keyword());
+ }
+ }
+
+ dict += allData;
+ }
+ else
+ {
+ // Add complete dictionary
+ dict += targetDict;
+ }
+ }
+}
+
+
+// * * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * //
+
+void Foam::functionObjects::caseInfo::writeMeta(dictionary& out) const
+{
+ out.add("case", time_.globalCaseName());
+ out.add("path", time_.globalPath());
+ out.add("regions", time_.sortedNames());
+ out.add("nTimes", time_.times().size());
+ out.add("nProc", Pstream::nProcs());
+}
+
+
+void Foam::functionObjects::caseInfo::writeRegisteredDicts
+(
+ const objectRegistry& obr,
+ dictionary& out,
+ dictionary& dictionaries
+) const
+{
+ for (const auto& e : dictionaries)
+ {
+ const auto& keyword = e.keyword();
+
+ if (!e.isDict())
+ {
+ FatalIOErrorInFunction(dictionaries)
+ << "Entries must be specified in dictionary format. Please "
+ << "correct entry " << keyword
+ << exit(FatalIOError);
+ }
+
+ const dictionary& inputDict = e.dict();
+
+ auto* includePtr = inputDict.findEntry("include");
+ auto* excludePtr = inputDict.findEntry("exclude");
+
+ const auto* ePtr = inputDict.findEntry("name");
+
+ if (ePtr)
+ {
+ const word name(ePtr->stream());
+ auto* dictPtr = obr.cfindObject(name);
+
+ if (dictPtr)
+ {
+ processDict
+ (
+ out.subDictOrAdd(keyword),
+ *dictPtr,
+ includePtr,
+ excludePtr
+ );
+ dictionaries.remove(keyword);
+ }
+ }
+ }
+}
+
+
+void Foam::functionObjects::caseInfo::writeFileDicts
+(
+ dictionary& out,
+ dictionary& dictionaries
+) const
+{
+ for (auto& e : dictionaries)
+ {
+ const auto& keyword = e.keyword();
+
+ if (!e.isDict())
+ {
+ FatalIOErrorInFunction(dictionaries)
+ << "Entries must be specified in dictionary format. Please "
+ << "correct entry " << keyword
+ << exit(FatalIOError);
+ }
+
+ const dictionary& inputDict = e.dict();
+
+ auto* includePtr = inputDict.findEntry("include");
+ auto* excludePtr = inputDict.findEntry("exclude");
+
+ const auto* ePtr = inputDict.findEntry("path");
+
+ if (ePtr)
+ {
+ fileName path(ePtr->stream());
+ path.expand();
+
+ IOobject io(path, time(), IOobject::MUST_READ);
+
+ if (!io.typeHeaderOk(false))
+ {
+ continue;
+ }
+
+ const word oldTypeName = IOdictionary::typeName;
+ const_cast(IOdictionary::typeName) = word::null;
+
+ processDict
+ (
+ out.subDictOrAdd(keyword),
+ IOdictionary(io),
+ includePtr,
+ excludePtr
+ );
+
+ const_cast(IOdictionary::typeName) = oldTypeName;
+
+ dictionaries.remove(keyword);
+ }
+ }
+}
+
+
+void Foam::functionObjects::caseInfo::writeFunctionObjects
+(
+ dictionary& out
+) const
+{
+ for (const auto& fo : functionObjectNames_)
+ {
+ dictionary dict;
+ if (getObjectResultDict(fo, dict))
+ {
+ out.add(fo, dict);
+ }
+ else
+ {
+ report("No result entries found for function object " + fo);
+ }
+ }
+}
+
+
+void Foam::functionObjects::caseInfo::writeMeshStats
+(
+ const polyMesh& mesh,
+ dictionary& dict
+) const
+{
+ dict.add("nGeometricD", mesh.nGeometricD());
+ dict.add("nSolutionD", mesh.nSolutionD());
+
+ const auto& globalData = mesh.globalData();
+
+ dict.add("nPoints", globalData.nTotalPoints());
+ dict.add("nFaces", globalData.nTotalFaces());
+ dict.add("nCells", globalData.nTotalCells());
+
+ dict.add("nPatches", mesh.boundaryMesh().nNonProcessor());
+
+ dict.add("pointZones", mesh.pointZones().names());
+ dict.add("faceZones", mesh.faceZones().names());
+ dict.add("cellZones", mesh.cellZones().names());
+
+ dict.add("boundsMin", mesh.bounds().min());
+ dict.add("boundsMax", mesh.bounds().max());
+
+ dict.add("clouds", mesh.sortedNames());
+}
+
+
+namespace Foam
+{
+ template
+ void addPatchTypeDetails(const fvMesh& mesh, dictionary& dict)
+ {
+ auto objects = mesh.lookupClass();
+ for (const auto* objPtr : objects)
+ {
+ if (objPtr->readOpt() == IOobject::MUST_READ)
+ {
+ const auto& bf = objPtr->boundaryField();
+ dictionary& objDict = dict.subDictOrAdd(objPtr->name());
+
+ for (const auto& pf : bf)
+ {
+ if (!isA(pf.patch()))
+ {
+ objDict.add(pf.patch().name(), pf.type());
+ }
+ }
+ }
+ }
+ }
+
+ template class FieldType>
+ void addPatchDetails(const fvMesh& mesh, dictionary& dict)
+ {
+ addPatchTypeDetails>(mesh, dict);
+ addPatchTypeDetails>(mesh, dict);
+ addPatchTypeDetails>(mesh, dict);
+ addPatchTypeDetails>(mesh, dict);
+ addPatchTypeDetails>(mesh, dict);
+ }
+}
+
+
+void Foam::functionObjects::caseInfo::writePatches
+(
+ const fvMesh& mesh,
+ dictionary& dict
+) const
+{
+ // Geometry
+ dictionary& bnd = dict.subDictOrAdd("types");
+ const auto& pbm = mesh.boundaryMesh();
+ for (const auto& pp : pbm)
+ {
+ if (!isA(pp))
+ {
+ bnd.add(pp.name(), pp.type());
+ }
+ }
+
+ // Fields
+ dictionary& fld = dict.subDictOrAdd("fields");
+ addPatchDetails(mesh, fld);
+ addPatchDetails(mesh, fld);
+ //addPatchDetails(mesh, fld);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::functionObjects::caseInfo::caseInfo
+(
+ const word& name,
+ const Time& runTime,
+ const dictionary& dict
+)
+:
+ stateFunctionObject(name, runTime),
+ writeFile(runTime, name, typeName, dict),
+ writeFormat_(writeFormat::dict),
+ lookupMode_(lookupMode::warn)
+{
+ read(dict);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+bool Foam::functionObjects::caseInfo::read(const dictionary& dict)
+{
+ if (stateFunctionObject::read(dict) && writeFile::read(dict))
+ {
+ writeFormat_ = writeFormatNames_.get("writeFormat", dict);
+ lookupModeNames_.readIfPresent("lookupMode", dict, lookupMode_);
+
+ dictionaries_ = dict.subOrEmptyDict("dictionaries");
+
+ functionObjectNames_ =
+ dict.getOrDefault("functionObjects", wordList());
+
+ return true;
+ }
+
+ return false;
+}
+
+
+bool Foam::functionObjects::caseInfo::execute()
+{
+ return true;
+}
+
+
+bool Foam::functionObjects::caseInfo::write()
+{
+ // Output dictionary
+ dictionary data;
+
+ // Case meta data
+ writeMeta(data.subDictOrAdd("meta"));
+
+ // Note: copying dictionaries
+ // - these are consumed/removed when found to enable checks that all
+ // dictionaries are processed
+ dictionary dicts(dictionaries_);
+
+ dictionary& dataDicts = data.subDictOrAdd("dictionaries");
+
+ // Time-registered dictionaries
+ writeRegisteredDicts(time_, dataDicts, dicts);
+
+ // File-based dictionaries
+ writeFileDicts(dataDicts, dicts);
+
+ // Per-region information
+ const auto meshes = time_.lookupClass();
+ dictionary& regionDict = data.subDictOrAdd("regions");
+ for (const auto& iter : meshes.csorted())
+ {
+ dictionary meshDicts(dicts);
+
+ const fvMesh& mesh = *iter.val();
+
+ const word& name = mesh.name();
+
+ dictionary& out = regionDict.subDictOrAdd(name);
+
+ writeMeshStats(mesh, out.subDictOrAdd("mesh"));
+
+ writePatches(mesh, out.subDictOrAdd("boundary"));
+
+ // Mesh-registered dictionaries
+ writeRegisteredDicts(mesh, out.subDictOrAdd("dictionaries"), meshDicts);
+
+ for (const word& keyword : meshDicts.sortedToc())
+ {
+ report
+ (
+ "Mesh '"
+ + keyword
+ + "' : Unable to process dictionary entry '"
+ + keyword
+ + "'"
+ );
+ }
+ }
+
+
+ writeFunctionObjects(data.subDictOrAdd("functions"));
+
+
+ if (Pstream::master())
+ {
+ auto filePtr = newFileAtTime(name(), time_.value());
+ auto& os = filePtr();
+
+ // Reset stream width - was set in writeFile
+ os.width(0);
+
+ switch (writeFormat_)
+ {
+ case writeFormat::dict:
+ {
+ os << data << endl;
+ break;
+ }
+ case writeFormat::json:
+ {
+ JSONformatter json(os);
+ json.writeDict(data);
+ break;
+ }
+ }
+
+ Info<< "Written " << writeFormatNames_[writeFormat_]
+ << " file: " << os.name() << endl;
+ }
+
+ return true;
+}
+
+
+// ************************************************************************* //
\ No newline at end of file
diff --git a/src/functionObjects/utilities/caseInfo/caseInfo.H b/src/functionObjects/utilities/caseInfo/caseInfo.H
new file mode 100644
index 0000000000..0be7f29294
--- /dev/null
+++ b/src/functionObjects/utilities/caseInfo/caseInfo.H
@@ -0,0 +1,287 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 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::functionObjects::caseInfo
+
+Description
+ Collects and writes case information to file.
+
+ Example of function object specification:
+ \verbatim
+ caseInfo
+ {
+ type caseInfo;
+ libs (utilityFunctionObjects);
+
+ // Warn when entries are not found
+ lookupMode warn; // none | warn | error;
+
+ // Write format
+ writeFormat json; // dictionary | json;
+
+ dictionaries
+ {
+ USolver // User-specified names
+ {
+ // Look up using registered name
+ name "fvSolution";
+
+ // Optionally limit to specific entries
+ include
+ (
+ "solvers/U/solver"
+ );
+ }
+ fvSchemes
+ {
+ name "fvSchemes";
+
+ // include all entries by default
+ }
+ timeScheme
+ {
+ name "fvSchemes";
+
+ include
+ (
+ "/ddtSchemes/default"
+ );
+ }
+
+ turbulence
+ {
+ name "turbulenceProperties";
+
+ // include all entries by default
+ }
+ controlDict
+ {
+ // Look up using file path
+ path "/system/controlDict";
+
+ include
+ (
+ "application"
+ "deltaT"
+ "startTime"
+ "endTime"
+ );
+ }
+ }
+
+ functionObjects (minMax1); // v2306 only
+ }
+ \endverbatim
+
+ Where the entries comprise:
+ \table
+ Property | Description | Required | Default
+ type | Type name: caseInfo | yes |
+ lookupMode | Lookup mode | no | warn
+ writeFormat | Write format | yes |
+ dictionaries | Dictionaries to process | no | \
+ functionObjects | Function objects to process | no | \
+ \endtable
+
+See also
+ Foam::functionObject
+ Foam::timeFunctionObject
+
+SourceFiles
+ caseInfo.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionObjects_caseInfo_H
+#define functionObjects_caseInfo_H
+
+#include "writeFile.H"
+#include "stateFunctionObject.H"
+#include "Enum.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward declarations
+class fvMesh;
+
+namespace functionObjects
+{
+
+/*---------------------------------------------------------------------------*\
+ Class caseInfo Declaration
+\*---------------------------------------------------------------------------*/
+
+class caseInfo
+:
+ public stateFunctionObject,
+ public writeFile
+{
+public:
+
+ // Public enumerations
+
+ //- Write format enumeration
+ enum class writeFormat
+ {
+ dict,
+ json
+ };
+
+ //- Lookup mode enumeration
+ enum class lookupMode
+ {
+ none,
+ warn,
+ error
+ };
+
+
+private:
+
+ // Private Member Data
+
+ //- Write format names
+ static const Enum writeFormatNames_;
+
+ //- Lookup mode names
+ static const Enum lookupModeNames_;
+
+ //- Write/output format, e.g. dictionary, JSON
+ writeFormat writeFormat_;
+
+ //- Lookup mode when reading entries
+ lookupMode lookupMode_;
+
+ //- Dictionaries
+ dictionary dictionaries_;
+
+ //- List of function objects to process
+ wordList functionObjectNames_;
+
+
+ // Private Member Functions
+
+ //- Report
+ void report(const string& str) const;
+
+ //- Process dictionary
+ void processDict
+ (
+ dictionary& dict,
+ const dictionary& targetDict,
+ const entry* includePtr,
+ const entry* excludePtr
+ ) const;
+
+
+protected:
+
+ // Protected Member Functions
+
+ //- No copy construct
+ caseInfo(const caseInfo&) = delete;
+
+ //- No copy assignment
+ void operator=(const caseInfo&) = delete;
+
+
+ // Write data
+
+ //- Write case meta data
+ void writeMeta(dictionary& dict) const;
+
+ //- Write registered dictionaries
+ void writeRegisteredDicts
+ (
+ const objectRegistry& obr,
+ dictionary& dict,
+ dictionary& dictionaries
+ ) const;
+
+ //- Write file-based dictionaries
+ void writeFileDicts
+ (
+ dictionary& dict,
+ dictionary& dictionaries
+ ) const;
+
+ //- Write function object results
+ void writeFunctionObjects(dictionary& dict) const;
+
+ //- Write mesh statistics
+ void writeMeshStats(const polyMesh& mesh, dictionary& dict) const;
+
+ //- Write mesh patches
+ void writePatches(const fvMesh& mesh, dictionary& dict) const;
+
+
+public:
+
+ //- Runtime type information
+ TypeName("caseInfo");
+
+
+ // Constructors
+
+ //- Construct from Time and dictionary
+ caseInfo
+ (
+ const word& name,
+ const Time& runTime,
+ const dictionary& dict
+ );
+
+
+ //- Destructor
+ virtual ~caseInfo() = default;
+
+
+ // Member Functions
+
+ //- Read the controls
+ virtual bool read(const dictionary& dict);
+
+ //- Execute, does nothing
+ virtual bool execute();
+
+ //- Write the caseInfo
+ virtual bool write();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionObjects
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
\ No newline at end of file