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 abort/abort.C
codedFunctionObject/codedFunctionObject.C codedFunctionObject/codedFunctionObject.C
ensightWrite/ensightWrite.C ensightWrite/ensightWrite.C
ensightWrite/ensightWriteUpdate.C
vtkWrite/vtkWrite.C vtkWrite/vtkWrite.C
vtkWrite/vtkWriteUpdate.C vtkWrite/vtkWriteUpdate.C

View File

@ -47,50 +47,21 @@ namespace functionObjects
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // // * * * * * * * * * * * * * 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 count;
{
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;
} }
@ -115,19 +86,24 @@ Foam::functionObjects::ensightWrite::ensightWrite
) )
), ),
caseOpts_(writeOpts_.format()), caseOpts_(writeOpts_.format()),
selectFields_(), outputDir_(),
dirName_("ensightWrite"),
consecutive_(false), consecutive_(false),
bounds_() meshState_(polyMesh::TOPO_CHANGE),
selectFields_(),
selection_(),
meshSubset_(mesh_),
ensCase_(nullptr),
ensMesh_(nullptr)
{ {
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);
} }
@ -139,16 +115,29 @@ bool Foam::functionObjects::ensightWrite::read(const dictionary& dict)
{ {
fvMeshFunctionObject::read(dict); fvMeshFunctionObject::read(dict);
// Ensure consistency readSelection(dict);
meshSubset_.clear();
ensMesh_.clear();
// // Writer options
// writer options
// consecutive_ = dict.lookupOrDefault("consecutive", false);
writeOpts_.noPatches(dict.lookupOrDefault("noPatches", 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; wordRes list;
if (dict.readIfPresent("patches", list)) if (dict.readIfPresent("patches", list))
@ -164,33 +153,35 @@ bool Foam::functionObjects::ensightWrite::read(const dictionary& dict)
} }
bounds_.clear(); // Case options
dict.readIfPresent("bounds", bounds_);
//
// case options
//
caseOpts_.nodeValues(dict.lookupOrDefault("nodeValues", false)); caseOpts_.nodeValues(dict.lookupOrDefault("nodeValues", false));
caseOpts_.width(dict.lookupOrDefault<label>("width", 8)); caseOpts_.width(dict.lookupOrDefault<label>("width", 8));
// remove existing output directory
caseOpts_.overwrite(dict.lookupOrDefault("overwrite", false)); caseOpts_.overwrite(dict.lookupOrDefault("overwrite", false));
//
// other options
//
dict.readIfPresent("directory", dirName_);
consecutive_ = dict.lookupOrDefault("consecutive", false);
// Output directory
// outputDir_.clear();
// output fields dict.readIfPresent("directory", outputDir_);
//
dict.readEntry("fields", selectFields_); const Time& time_ = obr_.time();
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;
} }
@ -208,24 +199,9 @@ bool Foam::functionObjects::ensightWrite::write()
if (!ensCase_.valid()) 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 ensCase_.reset
( (
new ensightCase new ensightCase(outputDir_, t.globalCaseName(), caseOpts_)
(
ensightDir,
t.globalCaseName(),
caseOpts_
)
); );
} }
@ -238,37 +214,8 @@ bool Foam::functionObjects::ensightWrite::write()
ensCase().setTime(t.value(), t.timeIndex()); ensCase().setTime(t.value(), t.timeIndex());
} }
bool writeGeom = false;
if (!ensMesh_.valid())
{
writeGeom = true;
bitSet selection = this->cellSelection(); if (update())
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)
{ {
// Treat all geometry as moving, since we do not know a priori // Treat all geometry as moving, since we do not know a priori
// if the simulation has mesh motion later on. // if the simulation has mesh motion later on.
@ -276,47 +223,19 @@ bool Foam::functionObjects::ensightWrite::write()
ensMesh_().write(os); 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: ("; Log << type() << " " << name() << " write: (";
writeAllVolFields(meshSubset_, acceptField);
wordHashSet candidates(subsetStrings(selectFields_, mesh_.names())); Log << " )" << nl;
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;
}
ensCase().write(); // Flush case information 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; overwrite true;
width 12; width 12;
directory "EnSight";
fields fields (U p);
(
U selection
p {
); 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
\heading Function object usage \heading Basic Usage
\table \table
Property | Description | Required | Default value Property | Description | Required | Default
type | Type name: ensightWrite | yes | type | Type name: ensightWrite | yes |
fields | Fields to output | yes | fields | Fields to output | yes |
writeControl | Output control | recommended | timeStep boundary | Convert boundary fields | no | true
directory | The output directory name | no | "ensightWrite" internal | Convert internal fields | no | true
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 nodeValues | Write values at nodes | no | false
bounds | Limit with a bounding box | no |
\endtable \endtable
Note that if the \c patches entry is an empty list, this will select all \heading Ensight Output Options
patches and suppress writing the internalMesh. \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. Consecutive output numbering can be used in conjunction with \c overwrite.
See also See also
@ -94,8 +138,9 @@ SourceFiles
#include "interpolation.H" #include "interpolation.H"
#include "volFields.H" #include "volFields.H"
#include "fvMeshSubset.H"
#include "surfaceFields.H" #include "surfaceFields.H"
#include "fvMeshSubsetProxy.H"
#include "searchableSurfaces.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -118,24 +163,29 @@ class ensightWrite
{ {
// Private data // Private data
//- Writer options //- Ensight writer options
ensightMesh::options writeOpts_; ensightMesh::options writeOpts_;
//- Ensight case options
ensightCase::options caseOpts_; ensightCase::options caseOpts_;
//- Name of fields to process //- The output directory
wordRes selectFields_; fileName outputDir_;
//- Output directory name
fileName dirName_;
//- Consecutive output numbering //- Consecutive output numbering
bool consecutive_; bool consecutive_;
//- Restrict to bounding box //- Track changes in mesh geometry
boundBox bounds_; enum polyMesh::readUpdateState meshState_;
//- Requested names of fields to process
wordRes selectFields_;
//- Dictionary of volume selections
dictionary selection_;
//- Mesh subset handler //- Mesh subset handler
autoPtr<fvMeshSubset> meshSubset_; fvMeshSubset meshSubset_;
//- Ensight case handler //- Ensight case handler
autoPtr<ensightCase> ensCase_; autoPtr<ensightCase> ensCase_;
@ -158,16 +208,34 @@ class ensightWrite
return *ensMesh_; 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> 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 //- No copy construct
ensightWrite(const ensightWrite&) = delete; ensightWrite(const ensightWrite&) = delete;

View File

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

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;
}
}
// ************************************************************************* //