ENH: refactor surface writer collated time management (#1600)

- abstracted out from ensight surface writer for potential reuse by
  other surface writers.
This commit is contained in:
Mark Olesen
2020-09-09 09:50:09 +02:00
parent f8ffee8135
commit 7d203443c3
7 changed files with 560 additions and 288 deletions

View File

@ -62,6 +62,7 @@ triSurface/patches/surfacePatch.C
writers = writers
$(writers)/surfaceWriter.C
$(writers)/caching/surfaceWriterCaching.C
$(writers)/boundaryData/boundaryDataSurfaceWriter.C
$(writers)/ensight/ensightSurfaceWriter.C
$(writers)/foam/foamSurfaceWriter.C

View File

@ -0,0 +1,275 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2016-2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "surfaceWriterCaching.H"
#include "ListOps.H"
#include "Fstream.H"
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
namespace Foam
{
// Compare time values with tolerance
static const equalOp<scalar> equalTimes(ROOTSMALL);
// Use ListOps findLower (with tolerance), to find the location of the next
// time-related index.
// The returned index is always 0 or larger (no negative values).
static label findTimeIndex(const UList<scalar>& list, const scalar val)
{
label idx =
findLower
(
list,
val,
0,
[](const scalar a, const scalar b)
{
return (a < b) && (Foam::mag(b - a) > ROOTSMALL);
}
);
if (idx < 0 || !equalTimes(list[idx], val))
{
++idx;
}
return idx;
}
} // End namespace Foam
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::surfaceWriters::writerCaching::writerCaching(const word& cacheFileName)
:
dictName_(cacheFileName)
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
const Foam::dictionary& Foam::surfaceWriters::writerCaching::fieldsDict() const
{
const dictionary* dictptr = cache_.findDict("fields", keyType::LITERAL);
if (!dictptr)
{
dictptr = &dictionary::null;
}
return *dictptr;
}
Foam::dictionary& Foam::surfaceWriters::writerCaching::fieldDict
(
const word& fieldName
)
{
return
cache_
.subDictOrAdd("fields", keyType::LITERAL)
.subDictOrAdd(fieldName, keyType::LITERAL);
}
bool Foam::surfaceWriters::writerCaching::remove(const word& fieldName)
{
dictionary* dictptr = cache_.findDict("fields", keyType::LITERAL);
if (dictptr)
{
return dictptr->remove(fieldName);
}
return false;
}
void Foam::surfaceWriters::writerCaching::clear()
{
times_.clear();
geoms_.clear();
cache_.clear();
}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
Foam::label Foam::surfaceWriters::writerCaching::readPreviousTimes
(
const fileName& dictFile,
const scalar timeValue
)
{
// In 1906 and earlier, the fieldsDict contained "meshes" and "times"
// entries, each with their own time values.
// This makes it more difficult to define the exact correspondence
// between geometry intervals and times.
//
// Now track the used geometry intervals as a bitSet.
// Only called from master
label timeIndex = 0;
cache_.clear();
IFstream is(dictFile);
if (is.good() && cache_.read(is))
{
geoms_.clear();
cache_.readIfPresent("times", times_);
timeIndex = findTimeIndex(times_, timeValue);
labelList geomIndices;
scalarList meshTimes;
if (cache_.readIfPresent("geometry", geomIndices))
{
// Convert indices to bitSet entries
geoms_.set(geomIndices);
}
else if (cache_.readIfPresent("meshes", meshTimes))
{
WarningInFunction
<< nl
<< "Setting geometry timeset information from time values"
<< " (cache from an older OpenFOAM version)." << nl
<< "This may not be fully reliable." << nl
<< nl;
for (const scalar meshTime : meshTimes)
{
const label geomIndex = findTimeIndex(times_, meshTime);
geoms_.set(geomIndex);
}
}
// Make length consistent with time information.
// We read/write the indices instead of simply dumping the bitSet.
// This makes the contents more human readable.
geoms_.resize(times_.size());
}
return timeIndex;
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::surfaceWriters::writerCaching::update
(
const fileName& baseDir,
const scalar timeValue,
const bool geomChanged,
const word& fieldName,
const word& fieldType,
const word& varName
)
{
const fileName dictFile(baseDir/dictName_);
bool stateChanged = false;
const label timeIndex =
(
times_.empty()
? readPreviousTimes(dictFile, timeValue)
: findTimeIndex(times_, timeValue)
);
// Update stored times list and geometry index
if (timeIndex < geoms_.size()-1)
{
// Clear old content when shrinking
geoms_.unset(timeIndex);
}
// Extend or truncate list
geoms_.resize(timeIndex+1);
times_.resize(timeIndex+1, VGREAT);
if (!equalTimes(times_[timeIndex], timeValue))
{
stateChanged = true;
times_[timeIndex] = timeValue;
}
if (geomChanged)
{
stateChanged = true;
geoms_.set(timeIndex);
}
// Update time/geometry information in dictionary
cache_.set("times", times_);
cache_.set("geometry", geoms_.sortedToc());
// Debugging, or if needed for older versions:
//// cache_.set
//// (
//// "meshes",
//// IndirectList<scalar>(times_, geoms_.sortedToc())
//// );
// Add field information to dictionary
dictionary& dict = fieldDict(fieldName);
if (dict.empty())
{
stateChanged = true;
dict.set("type", fieldType);
if (!varName.empty() && varName != fieldName)
{
// Use variable name, if it differs from fieldName
dict.set("name", varName);
}
}
if (stateChanged)
{
OFstream os(dictFile);
os << "// State file for surface writer output" << nl << nl;
cache_.write(os, false);
os << nl << "// End" << nl;
}
return stateChanged;
}
// ************************************************************************* //

View File

@ -0,0 +1,163 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2016-2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Class
Foam::surfaceWriters::writerCaching
Description
Information for surface writers with collated times.
The class maintains an internal list of the known times
as well as a file-cached version with the field information.
The information is used for restarts.
SourceFiles
surfaceWriterCaching.C
\*---------------------------------------------------------------------------*/
#ifndef surfaceWriters_writerCaching_H
#define surfaceWriters_writerCaching_H
#include "bitSet.H"
#include "dictionary.H"
#include "scalarList.H"
#include "DynamicList.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
namespace surfaceWriters
{
/*---------------------------------------------------------------------------*\
Class writerCaching Declaration
\*---------------------------------------------------------------------------*/
class writerCaching
{
// Private Data
//- Cache dictionary file name
word dictName_;
//- The output times
DynamicList<scalar> times_;
//- Indices in times_ when geometry (mesh) has been written
bitSet geoms_;
//- Cached information for geometry, times, fields
dictionary cache_;
// Private Member Functions
//- Read time information from dictFileName.
// Returns timeIndex corresponding to timeValue
label readPreviousTimes
(
const fileName& dictFile,
const scalar timeValue
);
//- Get or create a sub-dictionary for named field
dictionary& fieldDict(const word& fieldName);
//- Remove named field
bool remove(const word& fieldName);
public:
// Constructors
//- Construct with specified cache name
explicit writerCaching(const word& cacheFileName);
//- Destructor
virtual ~writerCaching() = default;
// Member Functions
//- The output times for fields
const scalarList& times() const
{
return times_;
}
//- Indices in times() when geometry (mesh) has been written
const bitSet& geometries() const
{
return geoms_;
}
//- The most current time index
label latestTimeIndex() const
{
return max(0, times_.size()-1);
}
//- The most current geometry index
label latestGeomIndex() const
{
return max(0, geoms_.find_last());
}
//- Get or create the 'fields' information dictionary.
const dictionary& fieldsDict() const;
//- Clear all values
void clear();
//- Update time/geometry information and file cache.
//- This routine should only be called from the master process
// \return True if there is a state change, which is either a
// geometry change or a new time interval
bool update
(
const fileName& baseDir, //!< Directory containing the cache file
const scalar timeValue, //!< The current time value
const bool geomChanged, //!< Monitored geometry changed
const word& fieldName, //!< Name of field
const word& fieldType, //!< Type of field
const word& varName = word::null //!< Alternative field name
);
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace surfaceWriters
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#endif
// ************************************************************************* //

View File

@ -56,7 +56,7 @@ void Foam::surfaceWriters::ensightWriter::printTimeset
(
OSstream& os,
const label ts,
const scalar& timeValue
const scalar timeValue
)
{
os
@ -176,7 +176,8 @@ Foam::surfaceWriters::ensightWriter::ensightWriter()
:
surfaceWriter(),
writeFormat_(IOstream::ASCII),
collateTimes_(true)
collateTimes_(true),
caching_("fieldsDict") // Historic name
{}
@ -190,7 +191,8 @@ Foam::surfaceWriters::ensightWriter::ensightWriter
(
IOstreamOption::formatEnum("format", options, IOstream::ASCII)
),
collateTimes_(options.getOrDefault("collateTimes", true))
collateTimes_(options.getOrDefault("collateTimes", true)),
caching_("fieldsDict") // Historic name
{}
@ -227,15 +229,13 @@ Foam::surfaceWriters::ensightWriter::ensightWriter
void Foam::surfaceWriters::ensightWriter::close()
{
times_.clear();
meshes_.clear();
cache_.clear();
caching_.clear();
surfaceWriter::close();
}
// Note that ensight does supports geometry in a separate file,
// but setting this true leaves mesh files in the wrong places
// but setting this true leaves geometry files in the wrong places
// (when there are fields).
//
// Make this false to let the field writers take back control

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011 OpenFOAM Foundation
Copyright (C) 2015-2019 OpenCFD Ltd.
Copyright (C) 2015-2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -61,8 +61,7 @@ SourceFiles
#define ensightSurfaceWriter_H
#include "surfaceWriter.H"
#include "bitSet.H"
#include "DynamicList.H"
#include "surfaceWriterCaching.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -87,27 +86,12 @@ class ensightWriter
//- Collate times (default: true)
bool collateTimes_;
//- The collated output times
DynamicList<scalar> times_;
//- Indices in times_ when geometry (mesh) has been written (collated)
bitSet meshes_;
//- Cached information for times, geometry, fields (collated)
dictionary cache_;
writerCaching caching_;
// Private Member Functions
//- Read time information from baseDir / dictName.
// Returns timeIndex corresponding to timeValue
label readPreviousTimes
(
const fileName& baseDir,
const word& dictName,
const scalar& timeValue
);
//- The geometry can be any of the following:
//
// 0: constant/static
@ -120,7 +104,7 @@ class ensightWriter
(
OSstream& os,
const label ts,
const scalar& timeValue
const scalar timeValue
);
//- Print time-set for ensight case file, with N times and 0-based

View File

@ -26,122 +26,20 @@ License
\*---------------------------------------------------------------------------*/
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
namespace Foam
{
// Compare time values with tolerance
static const equalOp<scalar> equalTimes(ROOTSMALL);
// Use ListOps findLower (with tolerance), to find the location of the next
// time-related index.
// The returned index is always 0 or larger (no negative values).
static label findTimeIndex(const UList<scalar>& list, const scalar val)
{
label idx =
findLower
(
list,
val,
0,
[](const scalar a, const scalar b)
{
return (a < b) && (Foam::mag(b - a) > ROOTSMALL);
}
);
if (idx < 0 || !equalTimes(list[idx], val))
{
++idx;
}
return idx;
}
} // End namespace Foam
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
Foam::label Foam::surfaceWriters::ensightWriter::readPreviousTimes
(
const fileName& baseDir,
const word& dictName,
const scalar& timeValue
)
{
// In 1906 and earlier, the fieldsDict contained "meshes" and "times"
// entries, each with their own time values.
// This makes it more difficult to define the exact correspondence
// between geometry intervals and times.
//
// We now instead track used geometry intervals as a bitSet.
// Only called from master
label timeIndex = 0;
labelList geomIndices;
scalarList meshTimes;
cache_.clear();
const fileName dictFile(baseDir/dictName);
if (isFile(dictFile))
{
IFstream is(dictFile);
if (is.good() && cache_.read(is))
{
meshes_.clear();
cache_.readIfPresent("times", times_);
timeIndex = findTimeIndex(times_, timeValue);
if (cache_.readIfPresent("geometry", geomIndices))
{
// Convert indices to bitSet entries
meshes_.set(geomIndices);
}
else if (cache_.readIfPresent("meshes", meshTimes))
{
WarningInFunction
<< nl
<< "Setting geometry timeset information from time values"
<< " (fieldsDict from an older OpenFOAM version)." << nl
<< "This may not be fully reliable." << nl
<< nl;
for (const scalar& meshTime : meshTimes)
{
const label meshIndex = findTimeIndex(times_, meshTime);
meshes_.set(meshIndex);
}
}
// Make length consistent with time information.
// We read/write the indices instead of simply dumping the bitSet.
// This makes the contents more human readable.
meshes_.resize(times_.size());
}
}
return timeIndex;
}
int Foam::surfaceWriters::ensightWriter::geometryTimeset() const
{
if (meshes_.count() <= 1)
const scalarList& times = caching_.times();
const bitSet& geoms = caching_.geometries();
if (geoms.count() <= 1)
{
// Static
return 0;
}
if (meshes_.size() == times_.size() && meshes_.all())
if (geoms.size() == times.size() && geoms.all())
{
// Geometry changing is the same as fields changing
return 1;
@ -158,7 +56,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated()
{
// Collated?
// ========
// Geometry: rootdir/surfaceName/surfaceName.case
// CaseFile: rootdir/surfaceName/surfaceName.case
// Geometry: rootdir/surfaceName/surfaceName.mesh
wroteGeom_ = true;
@ -173,6 +71,9 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
const Field<Type>& localValues
)
{
// Geometry changed since last output? Capture now before any merging.
const bool geomChanged = (!upToDate_);
checkOpen();
const ensight::FileName surfName(outputPath_.name());
@ -181,7 +82,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
// Collated
// ========
// Geometry: rootdir/surfaceName/surfaceName.case
// CaseFile: rootdir/surfaceName/surfaceName.case
// Geometry: rootdir/surfaceName/data/<index>/geometry
// Field: rootdir/surfaceName/data/<index>/field
@ -210,11 +111,8 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
}
// Mesh changed since last output? Do before any merging.
const bool meshChanged = (!upToDate_);
// geometry merge() implicit
// Implicit geometry merge()
tmp<Field<Type>> tfield = mergeField(localValues);
const meshedSurf& surf = surface();
@ -226,42 +124,21 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
mkDir(outputFile.path());
}
bool stateChanged = meshChanged;
const label timeIndex =
(
times_.empty()
? readPreviousTimes(baseDir, "fieldsDict", timeValue)
: findTimeIndex(times_, timeValue)
);
const bool stateChanged =
caching_.update
(
baseDir,
timeValue,
geomChanged,
fieldName,
ensightPTraits<Type>::typeName,
varName
);
// Update stored times list and mesh index
if (timeIndex < meshes_.size()-1)
{
// Clear old content when shrinking
meshes_.unset(timeIndex);
}
// Extend or truncate list
meshes_.resize(timeIndex+1);
times_.resize(timeIndex+1, VGREAT);
if (meshChanged)
{
meshes_.set(timeIndex);
}
if (!equalTimes(times_[timeIndex], timeValue))
{
stateChanged = true;
times_[timeIndex] = timeValue;
}
// The most current geometry index
const label geomIndex(max(0, meshes_.find_last()));
// The most current time and geometry indices
const label timeIndex = caching_.latestTimeIndex();
const label geomIndex = caching_.latestGeomIndex();
// This will be used for the name of a static geometry,
@ -273,126 +150,97 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
// Do case file
if (stateChanged)
{
// Add time information to dictionary
cache_.set("geometry", meshes_.sortedToc());
cache_.set("times", times_);
OFstream osCase(outputFile, IOstream::ASCII);
// Debugging, or if needed for older versions:
//// cache_.set
//// (
//// "meshes",
//// IndirectList<scalar>(times_, meshes_.sortedToc())
//// );
// Format options
osCase.setf(ios_base::left);
osCase.setf(ios_base::scientific, ios_base::floatfield);
osCase.precision(5);
// Add field information to dictionary
dictionary& fieldsDict = cache_.subDictOrAdd("fields");
dictionary& fieldDict = fieldsDict.subDictOrAdd(fieldName);
if (fieldDict.empty())
if (verbose_)
{
fieldDict.set("type", ensightPTraits<Type>::typeName);
fieldDict.set("name", varName); // ensight variable name
stateChanged = true;
Info<< "Writing case file to " << osCase.name() << endl;
}
// The geometry can be any of the following:
// 0: constant/static
// 1: moving, with the same frequency as the data
// 2: moving, with different frequency as the data
if (stateChanged)
const label tsGeom = geometryTimeset();
osCase
<< "FORMAT" << nl
<< "type: ensight gold" << nl
<< nl
<< "GEOMETRY" << nl;
if (tsGeom)
{
if (verbose_)
{
Info<< "Writing state file to fieldsDict" << endl;
}
{
OFstream os(baseDir/"fieldsDict");
os << "// Summary of Ensight fields, times" << nl << nl;
cache_.write(os, false);
}
OFstream osCase(outputFile, IOstream::ASCII);
// Format options
osCase.setf(ios_base::left);
osCase.setf(ios_base::scientific, ios_base::floatfield);
osCase.precision(5);
if (verbose_)
{
Info<< "Writing case file to " << osCase.name() << endl;
}
// The geometry can be any of the following:
// 0: constant/static
// 1: moving, with the same frequency as the data
// 2: moving, with different frequency as the data
const label tsGeom = geometryTimeset();
// moving
osCase
<< "FORMAT" << nl
<< "type: ensight gold" << nl
<< nl
<< "GEOMETRY" << nl;
if (tsGeom)
{
// moving
osCase
<< "model: " << tsGeom << " " // time-set (1|2)
<< mask << geometryName.name() << nl;
}
else
{
// steady
osCase
<< "model: "
<< geometryName.c_str() << nl;
}
<< "model: " << tsGeom << " " // time-set (1|2)
<< mask << geometryName.name() << nl;
}
else
{
// steady
osCase
<< nl
<< "VARIABLE" << nl;
<< "model: "
<< geometryName.c_str() << nl;
}
osCase
<< nl
<< "VARIABLE" << nl;
for (const entry& dEntry : fieldsDict)
{
const dictionary& subDict = dEntry.dict();
for (const entry& dEntry : caching_.fieldsDict())
{
const dictionary& subDict = dEntry.dict();
const word fieldType(subDict.get<word>("type"));
const word varName
const word varType(subDict.get<word>("type"));
const word varName
(
subDict.getOrDefault<word>
(
subDict.getOrDefault<word>
(
"name",
dEntry.keyword() // fieldName as fallback
)
);
osCase
<< fieldType
<<
(
this->isPointData()
? " per node: 1 " // time-set 1
: " per element: 1 " // time-set 1
)
<< setw(15) << varName << ' '
<< mask << varName << nl;
}
"name",
dEntry.keyword() // fieldName as fallback
)
);
osCase
<< nl
<< "TIME" << nl;
printTimeset(osCase, 1, times_);
if (tsGeom == 2)
{
printTimeset(osCase, 2, times_, meshes_);
}
osCase << "# end" << nl;
<< varType
<<
(
this->isPointData()
? " per node: 1 " // time-set 1
: " per element: 1 " // time-set 1
)
<< setw(15) << varName << ' '
<< mask << varName << nl;
}
osCase
<< nl
<< "TIME" << nl;
printTimeset(osCase, 1, caching_.times());
if (tsGeom == 2)
{
printTimeset
(
osCase,
tsGeom,
caching_.times(),
caching_.geometries()
);
}
osCase << "# end" << nl;
}
@ -403,27 +251,28 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeCollated
mkDir(dataDir);
const fileName meshFile(baseDir/geometryName);
const fileName geomFile(baseDir/geometryName);
// Ensight Geometry
ensightOutputSurface part
(
surf.points(),
surf.faces(),
meshFile.name()
geomFile.name()
);
if (!exists(meshFile))
if (!exists(geomFile))
{
if (verbose_)
{
Info<< "Writing mesh file to " << meshFile.name() << endl;
Info<< "Writing geometry to " << geomFile.name() << endl;
}
// Two-argument form for path-name to avoid validating base-dir
ensightGeoFile osGeom
(
meshFile.path(),
meshFile.name(),
geomFile.path(),
geomFile.name(),
writeFormat_
);
part.write(osGeom); // serial

View File

@ -37,7 +37,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated()
// Uncollated
// ==========
// Geometry: rootdir/<TIME>/surfaceName.case
// CaseFile: rootdir/<TIME>/surfaceName.case
// Geometry: rootdir/<TIME>/surfaceName.00000000.mesh
fileName outputDir;
@ -85,7 +85,7 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated()
<< nl
<< "TIME" << nl;
printTimeset(osCase, 1, 0.0);
printTimeset(osCase, 1, scalar(0));
ensightOutputSurface part
(
@ -116,9 +116,9 @@ Foam::fileName Foam::surfaceWriters::ensightWriter::writeUncollated
// Uncollated
// ==========
// geometry: rootdir/time/<field>/surfaceName.case
// geometry: rootdir/time/<field>/surfaceName.<index>.mesh
// field: rootdir/time/<field>/surfaceName.<index>.<field>
// CaseFile: rootdir/time/<field>/surfaceName.case
// Geometry: rootdir/time/<field>/surfaceName.<index>.mesh
// Field: rootdir/time/<field>/surfaceName.<index>.<field>
// Variable name as sub-directory for results. Eg,
// - VAR1/SURF1.case