ENH: improvements for vtkWrite function object (issue #926)

- parallel output.

  The output is now postProcessing/<name> for similar reasoning as
  mentioned in #866 - better alignment with other function objects, no
  collision with foamToVTK output.

- align the input parameters with those of vtkCloud so that we can
  specify the ASCII precision and the padding width for the output
  file names as well.

- emit TimeValue field, support file series generation

- support internal or boundary meshes, combining the result into a vtm
  file.

- can restrict conversion based on zone names, enclosing volumes,
  bounding box
This commit is contained in:
Mark Olesen
2018-10-09 15:52:52 +02:00
parent 3e75a3736b
commit 42bb497084
8 changed files with 1358 additions and 176 deletions

View File

@ -3,6 +3,7 @@ abort/abort.C
codedFunctionObject/codedFunctionObject.C codedFunctionObject/codedFunctionObject.C
ensightWrite/ensightWrite.C ensightWrite/ensightWrite.C
vtkWrite/vtkWrite.C vtkWrite/vtkWrite.C
vtkWrite/vtkWriteUpdate.C
removeRegisteredObject/removeRegisteredObject.C removeRegisteredObject/removeRegisteredObject.C

View File

@ -27,7 +27,11 @@ License
#include "dictionary.H" #include "dictionary.H"
#include "Time.H" #include "Time.H"
#include "areaFields.H" #include "areaFields.H"
#include "stringListOps.H"
#include "foamVtkInternalWriter.H" #include "foamVtkInternalWriter.H"
#include "foamVtkPatchWriter.H"
#include "foamVtkSeriesWriter.H"
#include "foamVtmWriter.H"
#include "addToRunTimeSelectionTable.H" #include "addToRunTimeSelectionTable.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@ -42,6 +46,73 @@ namespace functionObjects
} }
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
Foam::label Foam::functionObjects::vtkWrite::writeAllVolFields
(
autoPtr<vtk::internalWriter>& internalWriter,
UPtrList<vtk::patchWriter>& patchWriters,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const
{
#undef vtkWrite_WRITE_FIELD
#define vtkWrite_WRITE_FIELD(FieldType) \
writeVolFields<FieldType> \
( \
internalWriter, \
patchWriters, \
proxy, \
acceptField \
)
label count = 0;
count += vtkWrite_WRITE_FIELD(volScalarField);
count += vtkWrite_WRITE_FIELD(volVectorField);
count += vtkWrite_WRITE_FIELD(volSphericalTensorField);
count += vtkWrite_WRITE_FIELD(volSymmTensorField);
count += vtkWrite_WRITE_FIELD(volTensorField);
#undef vtkWrite_WRITE_FIELD
return count;
}
Foam::label Foam::functionObjects::vtkWrite::writeAllVolFields
(
autoPtr<vtk::internalWriter>& internalWriter,
const autoPtr<volPointInterpolation>& pInterp,
UPtrList<vtk::patchWriter>& patchWriters,
const UPtrList<PrimitivePatchInterpolation<primitivePatch>>& patchInterps,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const
{
#undef vtkWrite_WRITE_FIELD
#define vtkWrite_WRITE_FIELD(FieldType) \
writeVolFields<FieldType> \
( \
internalWriter, pInterp, \
patchWriters, patchInterps, \
proxy, \
acceptField \
)
label count = 0;
count += vtkWrite_WRITE_FIELD(volScalarField);
count += vtkWrite_WRITE_FIELD(volVectorField);
count += vtkWrite_WRITE_FIELD(volSphericalTensorField);
count += vtkWrite_WRITE_FIELD(volSymmTensorField);
count += vtkWrite_WRITE_FIELD(volTensorField);
#undef vtkWrite_WRITE_FIELD
return count;
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::functionObjects::vtkWrite::vtkWrite Foam::functionObjects::vtkWrite::vtkWrite
@ -51,19 +122,37 @@ Foam::functionObjects::vtkWrite::vtkWrite
const dictionary& dict const dictionary& dict
) )
: :
fvMeshFunctionObject(name, runTime, dict), functionObject(name),
time_(runTime),
outputDir_(),
printf_(),
writeOpts_(vtk::formatType::INLINE_BASE64), writeOpts_(vtk::formatType::INLINE_BASE64),
verbose_(true),
doInternal_(true),
doBoundary_(true),
oneBoundary_(false),
interpolate_(false),
decompose_(false),
writeIds_(false),
meshState_(polyMesh::TOPO_CHANGE),
selectRegions_(),
selectPatches_(),
selectFields_(), selectFields_(),
dirName_("VTK") selection_(),
meshes_(),
meshSubsets_(),
vtuMappings_(),
series_()
{ {
if (postProcess) // May still want this? (OCT-2018)
{ // if (postProcess)
// Disable for post-process mode. // {
// Emit as FatalError for the try/catch in the caller. // // Disable for post-process mode.
FatalError // // Emit as FatalError for the try/catch in the caller.
<< type() << " disabled in post-process mode" // FatalError
<< exit(FatalError); // << type() << " disabled in post-process mode"
} // << exit(FatalError);
// }
read(dict); read(dict);
} }
@ -73,16 +162,23 @@ Foam::functionObjects::vtkWrite::vtkWrite
bool Foam::functionObjects::vtkWrite::read(const dictionary& dict) bool Foam::functionObjects::vtkWrite::read(const dictionary& dict)
{ {
fvMeshFunctionObject::read(dict); functionObject::read(dict);
readSelection(dict);
// We probably cannot trust old information after a reread
series_.clear();
// verbose_ = dict.lookupOrDefault<bool>("verbose", true);
doInternal_ = dict.lookupOrDefault<bool>("internal", true);
doBoundary_ = dict.lookupOrDefault<bool>("boundary", true);
oneBoundary_ = dict.lookupOrDefault<bool>("single", false);
interpolate_ = dict.lookupOrDefault<bool>("interpolate", false);
// //
// writer options - default is xml base64 // Writer options - default is xml base64
// //
writeOpts_ = vtk::formatType::INLINE_BASE64; writeOpts_ = vtk::formatType::INLINE_BASE64;
if (dict.lookupOrDefault("legacy", false))
{
writeOpts_.legacy(true);
}
writeOpts_.ascii writeOpts_.ascii
( (
@ -90,33 +186,63 @@ bool Foam::functionObjects::vtkWrite::read(const dictionary& dict)
&& (IOstream::formatEnum(dict.get<word>("format")) == IOstream::ASCII) && (IOstream::formatEnum(dict.get<word>("format")) == IOstream::ASCII)
); );
// FUTURE? if (dict.lookupOrDefault("legacy", false))
// writeOpts_.precision {
// ( writeOpts_.legacy(true);
// dict.lookupOrDefault }
// (
// "writePrecision", writeOpts_.precision
// IOstream::defaultPrecision() (
// ) dict.lookupOrDefault
// ); (
"precision",
IOstream::defaultPrecision()
)
);
// Info<< type() << " " << name() << " output-format: " // Info<< type() << " " << name() << " output-format: "
// << writeOpts_.description() << nl; // << writeOpts_.description() << nl;
const int padWidth = dict.lookupOrDefault<int>("width", 8);
// Appropriate printf format - Enforce min/max sanity limits
if (padWidth < 1 || padWidth > 31)
{
printf_.clear();
}
else
{
printf_ = "%0" + std::to_string(padWidth) + "d";
}
// //
// other options // Other options
// //
dict.readIfPresent("directory", dirName_);
decompose_ = dict.lookupOrDefault("decompose", false); decompose_ = dict.lookupOrDefault("decompose", false);
writeIds_ = dict.lookupOrDefault("writeIds", false); writeIds_ = dict.lookupOrDefault("writeIds", false);
// // Output directory
// output fields
// outputDir_.clear();
dict.readEntry("fields", selectFields_); dict.readIfPresent("directory", outputDir_);
selectFields_.uniq();
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();
return true; return true;
} }
@ -133,96 +259,515 @@ bool Foam::functionObjects::vtkWrite::write()
// const word timeDesc = // const word timeDesc =
// useTimeName ? time_.timeName() : Foam::name(time_.timeIndex()); // useTimeName ? time_.timeName() : Foam::name(time_.timeIndex());
const word timeDesc = time_.timeName(); const word timeDesc = "_" +
(
printf_.empty()
? Foam::name(time_.timeIndex())
: word::printf(printf_, time_.timeIndex())
);
fileName vtkDir = dirName_; const scalar timeValue = time_.value();
if (!vtkDir.isAbsolute())
update();
if (meshes_.empty() || (!doInternal_ && !doBoundary_))
{ {
vtkDir = time_.path()/vtkDir; // Skip
return true;
} }
mkDir(vtkDir);
string vtkName = time_.caseName();
if (Pstream::parRun()) fileName vtkName = time_.globalCaseName();
vtk::vtmWriter vtmMultiRegion;
Info<< name() << " output Time: " << time_.timeName() << nl;
label regioni = 0;
for (const word& regionName : meshes_.sortedToc())
{ {
// Strip off leading casename, leaving just processor_DDD ending. fileName regionPrefix;
const auto i = vtkName.rfind("processor"); if (regionName != polyMesh::defaultRegion)
if (i != string::npos)
{ {
vtkName = vtkName.substr(i); regionPrefix = regionName;
} }
}
// internal mesh auto& meshProxy = meshSubsets_[regioni];
{ auto& vtuMeshCells = vtuMappings_[regioni];
const fileName outputName ++regioni;
const fvMesh& baseMesh = meshProxy.baseMesh();
wordHashSet acceptField(baseMesh.names<void>(selectFields_));
// Prune restart fields
acceptField.filterKeys
( (
vtkDir/vtkName [](const word& k){ return k.endsWith("_0"); },
+ "_" true // prune
+ timeDesc
); );
Info<< name() << " output Time: " << time_.timeName() << nl const label nVolFields =
<< " Internal : " << outputName << endl; (
(doInternal_ || doBoundary_)
// Number of fields to be written: only needed for legacy vtk format ? baseMesh.count
label nVolFields = 0;
if (writeOpts_.legacy())
{
nVolFields =
( (
(writeIds_ ? 1 : 0) stringListOps::foundOp<word>(fieldTypes::volume),
+ countFields<volScalarField>() acceptField
+ countFields<volVectorField>() )
+ countFields<volSphericalTensorField>() : 0
+ countFields<volSymmTensorField>() );
+ countFields<volTensorField>()
// Undecided if we want to automatically support DimensionedFields
// or only on demand:
const label nDimFields = 0;
// (
// (doInternal_ || doBoundary_)
// ? baseMesh.count
// (
// stringListOps::foundOp<word>(fieldTypes::internal),
// acceptField
// )
// : 0
// );
// Setup for the vtm writer.
// For legacy format, the information added is simply ignored.
fileName vtmOutputBase
(
outputDir_/regionPrefix/vtkName + timeDesc
);
// Combined internal + boundary in a vtm file
vtk::vtmWriter vtmWriter;
// Collect individual boundaries into a vtm file
vtk::vtmWriter vtmBoundaries;
// Setup the internal writer
autoPtr<vtk::internalWriter> internalWriter;
// Interpolator for volume and dimensioned fields
autoPtr<volPointInterpolation> pInterp;
if (doInternal_)
{
if (interpolate_)
{
pInterp.reset(new volPointInterpolation(meshProxy.mesh()));
}
if (vtuMeshCells.empty())
{
// Use the appropriate mesh (baseMesh or subMesh)
vtuMeshCells.reset(meshProxy.mesh());
}
internalWriter = autoPtr<vtk::internalWriter>::New
(
meshProxy.mesh(),
vtuMeshCells,
writeOpts_,
// Output name for internal
(
writeOpts_.legacy()
? vtmOutputBase
: (vtmOutputBase / "internal")
),
Pstream::parRun()
); );
Info<< " Internal : "
<< internalWriter->output().relative(time_.globalPath())
<< endl;
// No sub-block for internal
vtmWriter.append_vtu
(
"internal",
vtmOutputBase.name()/"internal"
);
internalWriter->writeTimeValue(timeValue);
internalWriter->writeGeometry();
} }
vtk::vtuCells vtuMeshCells
(
mesh_,
writeOpts_,
decompose_
);
// Write mesh // Setup the patch writers
vtk::internalWriter writer
( const polyBoundaryMesh& patches = meshProxy.mesh().boundaryMesh();
mesh_,
vtuMeshCells, PtrList<vtk::patchWriter> patchWriters;
outputName, PtrList<PrimitivePatchInterpolation<primitivePatch>> patchInterps;
writeOpts_
); labelList patchIds;
if (doBoundary_)
{
patchIds = getSelectedPatches(patches);
}
if (oneBoundary_ && patchIds.size())
{
auto writer = autoPtr<vtk::patchWriter>::New
(
meshProxy.mesh(),
patchIds,
writeOpts_,
// Output name for one patch: "boundary"
(
writeOpts_.legacy()
? (outputDir_/regionPrefix/"boundary"/"boundary" + timeDesc)
: (vtmOutputBase / "boundary")
),
Pstream::parRun()
);
// No sub-block for one-patch
vtmWriter.append_vtp
(
"boundary",
vtmOutputBase.name()/"boundary"
);
Info<< " Boundaries: "
<< writer->output().relative(time_.globalPath()) << nl;
writer->writeTimeValue(timeValue);
writer->writeGeometry();
// Transfer writer to list for later use
patchWriters.resize(1);
patchWriters.set(0, writer);
// Avoid patchInterpolation for each sub-patch
patchInterps.resize(1); // == nullptr
}
else if (patchIds.size())
{
patchWriters.resize(patchIds.size());
if (interpolate_)
{
patchInterps.resize(patchIds.size());
}
label nPatchWriters = 0;
label nPatchInterps = 0;
for (const label patchId : patchIds)
{
const polyPatch& pp = patches[patchId];
auto writer = autoPtr<vtk::patchWriter>::New
(
meshProxy.mesh(),
labelList(one(), pp.index()),
writeOpts_,
// Output name for patch: "boundary"/name
(
writeOpts_.legacy()
?
(
outputDir_/regionPrefix/pp.name()
/ (pp.name()) + timeDesc
)
: (vtmOutputBase / "boundary" / pp.name())
),
Pstream::parRun()
);
if (!nPatchWriters)
{
vtmWriter.beginBlock("boundary");
vtmBoundaries.beginBlock("boundary");
}
vtmWriter.append_vtp
(
pp.name(),
vtmOutputBase.name()/"boundary"/pp.name()
);
vtmBoundaries.append_vtp
(
pp.name(),
"boundary"/pp.name()
);
Info<< " Boundary : "
<< writer->output().relative(time_.globalPath()) << nl;
writer->writeTimeValue(timeValue);
writer->writeGeometry();
// Transfer writer to list for later use
patchWriters.set(nPatchWriters++, writer);
if (patchInterps.size())
{
patchInterps.set
(
nPatchInterps++,
new PrimitivePatchInterpolation<primitivePatch>(pp)
);
}
}
if (nPatchWriters)
{
vtmWriter.endBlock("boundary");
vtmBoundaries.endBlock("boundary");
}
patchWriters.resize(nPatchWriters);
patchInterps.resize(nPatchInterps);
}
// CellData // CellData
{ {
writer.beginCellData(nVolFields); if (internalWriter.valid())
// Write cellID field
if (writeIds_)
{ {
writer.writeCellIDs(); // cellIds + procIds (parallel)
internalWriter->beginCellData
(
(writeIds_ ? 1 + (internalWriter->parallel() ? 1 : 0) : 0)
+ (internalWriter->parallel() ? 1 : 0)
+ nVolFields + nDimFields
);
// Write cellID field + procID (parallel only)
if (writeIds_)
{
internalWriter->writeCellIDs();
internalWriter->writeProcIDs(); // parallel only
}
} }
// Write volFields if (nVolFields)
writeFields<volScalarField>(writer); {
writeFields<volVectorField>(writer); for (vtk::patchWriter& writer : patchWriters)
writeFields<volSphericalTensorField>(writer); {
writeFields<volSymmTensorField>(writer); writer.beginCellData
writeFields<volTensorField>(writer); (
(writeIds_ ? 1 : 0)
+ nVolFields
);
if (writeIds_)
{
writer.writePatchIDs();
}
}
}
writer.endCellData(); writeAllVolFields
(
internalWriter,
patchWriters,
meshProxy,
acceptField
);
// writeAllDimFields
// (
// internalWriter,
// meshProxy,
// acceptField
// );
// End CellData is implicit
} }
writer.writeFooter();
// PointData
// - only construct pointMesh on request since it constructs
// edge addressing
if (interpolate_)
{
// Begin PointData
if (internalWriter.valid())
{
internalWriter->beginPointData
(
nVolFields + nDimFields
);
}
forAll(patchWriters, writeri)
{
const label nPatchFields =
(
writeri < patchInterps.size() && patchInterps.set(writeri)
? nVolFields
: 0
);
if (nPatchFields)
{
patchWriters[writeri].beginPointData(nPatchFields);
}
}
writeAllVolFields
(
internalWriter, pInterp,
patchWriters, patchInterps,
meshProxy,
acceptField
);
// writeAllDimFields
// (
// internalWriter, pInterp,
// meshProxy,
// acceptField
// );
// writeAllPointFields
// (
// internalWriter,
// patchWriters,
// meshProxy,
// acceptField
// );
// End PointData is implicit
}
// Finish writers
if (internalWriter.valid())
{
internalWriter->close();
}
for (vtk::patchWriter& writer : patchWriters)
{
writer.close();
}
pInterp.clear();
patchWriters.clear();
patchInterps.clear();
// Collective output
if (Pstream::master())
{
// Naming for vtm, file series etc.
fileName outputName(vtmOutputBase);
if (writeOpts_.legacy())
{
if (doInternal_)
{
// Add to file-series and emit as JSON
outputName.ext(vtk::legacy::fileExtension);
fileName seriesName(vtk::seriesWriter::base(outputName));
vtk::seriesWriter& series = series_(seriesName);
// First time?
// Load from file, verify against filesystem,
// prune time >= currentTime
if (series.empty())
{
series.load(seriesName, true, timeValue);
}
series.append(timeValue, timeDesc);
series.write(seriesName);
}
}
else
{
if (vtmWriter.size())
{
// Emit ".vtm"
outputName.ext(vtmWriter.ext());
vtmWriter.setTime(timeValue);
vtmWriter.write(outputName);
fileName seriesName(vtk::seriesWriter::base(outputName));
vtk::seriesWriter& series = series_(seriesName);
// First time?
// Load from file, verify against filesystem,
// prune time >= currentTime
if (series.empty())
{
series.load(seriesName, true, timeValue);
}
series.append(timeValue, outputName);
series.write(seriesName);
// Add to multi-region vtm
vtmMultiRegion.add(regionName, regionPrefix, vtmWriter);
}
if (vtmBoundaries.size())
{
// Emit "boundary.vtm" with collection of boundaries
// Naming for vtm
fileName outputName(vtmOutputBase / "boundary");
outputName.ext(vtmBoundaries.ext());
vtmBoundaries.setTime(timeValue);
vtmBoundaries.write(outputName);
}
}
}
}
// Emit multi-region vtm
if (Pstream::master() && meshes_.size() > 1)
{
fileName outputName
(
outputDir_/vtkName + "-regions" + timeDesc + ".vtm"
);
vtmMultiRegion.setTime(timeValue);
vtmMultiRegion.write(outputName);
fileName seriesName(vtk::seriesWriter::base(outputName));
vtk::seriesWriter& series = series_(seriesName);
// First time?
// Load from file, verify against filesystem,
// prune time >= currentTime
if (series.empty())
{
series.load(seriesName, true, timeValue);
}
series.append(timeValue, outputName);
series.write(seriesName);
} }
return true; return true;
} }
bool Foam::functionObjects::vtkWrite::end()
{
meshSubsets_.clear();
vtuMappings_.clear();
meshes_.clear();
return true;
}
// ************************************************************************* // // ************************************************************************* //

