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

- align input parameters and some of the behaviour with vtkWrite

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

- separate controls for internal and boundary meshes

- can restrict conversion based on zone names, enclosing volumes,
  bounding box.
This commit is contained in:
Mark Olesen 2018-10-09 18:22:40 +02:00
parent 4f2ec88d24
commit 8cff734abc
5 changed files with 453 additions and 278 deletions

View File

@ -1,7 +1,10 @@
abort/abort.C
codedFunctionObject/codedFunctionObject.C
ensightWrite/ensightWrite.C
ensightWrite/ensightWriteUpdate.C
vtkWrite/vtkWrite.C
vtkWrite/vtkWriteUpdate.C

View File

@ -47,50 +47,21 @@ namespace functionObjects
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
Foam::bitSet Foam::functionObjects::ensightWrite::cellSelection() const
Foam::label Foam::functionObjects::ensightWrite::writeAllVolFields
(
const fvMeshSubset& proxy,
const wordHashSet& acceptField
)
{
bitSet cellsToSelect;
label count = 0;
// Could also deal with cellZones here, as required
count += writeVolFields<scalar>(proxy, acceptField);
count += writeVolFields<vector>(proxy, acceptField);
count += writeVolFields<sphericalTensor>(proxy, acceptField);
count += writeVolFields<symmTensor>(proxy, acceptField);
count += writeVolFields<tensor>(proxy, acceptField);
if (bounds_.empty())
{
return cellsToSelect;
}
const auto& cellCentres = static_cast<const fvMesh&>(mesh_).C();
const label len = mesh_.nCells();
cellsToSelect.resize(len);
for (label celli=0; celli < len; ++celli)
{
const point& cc = cellCentres[celli];
if (bounds_.contains(cc))
{
cellsToSelect.set(celli);
}
}
return cellsToSelect;
}
int Foam::functionObjects::ensightWrite::process(const word& fieldName)
{
int state = 0;
writeVolField<scalar>(fieldName, state);
writeVolField<vector>(fieldName, state);
writeVolField<sphericalTensor>(fieldName, state);
writeVolField<symmTensor>(fieldName, state);
writeVolField<tensor>(fieldName, state);
return state;
return count;
}
@ -115,19 +86,24 @@ Foam::functionObjects::ensightWrite::ensightWrite
)
),
caseOpts_(writeOpts_.format()),
selectFields_(),
dirName_("ensightWrite"),
outputDir_(),
consecutive_(false),
bounds_()
meshState_(polyMesh::TOPO_CHANGE),
selectFields_(),
selection_(),
meshSubset_(mesh_),
ensCase_(nullptr),
ensMesh_(nullptr)
{
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);
}
@ -139,16 +115,29 @@ bool Foam::functionObjects::ensightWrite::read(const dictionary& dict)
{
fvMeshFunctionObject::read(dict);
// Ensure consistency
meshSubset_.clear();
ensMesh_.clear();
readSelection(dict);
//
// writer options
//
writeOpts_.noPatches(dict.lookupOrDefault("noPatches", false));
// Writer options
consecutive_ = dict.lookupOrDefault("consecutive", false);
writeOpts_.useBoundaryMesh(dict.lookupOrDefault("boundary", true));
writeOpts_.useInternalMesh(dict.lookupOrDefault("internal", true));
// Warn if noPatches keyword (1806) exists and contradicts our settings
// Cannot readily use Compat since the boolean has the opposite value.
if
(
dict.lookupOrDefault("noPatches", false)
&& writeOpts_.useBoundaryMesh()
)
{
WarningInFunction
<< "Use 'boundary' instead of 'noPatches' to enable/disable "
<< "conversion of the boundaries" << endl;
}
wordRes list;
if (dict.readIfPresent("patches", list))
@ -164,33 +153,35 @@ bool Foam::functionObjects::ensightWrite::read(const dictionary& dict)
}
bounds_.clear();
dict.readIfPresent("bounds", bounds_);
//
// case options
//
// Case options
caseOpts_.nodeValues(dict.lookupOrDefault("nodeValues", false));
caseOpts_.width(dict.lookupOrDefault<label>("width", 8));
// remove existing output directory
caseOpts_.overwrite(dict.lookupOrDefault("overwrite", false));
//
// other options
//
dict.readIfPresent("directory", dirName_);
consecutive_ = dict.lookupOrDefault("consecutive", false);
// Output directory
//
// output fields
//
dict.readEntry("fields", selectFields_);
selectFields_.uniq();
outputDir_.clear();
dict.readIfPresent("directory", outputDir_);
const Time& time_ = obr_.time();
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;
}
@ -208,24 +199,9 @@ bool Foam::functionObjects::ensightWrite::write()
if (!ensCase_.valid())
{
// Define sub-directory name to use for EnSight data.
// The path to the ensight directory is at case level only
// - For parallel cases, data only written from master
fileName ensightDir = dirName_;
if (!ensightDir.isAbsolute())
{
ensightDir = t.globalPath()/ensightDir;
}
ensCase_.reset
(
new ensightCase
(
ensightDir,
t.globalCaseName(),
caseOpts_
)
new ensightCase(outputDir_, t.globalCaseName(), caseOpts_)
);
}
@ -238,37 +214,8 @@ bool Foam::functionObjects::ensightWrite::write()
ensCase().setTime(t.value(), t.timeIndex());
}
bool writeGeom = false;
if (!ensMesh_.valid())
{
writeGeom = true;
bitSet selection = this->cellSelection();
if (returnReduce(!selection.empty(), orOp<bool>()))
{
meshSubset_.clear();
meshSubset_.reset(new fvMeshSubset(mesh_, selection));
ensMesh_.reset
(
new ensightMesh(meshSubset_->subMesh(), writeOpts_)
);
}
else
{
ensMesh_.reset
(
new ensightMesh(mesh_, writeOpts_)
);
}
}
if (ensMesh_().needsUpdate())
{
writeGeom = true;
ensMesh_().correct();
}
if (writeGeom)
if (update())
{
// Treat all geometry as moving, since we do not know a priori
// if the simulation has mesh motion later on.
@ -276,47 +223,19 @@ bool Foam::functionObjects::ensightWrite::write()
ensMesh_().write(os);
}
wordHashSet acceptField(mesh_.names<void>(selectFields_));
// Prune restart fields
acceptField.filterKeys
(
[](const word& k){ return k.endsWith("_0"); },
true // prune
);
Log << type() << " " << name() << " write: (";
writeAllVolFields(meshSubset_, acceptField);
wordHashSet candidates(subsetStrings(selectFields_, mesh_.names()));
DynamicList<word> missing(selectFields_.size());
DynamicList<word> ignored(selectFields_.size());
// Check exact matches first
for (const wordRe& select : selectFields_)
{
if (select.isLiteral())
{
const word& fieldName = select;
if (!candidates.erase(fieldName))
{
missing.append(fieldName);
}
else if (process(fieldName) < 1)
{
ignored.append(fieldName);
}
}
}
for (const word& cand : candidates)
{
process(cand);
}
Log << " )" << endl;
if (missing.size())
{
WarningInFunction
<< "Missing field " << missing << endl;
}
if (ignored.size())
{
WarningInFunction
<< "Unprocessed field " << ignored << endl;
}
Log << " )" << nl;
ensCase().write(); // Flush case information
@ -330,40 +249,4 @@ bool Foam::functionObjects::ensightWrite::end()
}
void Foam::functionObjects::ensightWrite::updateMesh(const mapPolyMesh& mpm)
{
// fvMeshFunctionObject::updateMesh(mpm);
// This is heavy-handed, but with a bounding-box limited sub-mesh,
// we don't readily know if the updates affect the subsetted mesh.
if (!bounds_.empty())
{
ensMesh_.clear();
meshSubset_.clear();
}
else if (ensMesh_.valid())
{
ensMesh_->expire();
}
}
void Foam::functionObjects::ensightWrite::movePoints(const polyMesh& mpm)
{
// fvMeshFunctionObject::updateMesh(mpm);
// This is heavy-handed, but with a bounding-box limited sub-mesh,
// we don't readily know if the updates affect the subsetted mesh.
if (!bounds_.empty())
{
ensMesh_.clear();
meshSubset_.clear();
}
else if (ensMesh_.valid())
{
ensMesh_->expire();
}
}
// ************************************************************************* //

