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

@ -27,7 +27,11 @@ License
#include "dictionary.H"
#include "Time.H"
#include "areaFields.H"
#include "stringListOps.H"
#include "foamVtkInternalWriter.H"
#include "foamVtkPatchWriter.H"
#include "foamVtkSeriesWriter.H"
#include "foamVtmWriter.H"
#include "addToRunTimeSelectionTable.H"
// * * * * * * * * * * * * * * 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 * * * * * * * * * * * * * * //
Foam::functionObjects::vtkWrite::vtkWrite
@ -51,19 +122,37 @@ Foam::functionObjects::vtkWrite::vtkWrite
const dictionary& dict
)
:
fvMeshFunctionObject(name, runTime, dict),
functionObject(name),
time_(runTime),
outputDir_(),
printf_(),
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_(),
dirName_("VTK")
selection_(),
meshes_(),
meshSubsets_(),
vtuMappings_(),
series_()
{
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);
}
// May still want this? (OCT-2018)
// 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);
}
@ -73,16 +162,23 @@ Foam::functionObjects::vtkWrite::vtkWrite
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;
if (dict.lookupOrDefault("legacy", false))
{
writeOpts_.legacy(true);
}
writeOpts_.ascii
(
@ -90,33 +186,63 @@ bool Foam::functionObjects::vtkWrite::read(const dictionary& dict)
&& (IOstream::formatEnum(dict.get<word>("format")) == IOstream::ASCII)
);
// FUTURE?
// writeOpts_.precision
// (
// dict.lookupOrDefault
// (
// "writePrecision",
// IOstream::defaultPrecision()
// )
// );
if (dict.lookupOrDefault("legacy", false))
{
writeOpts_.legacy(true);
}
writeOpts_.precision
(
dict.lookupOrDefault
(
"precision",
IOstream::defaultPrecision()
)
);
// Info<< type() << " " << name() << " output-format: "
// << 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);
writeIds_ = dict.lookupOrDefault("writeIds", false);
//
// output fields
//
dict.readEntry("fields", selectFields_);
selectFields_.uniq();
// 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();
return true;
}
@ -133,96 +259,515 @@ bool Foam::functionObjects::vtkWrite::write()
// const word timeDesc =
// 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_;
if (!vtkDir.isAbsolute())
const scalar timeValue = time_.value();
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.
const auto i = vtkName.rfind("processor");
if (i != string::npos)
fileName regionPrefix;
if (regionName != polyMesh::defaultRegion)
{
vtkName = vtkName.substr(i);
regionPrefix = regionName;
}
}
// internal mesh
{
const fileName outputName
auto& meshProxy = meshSubsets_[regioni];
auto& vtuMeshCells = vtuMappings_[regioni];
++regioni;
const fvMesh& baseMesh = meshProxy.baseMesh();
wordHashSet acceptField(baseMesh.names<void>(selectFields_));
// Prune restart fields
acceptField.filterKeys
(
vtkDir/vtkName
+ "_"
+ timeDesc
[](const word& k){ return k.endsWith("_0"); },
true // prune
);
Info<< name() << " output Time: " << time_.timeName() << nl
<< " Internal : " << outputName << endl;
// Number of fields to be written: only needed for legacy vtk format
label nVolFields = 0;
if (writeOpts_.legacy())
{
nVolFields =
const label nVolFields =
(
(doInternal_ || doBoundary_)
? baseMesh.count
(
(writeIds_ ? 1 : 0)
+ countFields<volScalarField>()
+ countFields<volVectorField>()
+ countFields<volSphericalTensorField>()
+ countFields<volSymmTensorField>()
+ countFields<volTensorField>()
stringListOps::foundOp<word>(fieldTypes::volume),
acceptField
)
: 0
);
// 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
vtk::internalWriter writer
(
mesh_,
vtuMeshCells,
outputName,
writeOpts_
);
// Setup the patch writers
const polyBoundaryMesh& patches = meshProxy.mesh().boundaryMesh();
PtrList<vtk::patchWriter> patchWriters;
PtrList<PrimitivePatchInterpolation<primitivePatch>> patchInterps;
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
{
writer.beginCellData(nVolFields);
// Write cellID field
if (writeIds_)
if (internalWriter.valid())
{
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
writeFields<volScalarField>(writer);
writeFields<volVectorField>(writer);
writeFields<volSphericalTensorField>(writer);
writeFields<volSymmTensorField>(writer);
writeFields<volTensorField>(writer);
if (nVolFields)
{
for (vtk::patchWriter& writer : patchWriters)
{
writer.beginCellData
(
(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;
}
bool Foam::functionObjects::vtkWrite::end()
{
meshSubsets_.clear();
vtuMappings_.clear();
meshes_.clear();
return true;
}
// ************************************************************************* //