diff --git a/src/functionObjects/lagrangian/Make/files b/src/functionObjects/lagrangian/Make/files index e349d4f5454fbc240faf2ceb27ef1a052c0ea176..51e5c3b1128b7d52fb022c2488b0628c5cc3740a 100644 --- a/src/functionObjects/lagrangian/Make/files +++ b/src/functionObjects/lagrangian/Make/files @@ -5,5 +5,6 @@ icoUncoupledKinematicCloud/icoUncoupledKinematicCloud.C dsmcFields/dsmcFields.C vtkCloud/vtkCloud.C +ensightCloud/ensightCloudWriteObject.cxx LIB = $(FOAM_LIBBIN)/liblagrangianFunctionObjects diff --git a/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObject.H b/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObject.H new file mode 100644 index 0000000000000000000000000000000000000000..ea10e6bcd748ef0258ccea902eac08d0f705690f --- /dev/null +++ b/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObject.H @@ -0,0 +1,266 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2024 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::functionObjects::ensightCloudWriteObject + +Group + grpLagrangianFunctionObjects + +Description + This functionObject writes cloud(s) in ensight format + + Example of function object specification: + \verbatim + cloudWrite1 + { + type ensightCloud; + libs (lagrangianFunctionObjects); + writeControl writeTime; + writeInterval 1; + format ascii; + + timeFormat scientific; + timePrecision 5; + + cloud myCloud; + fields (T U rho); + width 4; // file-padding + + selection + { + stride + { + // every 10th parcelId + action add; + source stride; + stride 10; + } + Umin + { + // Remove slow parcels + action subtract; + source field; + field U; + accept (less 1e-3); + } + diam + { + // Only particular diameter ranges + action subset; + source field; + field d; + accept (greater 1e-3) and (less 1e-3); + } + } + } + \endverbatim + + \heading Basic Usage + \table + Property | Description | Required | Default + type | Type name: ensightCloud | yes | + clouds | List of clouds (name or regex) | no | + cloud | Cloud name | no | + fields | List of fields (name or regex) | no | + selection | Parcel selection control | no | empty-dict + \endtable + + \heading Output Options + \table + Property | Description | Required | Default + format | Format as ascii or binary | no | binary + width | Mask width for \c data/XXXX | no | 8 + directory | The output directory name | no | postProcessing/NAME + overwrite | Remove existing directory | no | false + consecutive | Consecutive output numbering | no | false + width | Padding width for file name | no | 8 + prune | Suppress writing of empty clouds | no | false + timeFormat | Time format (ensight case) | no | scientific + timePrecision | Time precision (ensight case) | no | 5 + writeControl | Output control | recommended | timeStep + \endtable + + The output filename and fields are added to the functionObjectProperties + information. For the previous example specification: + + \verbatim + cloudWrite1 + { + myCloud + { + file "<case>/simulation.case"; + fields (T U rho); + } + } + \endverbatim + +Note + The selection dictionary can be used for finer control of the parcel + output. It contains a set of (add,subtract,subset,clear,invert) + selection actions and sources. + Omitting the selection dictionary is the same as specifying the + conversion of all parcels (in the selected clouds). + More syntax details are to be found in the corresponding + Foam::Detail::parcelSelection class. + +See also + Foam::Detail::parcelSelection + Foam::functionObjects::vtkCloud + Foam::functionObjects::ensightWrite + Foam::functionObjects::fvMeshFunctionObject + Foam::functionObjects::timeControl + +SourceFiles + ensightCloudWriteObject.cxx + ensightCloudWriteObjectImpl.cxx + +\*---------------------------------------------------------------------------*/ + +#ifndef functionObjects_ensightCloudWriteObject_H +#define functionObjects_ensightCloudWriteObject_H + +#include "fvMeshFunctionObject.H" +#include "ensightCase.H" +#include "globalIndex.H" +#include "parcelSelectionDetail.H" +#include "wordRes.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ +namespace functionObjects +{ + +/*---------------------------------------------------------------------------*\ + Class ensightCloudWriteObject Declaration +\*---------------------------------------------------------------------------*/ + +class ensightCloudWriteObject +: + public fvMeshFunctionObject, + public Foam::Detail::parcelSelection +{ + // Private Data + + //- Ensight output options + ensightCase::options caseOpts_; + + //- Output directory + fileName outputDir_; + + //- Consecutive output numbering + bool consecutive_; + + //- Suppress writing of empty clouds + bool pruneEmpty_; + + //- Apply output filter (for the current cloud) + bool applyFilter_; + + //- Sizing of selected parcels (including any filtering) + globalIndex procAddr_; + + //- Requested names of clouds to process + wordRes selectClouds_; + + //- Subset of cloud fields to process + wordRes selectFields_; + + //- Ensight case handler + autoPtr<ensightCase> ensCase_; + + + // Private Member Functions + + //- Ensight case handler + ensightCase& ensCase() { return *ensCase_; } + + //- Write a cloud to disk (creates parent directory), + //- and record on the cloud OutputProperties. + // \param file is the output file name, with extension. + bool writeCloud(const word& cloudName); + + //- Write fields of IOField<Type> + template<class Type> + wordList writeFields + ( + const word& cloudName, + const objectRegistry& obrTmp + ); + + + //- No copy construct + ensightCloudWriteObject(const ensightCloudWriteObject&) = delete; + + //- No copy assignment + void operator=(const ensightCloudWriteObject&) = delete; + + +public: + + //- Runtime type information + TypeName("ensightCloud"); + + + // Constructors + + //- Construct from Time and dictionary + ensightCloudWriteObject + ( + const word& name, + const Time& runTime, + const dictionary& dict + ); + + + //- Destructor + virtual ~ensightCloudWriteObject() = default; + + + // Member Functions + + //- Read the ensightCloud specification + virtual bool read(const dictionary& dict); + + //- Execute, currently does nothing + virtual bool execute(); + + //- Write fields + virtual bool write(); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace functionObjects +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // diff --git a/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObject.cxx b/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObject.cxx new file mode 100644 index 0000000000000000000000000000000000000000..01b6cdb083dd0f66fa80817d51961c3e819ff54a --- /dev/null +++ b/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObject.cxx @@ -0,0 +1,425 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2024 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 "ensightCloudWriteObject.H" +#include "ensightCells.H" +#include "Cloud.H" +#include "dictionary.H" +#include "fvMesh.H" +#include "ensightOutputCloud.H" +#include "addToRunTimeSelectionTable.H" +#include "pointList.H" +#include "stringOps.H" + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +namespace Foam +{ +namespace functionObjects +{ + defineTypeNameAndDebug(ensightCloudWriteObject, 0); + + addToRunTimeSelectionTable + ( + functionObject, + ensightCloudWriteObject, + dictionary + ); +} +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +// Implementation +#include "ensightCloudWriteObjectImpl.cxx" + + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +bool Foam::functionObjects::ensightCloudWriteObject::writeCloud +( + const word& cloudName +) +{ + applyFilter_ = false; + procAddr_.clear(); + + const auto* cloudPtr = mesh_.cfindObject<cloud>(cloudName); + if (!cloudPtr) + { + return false; + } + + const auto& currCloud = *cloudPtr; + + objectRegistry obrTmp + ( + IOobject + ( + "ensight::ensightCloud::" + cloudName, + mesh_.time().constant(), + mesh_, + IOobject::NO_READ, + IOobject::NO_WRITE, + IOobject::NO_REGISTER + ) + ); + + currCloud.writeObjects(obrTmp); + + const auto* pointsPtr = cloud::findIOPosition(obrTmp); + + if (!pointsPtr) + { + // This should be impossible + return false; + } + + applyFilter_ = calculateFilter(obrTmp, log); + Pstream::reduceOr(applyFilter_); + + // Number of parcels (locally) + const label nParcels = + ( + applyFilter_ ? parcelAddr_.count() : pointsPtr->size() + ); + + // Gather sizes (offsets irrelevant) + procAddr_.reset(globalIndex::gatherOnly{}, nParcels); + + bool noCloud(!procAddr_.totalSize()); + Pstream::broadcast(noCloud); + + if (applyFilter_) + { + // Report filtered/unfiltered count + Log << "After filtering using " + << procAddr_.totalSize() << '/' + << (returnReduce(pointsPtr->size(), sumOp<label>())) + << " parcels" << nl; + } + + if (pruneEmpty_ && noCloud) + { + return false; + } + + + // Copy positions (for simplicity and for filtering). + // Store as floatVector, since that is what Ensight will write anyhow + + DynamicList<floatVector> positions; + positions.reserve(UPstream::master() ? procAddr_.maxSize() : nParcels); + + { + const auto& points = *pointsPtr; + + positions.resize_nocopy(nParcels); + + auto iter = positions.begin(); + + if (applyFilter_) + { + if (std::is_same<float, vector::cmptType>::value) + { + for (const label idx : parcelAddr_) + { + *iter = points[idx]; + ++iter; + } + } + else + { + for (const label idx : parcelAddr_) + { + const auto& pos = points[idx]; + + (*iter).x() = narrowFloat(pos.x()); + (*iter).y() = narrowFloat(pos.y()); + (*iter).z() = narrowFloat(pos.z()); + ++iter; + } + } + } + else + { + if (std::is_same<float, vector::cmptType>::value) + { + for (const auto& pos : points) + { + *iter = pos; + ++iter; + } + } + else + { + for (const auto& pos : points) + { + (*iter).x() = narrowFloat(pos.x()); + (*iter).y() = narrowFloat(pos.y()); + (*iter).z() = narrowFloat(pos.z()); + ++iter; + } + } + } + } + + + // Write positions + { + autoPtr<ensightFile> os = ensCase().newCloud(cloudName); + + ensightOutput::writeCloudPositions + ( + os.ref(), + positions, + procAddr_ + ); + } + + // Prevent any possible conversion of positions as a field + obrTmp.filterKeys + ( + [](const word& k) + { + return k.starts_with("position") || k.starts_with("coordinate"); + }, + true // prune + ); + + + // Write fields + + DynamicList<word> written(obrTmp.size() + currCloud.objectRegistry::size()); + + written.push_back + ( + writeFields<label>(cloudName, obrTmp) + ); + written.push_back + ( + writeFields<scalar>(cloudName, obrTmp) + ); + written.push_back + ( + writeFields<vector>(cloudName, obrTmp) + ); + + // Any cloudFunctions results + written.push_back + ( + writeFields<scalar>(cloudName, currCloud) + ); + + // Record information into the state (all processors) + // + // foName + // { + // cloudName + // { + // file "<case>/postProcessing/name/casename.case"; + // fields (U T rho); + // } + // } + + const fileName& file = ensCase().path(); + + // Case-local file name with "<case>" to make relocatable + dictionary propsDict; + propsDict.add + ( + "file", + time_.relativePath(file, true) + ); + propsDict.add("fields", written); + + setObjectProperty(name(), cloudName, propsDict); + + return true; +} + + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::functionObjects::ensightCloudWriteObject::ensightCloudWriteObject +( + const word& name, + const Time& runTime, + const dictionary& dict +) +: + fvMeshFunctionObject(name, runTime, dict), + caseOpts_("format", dict, IOstreamOption::BINARY), + outputDir_(), + consecutive_(false), + pruneEmpty_(false), + applyFilter_(false), + procAddr_() +{ + // May still want this? + // if (postProcess) + // { + // // Disable for post-process mode. + // // Emit as FatalError for the try/catch in the caller. + // FatalError + // << type() << " disabled in post-process mode" + // << exit(FatalError); + // } + + read(dict); +} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +bool Foam::functionObjects::ensightCloudWriteObject::read +( + const dictionary& dict +) +{ + fvMeshFunctionObject::read(dict); + + // Case/writer options + consecutive_ = dict.getOrDefault("consecutive", false); + + caseOpts_.width(dict.getOrDefault<label>("width", 8)); + caseOpts_.overwrite(dict.getOrDefault("overwrite", false)); + + caseOpts_.timeFormat("timeFormat", dict); + caseOpts_.timePrecision("timePrecision", dict); + + + pruneEmpty_ = dict.getOrDefault("prune", false); + + selectClouds_.clear(); + dict.readIfPresent("clouds", selectClouds_); + selectClouds_.uniq(); + if (selectClouds_.empty()) + { + word cloudName; + if (dict.readIfPresent("cloud", cloudName)) + { + selectClouds_.push_back(std::move(cloudName)); + } + } + + selectFields_.clear(); + dict.readIfPresent("fields", selectFields_); + selectFields_.uniq(); + + // Actions to define selection + parcelSelect_ = dict.subOrEmptyDict("selection"); + + + // Output directory + + outputDir_.clear(); + dict.readIfPresent("directory", outputDir_); + + if (outputDir_.size()) + { + // User-defined output directory + outputDir_.expand(); + if (!outputDir_.isAbsolute()) + { + outputDir_ = time_.globalPath()/outputDir_; + } + } + else + { + // Standard postProcessing/ naming + outputDir_ = time_.globalPath()/functionObject::outputPrefix/name(); + } + outputDir_.clean(); // Remove unneeded ".." + + return true; +} + + +bool Foam::functionObjects::ensightCloudWriteObject::execute() +{ + return true; +} + + +bool Foam::functionObjects::ensightCloudWriteObject::write() +{ + const wordList cloudNames + ( + selectClouds_.empty() + ? mesh_.sortedNames<cloud>() + : mesh_.sortedNames<cloud>(selectClouds_) + ); + + if (cloudNames.empty()) + { + return true; // skip - nothing available + } + + if (!ensCase_) + { + ensCase_.reset + ( + new ensightCase(outputDir_, time_.globalCaseName(), caseOpts_) + ); + + // Generate a (non-moving) dummy geometry + // - ParaView ensight-reader needs this, and usually ensight does too + autoPtr<ensightGeoFile> os = ensCase().newGeometry(false); + ensightCells::writeBox(os.ref(), mesh_.bounds()); + } + + if (consecutive_) + { + ensCase().nextTime(time_.value()); + } + else + { + ensCase().setTime(time_.value(), time_.timeIndex()); + } + + Log << type() << ' ' << name() << " write" << nl; + + // Each cloud separately + for (const word& cloudName : cloudNames) + { + // writeCloud() includes mkDir (on master) + + if (writeCloud(cloudName)) + { + Log << " cloud : " << endl; + } + } + + ensCase().write(); // Flush case information + + return true; +} + + +// ************************************************************************* // diff --git a/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObjectImpl.cxx b/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObjectImpl.cxx new file mode 100644 index 0000000000000000000000000000000000000000..01fcf08b14891f9d8347309bd2db1ebf6663e88f --- /dev/null +++ b/src/functionObjects/lagrangian/ensightCloud/ensightCloudWriteObjectImpl.cxx @@ -0,0 +1,106 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2024 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 "IOField.H" +#include "ensightOutputCloud.H" + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +template<class Type> +Foam::wordList Foam::functionObjects::ensightCloudWriteObject::writeFields +( + const word& cloudName, + const objectRegistry& obrTmp +) +{ + static_assert + ( + ( + std::is_same<label, typename pTraits<Type>::cmptType>::value + || std::is_floating_point<typename pTraits<Type>::cmptType>::value + ), + "Label and Floating-point vector space only" + ); + + // Other integral types (eg, bool etc) would need cast/convert to label. + // Similarly for labelVector etc. + + + // Fields are not always on all processors (eg, multi-component parcels). + // Thus need to resolve names between all processors. + + wordList fieldNames = + ( + selectFields_.size() + ? obrTmp.names<IOField<Type>>(selectFields_) + : obrTmp.names<IOField<Type>>() + ); + + Pstream::combineReduce(fieldNames, ListOps::uniqueEqOp<word>()); + Foam::sort(fieldNames); // Consistent order + + DynamicList<Type> scratch; + + for (const word& fieldName : fieldNames) + { + const List<Type>* fldPtr = obrTmp.findObject<IOField<Type>>(fieldName); + const List<Type>& values = (fldPtr ? *fldPtr : List<Type>::null()); + + autoPtr<ensightFile> os = + ensCase().newCloudData<Type>(cloudName, fieldName); + + if (applyFilter_) + { + scratch.resize_nocopy(parcelAddr_.count()); + + auto iter = scratch.begin(); + + for (const label idx : parcelAddr_) + { + *iter = values[idx]; + ++iter; + } + + // TBD: + // recalculate globalIndex instead of relying on procAddr_ ? + + ensightOutput::writeCloudField(os.ref(), scratch, procAddr_); + } + else + { + // TBD: + // recalculate globalIndex instead of relying on procAddr_ ? + + ensightOutput::writeCloudField(os.ref(), values, procAddr_); + } + } + + return fieldNames; +} + + +// ************************************************************************* //