View File

@ -42,36 +42,80 @@ Description
overwrite true;
width 12;
directory "EnSight";
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
\heading Function object usage
\heading Basic Usage
\table
Property | Description | Required | Default value
type | Type name: ensightWrite | yes |
fields | Fields to output | yes |
writeControl | Output control | recommended | timeStep
directory | The output directory name | no | "ensightWrite"
overwrite | Remove existing directory | no | false
format | Either ascii or binary | no | same as simulation
width | Mask width for \c data/XXXX | no | 8
noPatches | Suppress writing patches | no | false
patches | Select patches to write | no |
faceZones | Select faceZones to write | no |
consecutive | Consecutive output numbering | no | false
nodeValues | Write values at nodes | no | false
bounds | Limit with a bounding box | no |
Property | Description | Required | Default
type | Type name: ensightWrite | yes |
fields | Fields to output | yes |
boundary | Convert boundary fields | no | true
internal | Convert internal fields | no | true
nodeValues | Write values at nodes | no | false
\endtable
Note that if the \c patches entry is an empty list, this will select all
patches and suppress writing the internalMesh.
\heading Ensight Output Options
\table
Property | Description | Required | Default
format | ascii or binary format | no | same as simulation
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
nodeValues | Write values at nodes | no | false
\endtable
\heading Output Selection
\table
Property | Description | Required | Default
region | Name for a single region | no | region0
faceZones | Select faceZones to write | 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.
Consecutive output numbering can be used in conjunction with \c overwrite.
See also
@ -94,8 +138,9 @@ SourceFiles
#include "interpolation.H"
#include "volFields.H"
#include "fvMeshSubset.H"
#include "surfaceFields.H"
#include "fvMeshSubsetProxy.H"
#include "searchableSurfaces.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -118,24 +163,29 @@ class ensightWrite
{
// Private data
//- Writer options
//- Ensight writer options
ensightMesh::options writeOpts_;
//- Ensight case options
ensightCase::options caseOpts_;
//- Name of fields to process
wordRes selectFields_;
//- Output directory name
fileName dirName_;
//- The output directory
fileName outputDir_;
//- Consecutive output numbering
bool consecutive_;
//- Restrict to bounding box
boundBox bounds_;
//- Track changes in mesh geometry
enum polyMesh::readUpdateState meshState_;
//- Requested names of fields to process
wordRes selectFields_;
//- Dictionary of volume selections
dictionary selection_;
//- Mesh subset handler
autoPtr<fvMeshSubset> meshSubset_;
fvMeshSubset meshSubset_;
//- Ensight case handler
autoPtr<ensightCase> ensCase_;
@ -158,16 +208,34 @@ class ensightWrite
return *ensMesh_;
}
//- Define cell selection from bounding box.
// An empty set if no bounding box is specified.
bitSet cellSelection() const;
//- Apply for the volume field type
//- Update mesh subset according to zones, geometry, bounds
bool updateSubset(fvMeshSubset& subsetter) const;
//- Read information for selections
bool readSelection(const dictionary& dict);
//- Update meshes, subMeshes etc.
bool update();
// Write
//- Write all volume fields
label writeAllVolFields
(
const fvMeshSubset& proxy,
const wordHashSet& acceptField
);
//- Write selected volume fields.
template<class Type>
int writeVolField(const word& inputName, int& state);
label writeVolFields
(
const fvMeshSubset& proxy,
const wordHashSet& acceptField
);
//- Process by trying to apply for various volume field types.
int process(const word& inputName);
//- No copy construct
ensightWrite(const ensightWrite&) = delete;