View File

@ -2,8 +2,8 @@
========= | ========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | \\ / O peration |
\\ / A nd | Copyright (C) 2016 OpenFOAM Foundation \\ / A nd | Copyright (C) 2017-2018 OpenCFD Ltd.
\\/ M anipulation | Copyright (C) 2017-2018 OpenCFD Ltd. \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
This file is part of OpenFOAM. This file is part of OpenFOAM.
@ -28,11 +28,8 @@ Group
grpUtilitiesFunctionObjects grpUtilitiesFunctionObjects
Description Description
This functionObject writes objects registered to the database in VTK format. Writes fields in VTK (xml or legacy) format.
Writes cell-values or point-interpolated values for volFields.
Currently only supports writing of the cell-values of volFields but
support for other field types, patch fields, Lagrangian data etc. will be
added.
Example of function object specification: Example of function object specification:
\verbatim \verbatim
@ -47,22 +44,80 @@ Description
decompose false; decompose false;
... ...
fields (U p); fields (U p);
selection
{
box
{
action add;
source box;
box (-0.1 -0.01 -0.1) (0.1 0.30 0.1);
}
dome
{
action add;
shape sphere;
origin (-0.1 -0.01 -0.1);
radius 0.25;
}
centre
{
action subtract;
source sphere;
origin (-0.1 -0.01 -0.1);
radius 0.1;
}
blob
{
action add;
source surface;
surface triSurfaceMesh;
name blob.stl;
}
}
} }
\endverbatim \endverbatim
Usage \heading Basic Usage
\table \table
Property | Description | Required | Default Property | Description | Required | Default
type | Type name: vtkWrite | yes | type | Type name: vtkWrite | yes |
fields | Fields to output | yes | fields | Fields to output (wordRe list) | yes |
writeControl | Output control | recommended | timeStep boundary | Convert boundary fields | no | true
directory | The output directory name | no | "VTK" internal | Convert internal fields | no | true
format | ASCII or binary format | no | binary single | Combine patches into a single boundary | no | false
legacy | Legacy VTK output | no | false interpolate | Interpolate for point values | no | false
decompose | decompose polyhedra | no | false
writeIds | Write cell ids as field | no | true
\endtable \endtable
\heading Output Options
\table
Property | Description | Required | Default
format | ascii or binary format | no | binary
legacy | Legacy VTK output | no | false
precision | Write precision in ascii | no | same as IOstream
directory | The output directory name | no | postProcessing/NAME
width | Padding width for file name | no | 8
decompose | Decompose polyhedral cells | no | false
writeIds | Write cell/patch ids as field | no | true
\endtable
\heading Output Selection
\table
Property | Description | Required | Default
region | Name for a single region | no | region0
regions | List of regions (wordRe list) | no |
patches | Limit to listed patches (wordRe list) | no |
selection | Cell selection (topoSet actions) | no | empty dict
\endtable
Note
The region of interest is defined by the selection dictionary
as a set of actions (add,subtract,subset,invert).
Omitting the selection dictionary is the same as specifying the
conversion of all cells (in the selected regions).
Omitting the patches entry is the same as specifying the conversion of all
patches.
See also See also
Foam::functionObjects::ensightWrite Foam::functionObjects::ensightWrite
Foam::functionObjects::fvMeshFunctionObject Foam::functionObjects::fvMeshFunctionObject
@ -77,10 +132,12 @@ SourceFiles
#ifndef functionObjects_vtkWrite_H #ifndef functionObjects_vtkWrite_H
#define functionObjects_vtkWrite_H #define functionObjects_vtkWrite_H
#include "fvMeshFunctionObject.H" #include "functionObject.H"
#include "foamVtkInternalWriter.H" #include "foamVtkInternalWriter.H"
#include "foamVtkSurfaceMeshWriter.H" #include "foamVtkPatchWriter.H"
#include "wordRes.H" #include "foamVtkSeriesWriter.H"
#include "fvMeshSubsetProxy.H"
#include "searchableSurfaces.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -95,18 +152,36 @@ namespace functionObjects
class vtkWrite class vtkWrite
: :
public fvMeshFunctionObject public functionObject
{ {
// Private data // Private Data
//- Reference to the time database
const Time& time_;
//- The output directory
fileName outputDir_;
//- The printf format for zero-padding names
string printf_;
//- VTK output options //- VTK output options
vtk::outputOptions writeOpts_; vtk::outputOptions writeOpts_;
//- Name of fields to process //- Verbose output
wordRes selectFields_; bool verbose_;
//- Output directory name //- Convert internal mesh
fileName dirName_; bool doInternal_;
//- Convert boundary mesh
bool doBoundary_;
//- Combine patches into a single file
bool oneBoundary_;
//- Interpolate cell to point values
bool interpolate_;
//- Decompose polyhedra //- Decompose polyhedra
bool decompose_; bool decompose_;
@ -114,29 +189,99 @@ class vtkWrite
//- Write cell ids field //- Write cell ids field
bool writeIds_; bool writeIds_;
//- Track changes in mesh geometry
enum polyMesh::readUpdateState meshState_;
//- Requested names of regions to process
wordRes selectRegions_;
//- Requested names of patches to process
wordRes selectPatches_;
//- Requested names of fields to process
wordRes selectFields_;
//- Dictionary of volume selections
dictionary selection_;
//- Pointers to the requested mesh regions
HashTable<const fvMesh*> meshes_;
//- Subsetting for meshes.
// Access index according to sorted mesh names.
PtrList<fvMeshSubset> meshSubsets_;
//- Storage for VTU cells, sizing.
// Access index according to sorted mesh names.
PtrList<vtk::vtuCells> vtuMappings_;
//- VTK file series
HashTable<vtk::seriesWriter, fileName> series_;
// Private Member Functions // Private Member Functions
//- Count number of selected fields for GeoField type. //- Update mesh subset according to zones, geometry, bounds
// Only needed for legacy vtk format. bool updateSubset(fvMeshSubset& subsetter) const;
template<class GeoField>
label countFields() const;
//- Write selected fields for GeoField type. //- Get patchIds selected in list
template<class GeoField> labelList getSelectedPatches(const polyBoundaryMesh& patches) const;
label writeFields
//- Read information for selections
bool readSelection(const dictionary& dict);
//- Update meshes, subMeshes etc.
bool update();
// Write
//- Write all volume fields
label writeAllVolFields
( (
vtk::internalWriter& writer, autoPtr<vtk::internalWriter>& internalWriter,
bool verbose=true UPtrList<vtk::patchWriter>& patchWriters,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const; ) const;
//- Write all volume fields with point interpolation
//- Write selected fields for GeoField type. label writeAllVolFields
template<class GeoField>
label writeFields
( (
vtk::surfaceMeshWriter& writer, autoPtr<vtk::internalWriter>& internalWriter,
bool verbose=true const autoPtr<volPointInterpolation>& pInterp,
UPtrList<vtk::patchWriter>& patchWriters,
const UPtrList
<
PrimitivePatchInterpolation<primitivePatch>
>& patchInterps,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const;
//- Write selected GeoField fields.
template<class GeoField>
label writeVolFields
(
autoPtr<vtk::internalWriter>& internalWriter,
UPtrList<vtk::patchWriter>& patchWriters,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const;
//- Write selected GeoField fields with point interpolation
template<class GeoField>
label writeVolFields
(
autoPtr<vtk::internalWriter>& internalWriter,
const autoPtr<volPointInterpolation>& pInterp,
UPtrList<vtk::patchWriter>& patchWriters,
const UPtrList
<
PrimitivePatchInterpolation<primitivePatch>
>& patchInterps,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const; ) const;
@ -173,11 +318,20 @@ public:
//- Read the vtkWrite specification //- Read the vtkWrite specification
virtual bool read(const dictionary& dict); virtual bool read(const dictionary& dict);
//- Execute, currently does nothing //- Execute - does nothing
virtual bool execute(); virtual bool execute();
//- Write fields //- Write fields
virtual bool write(); virtual bool write();
//- On end - provide feedback about disconnecting from catatyst.
virtual bool end();
//- Update for changes of mesh
virtual void updateMesh(const mapPolyMesh& mpm);
//- Update for mesh point-motion
virtual void movePoints(const polyMesh& mesh);
}; };

View File

@ -2,7 +2,7 @@
========= | ========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | \\ / O peration |
\\ / A nd | Copyright (C) 2017 OpenCFD Ltd. \\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
@ -26,60 +26,147 @@ License
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
template<class GeoField> template<class GeoField>
Foam::label Foam::label Foam::functionObjects::vtkWrite::writeVolFields
Foam::functionObjects::vtkWrite::countFields() const (
autoPtr<vtk::internalWriter>& internalWriter,
UPtrList<vtk::patchWriter>& patchWriters,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const
{ {
return obr_.names<GeoField>(selectFields_).size(); const fvMesh& baseMesh = proxy.baseMesh();
label count = 0;
for (const word& fieldName : baseMesh.sortedNames<GeoField>(acceptField))
{
bool ok = false;
const auto* fieldptr = baseMesh.findObject<GeoField>(fieldName);
if (!fieldptr)
{
continue;
}
auto tfield = fvMeshSubsetProxy::interpolate(proxy, *fieldptr);
const auto& field = tfield();
// Internal
if (internalWriter.valid())
{
ok = true;
internalWriter->write(field);
}
// Boundary
label writeri = 0;
for (vtk::patchWriter& writer : patchWriters)
{
ok = true;
writer.write(field);
++writeri;
}
if (ok)
{
++count;
if (verbose_)
{
if (count == 1)
{
Log << " " << GeoField::typeName << '(';
}
else
{
Log << ' ';
}
Log << fieldName;
}
}
}
if (verbose_ && count)
{
Log << ')' << endl;
}
return count;
} }
template<class GeoField> template<class GeoField>
Foam::label Foam::label Foam::functionObjects::vtkWrite::writeVolFields
Foam::functionObjects::vtkWrite::writeFields
( (
vtk::internalWriter& writer, autoPtr<vtk::internalWriter>& internalWriter,
bool verbose const autoPtr<volPointInterpolation>& pInterp,
UPtrList<vtk::patchWriter>& patchWriters,
const UPtrList<PrimitivePatchInterpolation<primitivePatch>>& patchInterps,
const fvMeshSubset& proxy,
const wordHashSet& acceptField
) const ) const
{ {
const wordList names = obr_.sortedNames<GeoField>(selectFields_); const fvMesh& baseMesh = proxy.baseMesh();
if (verbose && names.size()) label count = 0;
for (const word& fieldName : baseMesh.sortedNames<GeoField>(acceptField))
{ {
Info<< " " << GeoField::typeName bool ok = false;
<< " " << flatOutput(names) << endl; const auto* fieldptr = baseMesh.findObject<GeoField>(fieldName);
if (!fieldptr)
{
continue;
}
auto tfield = fvMeshSubsetProxy::interpolate(proxy, *fieldptr);
const auto& field = tfield();
// Internal
if (internalWriter.valid() && pInterp.valid())
{
ok = true;
internalWriter->write(field, *pInterp);
}
// Boundary
label writeri = 0;
for (vtk::patchWriter& writer : patchWriters)
{
if (writeri < patchInterps.size() && patchInterps.set(writeri))
{
ok = true;
writer.write(field, patchInterps[writeri]);
}
++writeri;
}
if (ok)
{
++count;
if (verbose_)
{
if (count == 1)
{
Log << " " << GeoField::typeName << "->point(";
}
else
{
Log << ' ';
}
Log << fieldName;
}
}
} }
for (const word& fieldName : names) if (verbose_ && count)
{ {
writer.write(obr_.lookupObject<GeoField>(fieldName)); Log << ')' << endl;
} }
return names.size(); return count;
}
template<class GeoField>
Foam::label
Foam::functionObjects::vtkWrite::writeFields
(
vtk::surfaceMeshWriter& writer,
bool verbose
) const
{
const wordList names = obr_.sortedNames<GeoField>(selectFields_);
if (verbose && names.size())
{
Info<< " " << GeoField::typeName
<< " " << flatOutput(names) << endl;
}
for (const word& fieldName : names)
{
writer.write(obr_.lookupObject<GeoField>(fieldName));
}
return names.size();
} }

View File

@ -0,0 +1,274 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
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 "vtkWrite.H"
#include "cellBitSet.H"
#include "topoSetCellSource.H"
// * * * * * * * * * * * * * * Local Data Members * * * * * * * * * * * * * //
namespace Foam
{
// A limited selection of actions
const Enum<topoSetSource::setAction> actionNames
({
{ topoSetSource::ADD, "add" },
{ topoSetSource::SUBTRACT, "subtract" },
{ topoSetSource::SUBSET, "subset" },
{ topoSetSource::INVERT, "invert" },
});
}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
bool Foam::functionObjects::vtkWrite::updateSubset
(
fvMeshSubset& subsetter
) const
{
if (selection_.empty())
{
return false;
}
const fvMesh& mesh = subsetter.baseMesh();
// Start with all cells unselected
cellBitSet cellsToSelect(mesh, false);
for (const entry& dEntry : selection_)
{
if (!dEntry.isDict())
{
WarningInFunction
<< "Ignoring non-dictionary entry "
<< dEntry << endl;
continue;
}
const dictionary& dict = dEntry.dict();
const auto action = actionNames.get("action", dict);
// Handle manually
if (action == topoSetSource::INVERT)
{
cellsToSelect.invert(mesh.nCells());
continue;
}
auto source = topoSetCellSource::New
(
dict.get<word>("source"),
mesh,
dict.optionalSubDict("sourceInfo")
);
source->verbose(false);
switch (action)
{
case topoSetSource::ADD:
case topoSetSource::SUBTRACT:
source->applyToSet(action, cellsToSelect);
break;
case topoSetSource::SUBSET:
{
cellBitSet other(mesh, false);
source->applyToSet(topoSetSource::NEW, other);
cellsToSelect.subset(other);
}
break;
default:
// Should already have been caught
WarningInFunction
<< "Ignoring unhandled action '"
<< actionNames[action] << "'" << endl;
break;
}
}
subsetter.setCellSubset(cellsToSelect.addressing());
return true;
}
Foam::labelList Foam::functionObjects::vtkWrite::getSelectedPatches
(
const polyBoundaryMesh& patches
) const
{
DynamicList<label> patchIDs(patches.size());
for (const polyPatch& pp : patches)
{
if (isType<emptyPolyPatch>(pp))
{
continue;
}
else if (isType<processorPolyPatch>(pp))
{
break; // No processor patches
}
if
(
selectPatches_.size()
? selectPatches_.match(pp.name())
: true
)
{
patchIDs.append(pp.index());
}
}
return patchIDs.shrink();
}
bool Foam::functionObjects::vtkWrite::update()
{
if
(
meshState_ == polyMesh::UNCHANGED
&& !meshSubsets_.empty()
&& !vtuMappings_.empty()
)
{
return false;
}
meshSubsets_.resize(meshes_.size());
vtuMappings_.resize(meshes_.size());
label regioni = 0;
for (const word& regionName : meshes_.sortedToc())
{
const fvMesh& mesh = *(meshes_[regionName]);
if (meshSubsets_.set(regioni))
{
meshSubsets_.clear();
}
else
{
// Mesh subsetting, or pass through
meshSubsets_.set(regioni, new fvMeshSubset(mesh));
}
if (vtuMappings_.set(regioni))
{
// Trigger change for vtk cells too
vtuMappings_[regioni].clear();
}
else
{
// VTU sizing and decomposition information
vtuMappings_.set
(
regioni,
new vtk::vtuCells(writeOpts_, decompose_)
);
}
++regioni;
}
for (auto& subsetter : meshSubsets_)
{
updateSubset(subsetter);
}
meshState_ = polyMesh::UNCHANGED;
return true;
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::functionObjects::vtkWrite::readSelection(const dictionary& dict)
{
meshSubsets_.clear();
vtuMappings_.clear();
meshState_ = polyMesh::TOPO_CHANGE;
// All possible meshes
meshes_ = time_.lookupClass<fvMesh>();
selectRegions_.clear();
dict.readIfPresent("regions", selectRegions_);
if (selectRegions_.empty())
{
selectRegions_.resize(1);
selectRegions_.first() =
dict.lookupOrDefault<word>("region", polyMesh::defaultRegion);
}
// Restrict to specified meshes
meshes_.filterKeys(selectRegions_);
if (meshes_.empty())
{
WarningInFunction
<< "No mesh regions selected for function object " << name()
<< nl;
}
selectPatches_.clear();
dict.readIfPresent("patches", selectPatches_);
selectFields_.clear();
dict.readEntry("fields", selectFields_);
selectFields_.uniq();
// Actions to define selection
selection_ = dict.subOrEmptyDict("selection");
return true;
}
void Foam::functionObjects::vtkWrite::updateMesh(const mapPolyMesh&)
{
meshState_ = polyMesh::TOPO_CHANGE;
}
void Foam::functionObjects::vtkWrite::movePoints(const polyMesh&)
{
// Only move to worse states
if (meshState_ == polyMesh::UNCHANGED)
{
meshState_ = polyMesh::POINTS_MOVED;
}
}
// ************************************************************************* //

View File

@ -52,4 +52,9 @@ maxDi 10.0;
adjustTimeStep yes; adjustTimeStep yes;
functions
{
#include "vtkWrite"
}
// ************************************************************************* // // ************************************************************************* //

View File

@ -0,0 +1,62 @@
// -*- C++ -*-
// Use the vtkWrite function object
vtkWrite
{
type vtkWrite;
libs ("libutilityFunctionObjects.so");
log true;
writeControl writeTime;
writeInterval 1;
// Fields to output (words or regex)
regions (".*");
internal true;
boundary true;
single false;
interpolate true;
// Fields to output (words or regex)
fields (".*");
//- Output format (ascii | binary) - Default=binary
// format binary;
//- Use legacy output format - Default=false
// legacy false;
//- Output directory name - Default="postProcessing/<name>"
// directory "VTK";
//- Write cell ids as field - Default=true
writeIds false;
}
// Solid walls only
walls
{
type vtkWrite;
libs ("libutilityFunctionObjects.so");
log true;
writeControl writeTime;
writeInterval 1;
internal false;
// single true;
regions ( heater "(?i).*solid" );
patches ( "(?i).*solid_to.*" "heater.*(Air|Water)" );
fields (T);
}
// ************************************************************************* //

View File

@ -15,15 +15,69 @@ vtkWrite
//- Use legacy output format - Default=false //- Use legacy output format - Default=false
// legacy false; // legacy false;
//- Output directory name - Default="VTK" //- Output directory name - Default="postProcessing/<name>"
// directory "VTK"; // directory "VTK";
//- Write cell ids as field - Default=true //- Write cell ids as field - Default=true
writeIds false; writeIds false;
//- Write more frequent than fields //- Write more frequent than fields
writeControl timeStep; writeControl timeStep;
writeInterval 25; writeInterval 25;
} }
subset
{
type vtkWrite;
libs ("libutilityFunctionObjects.so");
log true;
// boundary false;
interpolate true;
// Fields to output (words or regex)
fields (U p);
//- Write cell ids as field - Default=true
writeIds false;
//- Write more frequent than fields
writeControl timeStep;
writeInterval 25;
// Region of interest
selection
{
dome
{
action add;
source sphere;
origin (125 100 0);
radius 100;
}
cylinder
{
action subtract;
source cylinder;
p1 (125 100 0);
p2 (125 100 100);
radius 20;
}
outlet
{
action add;
source box;
box (300 -100 -100) (330 250 100);
}
clipping
{
action subset;
source box;
box (-1000 -1000 -1000) (1000 1000 50);
}
}
}
// ************************************************************************* // // ************************************************************************* //