View File

@ -23,65 +23,51 @@ License
\*---------------------------------------------------------------------------*/
#include "Time.H"
#include "ensightOutput.H"
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
template<class Type>
int Foam::functionObjects::ensightWrite::writeVolField
Foam::label Foam::functionObjects::ensightWrite::writeVolFields
(
const word& inputName,
int& state
const fvMeshSubset& proxy,
const wordHashSet& acceptField
)
{
// State: return 0 (not-processed), -1 (skip), +1 ok
typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType;
typedef GeometricField<Type, fvPatchField, volMesh> GeoField;
// Already done
if (state)
const fvMesh& baseMesh = proxy.baseMesh();
label count = 0;
for (const word& fieldName : baseMesh.sortedNames<GeoField>(acceptField))
{
return state;
}
const auto* fieldptr = baseMesh.findObject<GeoField>(fieldName);
const VolFieldType* fldPtr = findObject<VolFieldType>(inputName);
if (!fieldptr)
{
continue;
}
// Not available
if (!fldPtr)
{
return state;
}
auto tfield = fvMeshSubsetProxy::interpolate(proxy, *fieldptr);
const auto& field = tfield();
autoPtr<ensightFile> os = ensCase().newData<Type>(inputName);
if (meshSubset_.valid())
{
tmp<VolFieldType> tfield = meshSubset_->interpolate(*fldPtr);
autoPtr<ensightFile> os = ensCase().newData<Type>(fieldName);
ensightOutput::writeField<Type>
(
tfield(),
ensMesh(),
os,
caseOpts_.nodeValues()
);
}
else
{
ensightOutput::writeField<Type>
(
*fldPtr,
field,
ensMesh(),
os,
caseOpts_.nodeValues()
);
Log << ' ' << fieldName;
++count;
}
Log << " " << inputName;
state = +1;
return state;
return count;
}

View File

@ -0,0 +1,235 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / 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 "ensightWrite.H"
#include "dictionary.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::ensightWrite::updateSubset
(
fvMeshSubset& subsetter
) const
{
if (selection_.empty())
{
return false;
}
const fvMesh& mesh = subsetter.baseMesh();
// Start with all cells unselected
cellBitSet cellsToSelect(mesh, false);
// Execute all actions
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;
}
#if 0
Foam::labelList Foam::functionObjects::ensightWrite::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();
}
#endif
bool Foam::functionObjects::ensightWrite::update()
{
if (meshState_ == polyMesh::UNCHANGED)
{
return false;
}
// This is heavy-handed, but with a bounding-box limited sub-mesh,
// we don't readily know if the updates affect the subsetted mesh.
// if (meshSubset_.hasSubMesh())
// {
// ensMesh_.clear();
// meshSubset_.clear();
// }
// else if (ensMesh_.valid())
// {
// ensMesh_->expire();
// }
meshSubset_.clear();
updateSubset(meshSubset_);
meshState_ = polyMesh::UNCHANGED;
if (!ensMesh_.valid())
{
ensMesh_.reset(new ensightMesh(meshSubset_.mesh(), writeOpts_));
}
else if (ensMesh_().needsUpdate())
{
ensMesh_().correct();
}
return true;
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::functionObjects::ensightWrite::readSelection(const dictionary& dict)
{
// Ensure consistency
ensMesh_.clear();
meshSubset_.clear();
meshState_ = polyMesh::TOPO_CHANGE;
selectFields_.clear();
dict.readEntry("fields", selectFields_);
selectFields_.uniq();
// Actions to define selection
selection_ = dict.subOrEmptyDict("selection");
return true;
}
void Foam::functionObjects::ensightWrite::updateMesh(const mapPolyMesh&)
{
meshState_ = polyMesh::TOPO_CHANGE;
}
void Foam::functionObjects::ensightWrite::movePoints(const polyMesh&)
{
// Only move to worse states
if (meshState_ == polyMesh::UNCHANGED)
{
meshState_ = polyMesh::POINTS_MOVED;
}
}
// ************************************************************************* //