From e1240ece7bc74ea73f16d4afe5ce4c409acfa503 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@Germany>
Date: Wed, 5 Oct 2016 12:59:24 +0200
Subject: [PATCH] ENH: add ensightCase management to fileFormats

- part of generalizing the ensight infrastructure (issue #241)

- General bookkeeping when creating ensight files.
---
 src/fileFormats/Make/files                    |   2 +
 src/fileFormats/ensight/file/ensightCase.C    | 739 ++++++++++++++++++
 src/fileFormats/ensight/file/ensightCase.H    | 405 ++++++++++
 src/fileFormats/ensight/file/ensightCaseI.H   |  78 ++
 .../ensight/file/ensightCaseOptions.C         | 129 +++
 .../ensight/file/ensightCaseTemplates.C       |  99 +++
 6 files changed, 1452 insertions(+)
 create mode 100644 src/fileFormats/ensight/file/ensightCase.C
 create mode 100644 src/fileFormats/ensight/file/ensightCase.H
 create mode 100644 src/fileFormats/ensight/file/ensightCaseI.H
 create mode 100644 src/fileFormats/ensight/file/ensightCaseOptions.C
 create mode 100644 src/fileFormats/ensight/file/ensightCaseTemplates.C

diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 91c6affaf2e..c972fde32e7 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -1,3 +1,5 @@
+ensight/file/ensightCase.C
+ensight/file/ensightCaseOptions.C
 ensight/file/ensightFile.C
 ensight/file/ensightGeoFile.C
 ensight/part/ensightCells.C
diff --git a/src/fileFormats/ensight/file/ensightCase.C b/src/fileFormats/ensight/file/ensightCase.C
new file mode 100644
index 00000000000..0ac968e7993
--- /dev/null
+++ b/src/fileFormats/ensight/file/ensightCase.C
@@ -0,0 +1,739 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016 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 "ensightCase.H"
+#include "stringListOps.H"
+#include "Time.H"
+#include "cloud.H"
+#include "IOmanip.H"
+#include "globalIndex.H"
+
+#include "ensightFile.H"
+#include "ensightGeoFile.H"
+#include "demandDrivenData.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+const char* Foam::ensightCase::dataDirName  = "data";
+const char* Foam::ensightCase::geometryName = "geometry";
+
+
+// * * * * * * * * * * * * * Private Functions * * * * * * * * * * * * * * //
+
+Foam::fileName Foam::ensightCase::dataDir() const
+{
+    return ensightDir_/dataDirName;
+}
+
+
+void Foam::ensightCase::initialize()
+{
+    if (Pstream::master())
+    {
+        // EnSight and EnSight/data directories must exist
+
+        // We may wish to retain old data
+        // eg, convert new results or a particular time interva
+        // OR remove everything
+
+        if (options_->overwrite())
+        {
+            rmDir(ensightDir_);
+        }
+        else if (isDir(ensightDir_))
+        {
+            Info<<"Warning: re-using existing directory" << nl
+                << "    " << ensightDir_ << endl;
+        }
+
+        // Create ensight and data directories
+        mkDir(dataDir());
+
+        // The case file is always ASCII
+        os_ = new OFstream(ensightDir_/caseName_, IOstream::ASCII);
+
+        // Format options
+        os_->setf(ios_base::left);
+        os_->setf(ios_base::scientific, ios_base::floatfield);
+        os_->precision(5);
+
+        writeHeader();
+    }
+}
+
+
+Foam::label Foam::ensightCase::checkTimeset(const labelHashSet& lookup) const
+{
+    // assume the worst
+    label ts = -1;
+
+    // work on a copy
+    labelHashSet tsTimes(lookup);
+    tsTimes.erase(-1);
+
+    if (tsTimes.empty())
+    {
+        // no times needed
+        ts = 0;
+    }
+    else if (tsTimes.size() == timesUsed_.size())
+    {
+        forAllConstIter(Map<scalar>, timesUsed_, iter)
+        {
+            tsTimes.erase(iter.key());
+        }
+
+        // OR
+        // tsTimes -= timesUsed_.toc();
+        // tsTimes -= timesUsed_;
+
+        if (tsTimes.empty())
+        {
+            ts = 1; // can use timeset 1
+        }
+    }
+
+    return ts;
+}
+
+
+void Foam::ensightCase::writeHeader() const
+{
+    if (os_)  // master only
+    {
+        this->rewind();
+        *os_
+            << "FORMAT" << nl
+            << "type: ensight gold" << nl;
+    }
+}
+
+
+Foam::scalar Foam::ensightCase::writeTimeset() const
+{
+    const label ts = 1;
+
+    const labelList indices = timesUsed_.sortedToc();
+    label count = indices.size();
+
+    // correct for negative starting values
+    scalar timeCorrection = timesUsed_[indices[0]];
+    if (timeCorrection < 0)
+    {
+        timeCorrection = -timeCorrection;
+        Info<< "Correcting time values. Adding " << timeCorrection << endl;
+    }
+    else
+    {
+        timeCorrection = 0;
+    }
+
+
+    *os_
+        << "time set:               " << ts << nl
+        << "number of steps:        " << count << nl;
+
+    if (indices[0] == 0 && indices[count-1] == count-1)
+    {
+        // looks to be contiguous numbering
+        *os_
+            << "filename start number:  " << 0 << nl
+            << "filename increment:     " << 1 << nl;
+    }
+    else
+    {
+        *os_
+            << "filename numbers:" << nl;
+
+        count = 0;
+        forAll(indices, idx)
+        {
+            *os_ << " " << setw(12) << indices[idx];
+
+            if (++count % 6 == 0)
+            {
+                *os_ << nl;
+            }
+        }
+
+        if (count)
+        {
+            *os_ << nl;
+        }
+    }
+
+
+    *os_ << "time values:" << nl;
+
+    count = 0;
+    forAll(indices, idx)
+    {
+        *os_ << " " << setw(12) << timesUsed_[indices[idx]] + timeCorrection;
+
+        if (++count % 6 == 0)
+        {
+            *os_ << nl;
+        }
+    }
+    if (count)
+    {
+        *os_ << nl;
+    }
+
+    return timeCorrection;
+}
+
+
+void Foam::ensightCase::writeTimeset
+(
+    const label ts,
+    const labelHashSet& lookup,
+    const scalar timeCorrection
+) const
+{
+    // make a copy
+    labelHashSet hashed(lookup);
+    hashed.erase(-1);
+
+    const labelList indices = hashed.sortedToc();
+    label count = indices.size();
+
+    *os_
+        << "time set:               " << ts << nl
+        << "number of steps:        " << count  << nl
+        << "filename numbers:" << nl;
+
+    count = 0;
+    forAll(indices, idx)
+    {
+        *os_ << " " << setw(12) << indices[idx];
+
+        if (++count % 6 == 0)
+        {
+            *os_ << nl;
+        }
+    }
+
+    if (count)
+    {
+        *os_ << nl;
+    }
+
+    *os_ << "time values:" << nl;
+
+    count = 0;
+    forAll(indices, idx)
+    {
+        *os_ << " " << setw(12) << timesUsed_[indices[idx]] + timeCorrection;
+
+        if (++count % 6 == 0)
+        {
+            *os_ << nl;
+        }
+    }
+    if (count)
+    {
+        *os_ << nl;
+    }
+}
+
+
+void Foam::ensightCase::noteGeometry(const bool moving) const
+{
+    if (moving)
+    {
+        geomTimes_.insert(timeIndex_);
+    }
+    else
+    {
+        geomTimes_.insert(-1);
+    }
+
+    changed_ = true;
+}
+
+
+void Foam::ensightCase::noteCloud(const word& cloudName) const
+{
+    if (!cloudVars_.found(cloudName))
+    {
+        cloudVars_.insert(cloudName, HashTable<string>());
+    }
+    cloudTimes_.insert(timeIndex_);
+
+    changed_ = true;
+}
+
+
+void Foam::ensightCase::noteCloud
+(
+    const word& cloudName,
+    const word& varName,
+    const char* ensightType
+) const
+{
+    if (cloudVars_.found(cloudName))
+    {
+        if (cloudVars_[cloudName].insert(varName, ensightType))
+        {
+            changed_ = true;
+        }
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Tried to add a cloud variable for writing without having added a cloud"
+            << abort(FatalError);
+    }
+}
+
+
+void Foam::ensightCase::noteVariable
+(
+    const word& varName,
+    const char* ensightType
+) const
+{
+    if (variables_.insert(varName, ensightType))
+    {
+        changed_ = true;
+    }
+}
+
+
+Foam::autoPtr<Foam::ensightFile>
+Foam::ensightCase::createDataFile
+(
+    const word& name
+) const
+{
+    autoPtr<ensightFile> output;
+
+    if (Pstream::master())
+    {
+        // the data/ITER subdirectory must exist
+        // Note that data/ITER is indeed a valid ensight::FileName
+        const fileName outdir = dataDir()/padded(timeIndex_);
+        mkDir(outdir);
+
+        output.reset(new ensightFile(outdir, name, format()));
+    }
+
+    return output;
+}
+
+
+Foam::autoPtr<Foam::ensightFile>
+Foam::ensightCase::createCloudFile
+(
+    const word& cloudName,
+    const word& name
+) const
+{
+    autoPtr<Foam::ensightFile> output;
+
+    if (Pstream::master())
+    {
+        // Write
+        // eg -> "data/********/lagrangian/<cloudName>/positions"
+        // or -> "lagrangian/<cloudName>/********/positions"
+        // TODO? check that cloudName is a valid ensight filename
+        const fileName outdir =
+        (
+            separateCloud()
+          ? (ensightDir_ / cloud::prefix / cloudName / padded(timeIndex_))
+          : (dataDir() / padded(timeIndex_) / cloud::prefix / cloudName)
+        );
+
+        mkDir(outdir); // should be unnecessary after newCloud()
+
+        output.reset(new ensightFile(outdir, name, format()));
+    }
+
+    return output;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ensightCase::ensightCase
+(
+    const fileName& ensightDir,
+    const word& caseName,
+    const ensightCase::options& opts
+)
+:
+    options_(new options(opts)),
+    ensightDir_(ensightDir),
+    caseName_(caseName + ".case"),
+    os_(nullptr),
+    changed_(false),
+    timeIndex_(0),
+    timeValue_(0),
+    timesUsed_(),
+    geomTimes_(),
+    cloudTimes_(),
+    variables_(),
+    cloudVars_()
+{
+    initialize();
+}
+
+
+Foam::ensightCase::ensightCase
+(
+    const fileName& ensightDir,
+    const word& caseName,
+    const IOstream::streamFormat format
+)
+:
+    options_(new options(format)),
+    ensightDir_(ensightDir),
+    caseName_(caseName + ".case"),
+    os_(nullptr),
+    changed_(false),
+    timeIndex_(0),
+    timeValue_(0),
+    timesUsed_(),
+    geomTimes_(),
+    cloudTimes_(),
+    variables_(),
+    cloudVars_()
+{
+    initialize();
+}
+
+
+// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
+
+Foam::ensightCase::~ensightCase()
+{
+    deleteDemandDrivenData(options_);
+    deleteDemandDrivenData(os_);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::ensightCase::nextTime(const scalar value)
+{
+    // use next available index
+    setTime(value, timesUsed_.size());
+}
+
+
+void Foam::ensightCase::nextTime(const instant& t)
+{
+    nextTime(t.value());
+}
+
+
+void Foam::ensightCase::setTime(const scalar value, const label index)
+{
+    timeIndex_ = index;
+    timeValue_ = value;
+
+    if (Pstream::master())
+    {
+        // The data/ITER subdirectory must exist
+        // Note that data/ITER is indeed a valid ensight::FileName
+
+        const fileName outdir = dataDir()/padded(timeIndex_);
+        mkDir(outdir);
+
+        // place a timestamp in the directory for future reference
+        OFstream timeStamp(outdir/"time");
+        timeStamp
+            << "#  index  time" << nl
+            << outdir.name() << ' ' << timeValue_ << nl;
+    }
+
+    // Record of time index/value used
+    timesUsed_.set(index, value);
+}
+
+
+void Foam::ensightCase::setTime(const instant& t, const label index)
+{
+    setTime(t.value(), index);
+}
+
+
+void Foam::ensightCase::write() const
+{
+    if (!os_) return; // master only
+
+    // geometry timeset
+    bool staticGeom = (geomTimes_.size() == 1 && geomTimes_.found(-1));
+    label tsGeom = staticGeom ? 0 : checkTimeset(geomTimes_);
+
+    // cloud timeset
+    label tsCloud = checkTimeset(cloudTimes_);
+
+    // increment time-sets to the correct indices
+    if (tsGeom < 0)
+    {
+        tsGeom = 2;             // next available timeset
+    }
+    if (tsCloud < 0)
+    {
+        tsCloud = tsGeom + 1;   // next available timeset
+    }
+
+    writeHeader();
+
+
+    // data mask: eg "data/******"
+    const fileName dataMask = (dataDirName/mask());
+
+    //
+    // GEOMETRY
+    //
+    if (!geomTimes_.empty() || !cloudTimes_.empty())
+    {
+        // start of variables
+        *os_
+            << nl
+            << "GEOMETRY" << nl;
+    }
+
+    if (staticGeom)
+    {
+        // steady
+        *os_
+            << setw(16)  << "model:"
+            << geometryName
+            << nl;
+    }
+    else if (!geomTimes_.empty())
+    {
+        // moving
+        *os_
+            << Foam::name("model: %-9d", tsGeom)        // width 16
+            << (dataMask/geometryName).c_str()
+            << nl;
+    }
+
+    // clouds and cloud variables
+    const wordList cloudNames = cloudVars_.sortedToc();
+    forAll(cloudNames, cloudNo)
+    {
+        const word& cloudName = cloudNames[cloudNo];
+
+        const fileName masked =
+        (
+            separateCloud()
+          ? (cloud::prefix / cloudName / mask())
+          : (dataMask / cloud::prefix / cloudName)
+        );
+
+        *os_
+            << Foam::name("measured: %-6d", tsCloud)    // width 16
+            << (masked/"positions").c_str()
+            << nl;
+    }
+
+
+    //
+    // VARIABLE
+    //
+    if (variables_.size() || cloudVars_.size())
+    {
+        // start of variables
+        *os_
+            << nl
+            << "VARIABLE" << nl;
+    }
+
+
+    // field variables (always use timeset 1)
+    const wordList varNames = variables_.sortedToc();
+    forAll(varNames, vari)
+    {
+        const word&   varName = varNames[vari];
+        const string& ensType = variables_[varName];
+
+        *os_
+            << ensType.c_str()
+            <<
+            (
+                nodeValues()
+              ? " per node:    1  "  // time-set 1
+              : " per element: 1  "  // time-set 1
+            )
+            << setw(15) << varName << ' '
+            << (dataMask/varName).c_str() << nl;
+    }
+
+
+    // clouds and cloud variables (using cloud timeset)
+    // Write
+    // as -> "data/********/lagrangian/<cloudName>/positions"
+    // or -> "lagrangian/<cloudName>/********/positions"
+    forAll(cloudNames, cloudNo)
+    {
+        const word& cloudName = cloudNames[cloudNo];
+        const fileName masked =
+        (
+            separateCloud()
+          ? (cloud::prefix / cloudName / mask())
+          : (dataMask / cloud::prefix / cloudName)
+        );
+
+        const HashTable<string>& vars = cloudVars_[cloudName];
+        const wordList tocVars = vars.sortedToc();
+
+        forAll(tocVars, vari)
+        {
+            const word&   varName = tocVars[vari];
+            const string& ensType = vars[varName];
+
+            // prefix variables with 'c' (cloud) and cloud index
+            *os_
+                << ensType.c_str() << " per "
+                << Foam::name("measured node: %-5d", tsCloud) // width 20
+                << setw(15)
+                << ("c" + Foam::name(cloudNo) + varName).c_str() << ' '
+                << (masked/varName).c_str()
+                << nl;
+        }
+    }
+
+
+    //
+    // TIME
+    //
+
+    if (!timesUsed_.empty())
+    {
+        *os_
+            << nl << "TIME" << nl;
+
+        // timeset 1
+        const scalar timeCorrection = writeTimeset();
+
+        // timeset geometry
+        if (tsGeom > 1)
+        {
+            writeTimeset(tsGeom, geomTimes_, timeCorrection);
+        }
+
+        // timeset cloud
+        if (tsCloud > 1)
+        {
+            writeTimeset(tsCloud, cloudTimes_, timeCorrection);
+        }
+
+        *os_
+            << "# end" << nl;
+    }
+
+    *os_ << flush;
+    changed_ = false;
+}
+
+
+Foam::autoPtr<Foam::ensightGeoFile>
+Foam::ensightCase::newGeometry
+(
+    const bool moving
+) const
+{
+    autoPtr<Foam::ensightGeoFile> output;
+
+    if (Pstream::master())
+    {
+        // set the path of the ensight file
+        fileName path;
+
+        if (moving)
+        {
+            // Moving mesh: write as "data/********/geometry"
+            path = dataDir()/padded(timeIndex_);
+            mkDir(path);
+        }
+        else
+        {
+            // Static mesh: write as "geometry"
+            path = ensightDir_;
+        }
+
+        output.reset(new ensightGeoFile(path, geometryName, format()));
+
+        noteGeometry(moving);   // note for later use
+    }
+
+    return output;
+}
+
+
+Foam::autoPtr<Foam::ensightFile>
+Foam::ensightCase::newCloud
+(
+    const word& cloudName
+) const
+{
+    autoPtr<Foam::ensightFile> output;
+
+    if (Pstream::master())
+    {
+        output = createCloudFile(cloudName, "positions");
+
+        // tag binary format (just like geometry files)
+        output().writeBinaryHeader();
+
+        // description
+        output().write(cloud::prefix/cloudName);
+        output().newline();
+
+        noteCloud(cloudName);   // note for later use
+    }
+
+    return output;
+}
+
+
+void Foam::ensightCase::rewind() const
+{
+    if (os_)  // master only
+    {
+        os_->stdStream().seekp(0, std::ios_base::beg);
+    }
+}
+
+
+Foam::Ostream& Foam::ensightCase::printInfo(Ostream& os) const
+{
+    os  << "Ensight case:" << nl
+        << "   path: "   << ensightDir_ << nl
+        << "   name: "   << caseName_   << nl
+        << "   format: " << format()    << nl
+        << "   values per " << (nodeValues() ? "node" : "element") << nl;
+
+    return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/file/ensightCase.H b/src/fileFormats/ensight/file/ensightCase.H
new file mode 100644
index 00000000000..e6603d9a104
--- /dev/null
+++ b/src/fileFormats/ensight/file/ensightCase.H
@@ -0,0 +1,405 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016 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/>.
+
+Class
+    Foam::ensightCase
+
+Description
+    Supports writing of ensight cases as well as providing common factory
+    methods to open new files.
+
+SourceFiles
+    ensightCase.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ensightCase_H
+#define ensightCase_H
+
+#include "autoPtr.H"
+#include "HashSet.H"
+#include "InfoProxy.H"
+#include "Map.H"
+#include "OSspecific.H"
+#include "Pstream.H"
+
+#include "ensightGeoFile.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward declarations
+class ensightCase;
+class instant;
+class Time;
+
+/*---------------------------------------------------------------------------*\
+                         Class ensightCase Declaration
+\*---------------------------------------------------------------------------*/
+
+class ensightCase
+{
+public:
+
+    // Forward declarations
+    class options;
+
+    // Public Data
+
+        //- The name for "data" subdirectory
+        static const char* dataDirName;
+
+        //- The name for geometry files
+        static const char* geometryName;
+
+private:
+
+    // Private data
+
+        //- Case writing options
+        const options* options_;
+
+        //- Output path (absolute)
+        fileName ensightDir_;
+
+        //- Case name (with ".case" ending)
+        word caseName_;
+
+        //- Output stream
+        mutable OFstream* os_;
+
+        //- Track state changes since last write
+        mutable bool changed_;
+
+        //- Time index (timeset 1)
+        label timeIndex_;
+
+        //- Time value (timeset 1)
+        scalar timeValue_;
+
+        //- Record of time index/value used (eg, field values).
+        //  These values will be used for timeset 1.
+        Map<scalar> timesUsed_;
+
+        //- Record time indices when geometry is written.
+        //  These values will be used to decide if timeset 1
+        //  or a separate timeset are used.
+        //  The special index '-1' is used static geometry.
+        mutable labelHashSet geomTimes_;
+
+        //- Record time indices when clouds are written.
+        //  These values will be used to decide if timeset 1
+        //  or a separate timeset are used.
+        mutable labelHashSet cloudTimes_;
+
+        //- Fields/Variables with the ensight type
+        mutable HashTable<string> variables_;
+
+        //- Cloud names and variables
+        mutable HashTable<HashTable<string>> cloudVars_;
+
+
+    // Private Member Functions
+
+        //- The data directory
+        fileName dataDir() const;
+
+        //- Initial file management (master only)
+        void initialize();
+
+        //- Check if timeset uses different times than from time-set 1
+        label checkTimeset(const labelHashSet& lookup) const;
+
+        //- Write the header into the case file.
+        void writeHeader() const;
+
+        //- Write the timeset 1 into the case file.
+        //  Return the time correction in effect
+        scalar writeTimeset() const;
+
+        //- Write the timeset into the case file.
+        void writeTimeset
+        (
+            const label ts,
+            const labelHashSet& lookup,
+            const scalar timeCorrection = 0
+        ) const;
+
+
+        //- Note geometry being used
+        void noteGeometry(const bool moving) const;
+
+        //- Note cloud being used
+        void noteCloud(const word& cloudName) const;
+
+        //- Note cloud/variable being used
+        void noteCloud
+        (
+            const word& cloudName,
+            const word& varName,
+            const char* ensightType
+        ) const;
+
+        //- Note field variable being used
+        void noteVariable
+        (
+            const word& varName,
+            const char* ensightType
+        ) const;
+
+
+        //- Open stream for new data file (on master), using the current index.
+        //  File is without initial description lines.
+        autoPtr<ensightFile> createDataFile(const word&) const;
+
+        //- Open stream for new cloud file (on master).
+        //  File is without initial description lines.
+        autoPtr<ensightFile> createCloudFile
+        (
+            const word& cloudName,
+            const word& name
+        ) const;
+
+
+        //- Disallow default bitwise copy construct
+        ensightCase(const ensightCase&) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const ensightCase&) = delete;
+
+
+public:
+
+
+    // Constructors
+
+        //- Construct from components
+        ensightCase
+        (
+            const fileName& ensightDir,
+            const word& caseName,
+            const options& opts
+        );
+
+        //- Construct from components with all default options
+        ensightCase
+        (
+            const fileName& ensightDir,
+            const word& caseName,
+            const IOstream::streamFormat format = IOstream::BINARY
+        );
+
+
+
+    //- Destructor
+    ~ensightCase();
+
+
+    // Member Functions
+
+    // Access
+
+        //- Reference to the case options
+        inline const ensightCase::options& option() const;
+
+        //- Ascii/Binary file output
+        inline IOstream::streamFormat format() const;
+
+        //- The nominal path to the case file
+        inline const fileName& path() const;
+
+        //- The output '*' mask
+        inline const word& mask() const;
+
+        //- Consistent zero-padded integer value
+        inline word padded(const label i) const;
+
+        //- Use values per nodes instead of per element
+        inline bool nodeValues() const;
+
+        //- Write clouds into their own directory instead in "data" directory
+        inline bool separateCloud() const;
+
+
+    // Edit
+
+        //- Set time for time-set 1, using next available index.
+        //  Create corresponding sub-directory.
+        //  Do not mix between nextTime and setTime in an application.
+        void nextTime(const scalar t);
+
+        //- Set time for time-set 1, using next available index.
+        //  Create corresponding sub-directory.
+        //  Do not mix between nextTime and setTime in an application.
+        void nextTime(const instant& t);
+
+        //- Set current index and time for time-set 1.
+        //  Create corresponding sub-directory
+        //  Do not mix between nextTime and setTime in an application.
+        void setTime(const scalar t, const label index);
+
+        //- Set current index and time for time-set 1.
+        //  Create corresponding sub-directory
+        //  Do not mix between nextTime and setTime in an application.
+        void setTime(const instant& t, const label index);
+
+
+    // Addition of entries to case file
+
+        //- Open stream for new geometry file (on master).
+        autoPtr<ensightGeoFile> newGeometry(const bool moving = false) const;
+
+
+        //- Open stream for new cloud positions (on master).
+        //  Note the use of ensightFile, not ensightGeoFile.
+        autoPtr<ensightFile> newCloud
+        (
+            const word& cloudName
+        ) const;
+
+
+        //- Open stream for new data file (on master), using the current index.
+        template<class Type>
+        autoPtr<ensightFile> newData(const word& varName) const;
+
+
+        //- Open stream for new cloud data file (on master), using the current index.
+        template<class Type>
+        autoPtr<ensightFile> newCloudData
+        (
+            const word& cloudName,
+            const word& varName
+        ) const;
+
+
+    // Output
+
+        //- Rewind the output stream (master only).
+        void rewind() const;
+
+        //- Write the case file
+        void write() const;
+
+        //- Output stream (master only).
+        inline Ostream& operator()() const;
+
+        //- Print some general information.
+        Ostream& printInfo(Ostream&) const;
+};
+
+
+//- Configuration options for the ensightCase
+class ensightCase::options
+{
+private:
+
+    //- Ascii/Binary file output
+    IOstream::streamFormat format_;
+
+    //- Width of mask for subdirectories
+    label width_;
+
+    //- The '*' mask appropriate for subdirectories
+    word mask_;
+
+    //- The printf format for zero-padded subdirectory numbers
+    string printf_;
+
+    //- Remove existing directory and sub-directories on creation
+    bool overwrite_;
+
+    //- Write values at nodes
+    bool nodeValues_;
+
+    //- Write clouds into their own directory
+    bool separateCloud_;
+
+public:
+
+    // Constructors
+
+        //- Construct with the specified format (default is binary)
+        options(IOstream::streamFormat format = IOstream::BINARY);
+
+
+    // Member Functions
+
+    // Access
+
+        //- Ascii/Binary file output
+        IOstream::streamFormat format() const;
+
+        //- The '*' mask appropriate for sub-directories
+        const word& mask() const;
+
+        //- Consistent zero-padded integer value
+        word padded(const label i) const;
+
+        //- Return current width of mask and padded.
+        label width() const;
+
+        //- Remove existing directory and sub-directories on creation
+        bool overwrite() const;
+
+        //- Use values per nodes instead of per element
+        bool nodeValues() const;
+
+        //- Write clouds into their own directory instead in "data" directory
+        bool separateCloud() const;
+
+
+    // Edit
+
+        //- Set width of mask and padded.
+        //  Default width is 8 digits, max width is 31 digits.
+        void width(const label i);
+
+        //- Remove existing directory and sub-directories on creation
+        void overwrite(bool);
+
+        //- Use values per nodes instead of per element
+        void nodeValues(bool);
+
+        //- Write clouds into their own directory instead in "data" directory
+        void separateCloud(bool);
+
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "ensightCaseI.H"
+
+#ifdef NoRepository
+    #include "ensightCaseTemplates.C"
+#endif
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/file/ensightCaseI.H b/src/fileFormats/ensight/file/ensightCaseI.H
new file mode 100644
index 00000000000..b386f677b5b
--- /dev/null
+++ b/src/fileFormats/ensight/file/ensightCaseI.H
@@ -0,0 +1,78 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016 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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+inline const Foam::ensightCase::options& Foam::ensightCase::option() const
+{
+    return *options_;
+}
+
+
+inline Foam::IOstream::streamFormat Foam::ensightCase::format() const
+{
+    return options_->format();
+}
+
+
+inline const Foam::fileName& Foam::ensightCase::path() const
+{
+    return ensightDir_;
+}
+
+
+inline const Foam::word& Foam::ensightCase::mask() const
+{
+    return options_->mask();
+}
+
+
+inline Foam::word Foam::ensightCase::padded(const label i) const
+{
+    return options_->padded(i);
+}
+
+
+inline bool Foam::ensightCase::nodeValues() const
+{
+    return options_->nodeValues();
+}
+
+
+inline bool Foam::ensightCase::separateCloud() const
+{
+    return options_->separateCloud();
+}
+
+
+// * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
+
+inline Foam::Ostream& Foam::ensightCase::operator()() const
+{
+    return *os_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/file/ensightCaseOptions.C b/src/fileFormats/ensight/file/ensightCaseOptions.C
new file mode 100644
index 00000000000..7977334466c
--- /dev/null
+++ b/src/fileFormats/ensight/file/ensightCaseOptions.C
@@ -0,0 +1,129 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016 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 "ensightCase.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ensightCase::options::options(IOstream::streamFormat format)
+:
+    format_(format),
+    width_(0),
+    mask_(),
+    printf_(),
+    overwrite_(false),
+    nodeValues_(false),
+    separateCloud_(false)
+{
+    width(8); // ensures that the mask and printf-format are also resized
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::IOstream::streamFormat Foam::ensightCase::options::format() const
+{
+    return format_;
+}
+
+const Foam::word& Foam::ensightCase::options::mask() const
+{
+    return mask_;
+}
+
+
+Foam::word Foam::ensightCase::options::padded(const label i) const
+{
+    // As per Foam::name, but with fixed length
+    char buf[32];
+
+    ::snprintf(buf, 32, printf_.c_str(), i);
+    buf[31] = 0;
+
+    // no stripping required
+    return word(buf, false);
+}
+
+
+Foam::label Foam::ensightCase::options::width() const
+{
+    return width_;
+}
+
+
+void Foam::ensightCase::options::width(const label n)
+{
+    // enforce min/max sanity limits
+    if (n < 1 || n > 31)
+    {
+        return;
+    }
+
+    // set mask accordingly
+    mask_.resize(n, '*');
+
+    // appropriate printf format
+    printf_ = "%0" + Foam::name(n) + "d";
+}
+
+
+
+bool Foam::ensightCase::options::overwrite() const
+{
+    return overwrite_;
+}
+
+
+void Foam::ensightCase::options::overwrite(bool b)
+{
+    overwrite_ = b;
+}
+
+
+bool Foam::ensightCase::options::nodeValues() const
+{
+    return nodeValues_;
+}
+
+
+void Foam::ensightCase::options::nodeValues(bool b)
+{
+    nodeValues_ = b;
+}
+
+
+bool Foam::ensightCase::options::separateCloud() const
+{
+    return separateCloud_;
+}
+
+
+void Foam::ensightCase::options::separateCloud(bool b)
+{
+    separateCloud_ = b;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/ensight/file/ensightCaseTemplates.C b/src/fileFormats/ensight/file/ensightCaseTemplates.C
new file mode 100644
index 00000000000..48d7c03ec37
--- /dev/null
+++ b/src/fileFormats/ensight/file/ensightCaseTemplates.C
@@ -0,0 +1,99 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | Copyright (C) 2016 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 "cloud.H"
+#include "ensightPTraits.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+
+template<class Type>
+Foam::autoPtr<Foam::ensightFile>
+Foam::ensightCase::newData
+(
+    const word& name
+) const
+{
+    autoPtr<ensightFile> output;
+
+    if (Pstream::master())
+    {
+        const ensight::VarName varName(name);
+        output = createDataFile(varName);
+
+        // description
+        output().write
+        (
+            string
+            (
+                padded(timeIndex_) / varName
+              + " <" + pTraits<Type>::typeName + ">"
+            )
+        );
+        output().newline();
+
+        // note variable for later use
+        noteVariable(varName, ensightPTraits<Type>::typeName);
+    }
+
+    return output;
+}
+
+
+template<class Type>
+Foam::autoPtr<Foam::ensightFile>
+Foam::ensightCase::newCloudData
+(
+    const word& cloudName,
+    const word& name
+) const
+{
+    autoPtr<Foam::ensightFile> output;
+
+    if (Pstream::master())
+    {
+        const ensight::VarName varName(name);
+        output = createCloudFile(cloudName, varName);
+
+        // description
+        output().write
+        (
+            string
+            (
+                padded(timeIndex_) / cloudName / varName
+              + " <" + pTraits<Type>::typeName + ">"
+            )
+        );
+        output().newline();
+
+        // note cloud variable for later use
+        noteCloud(cloudName, varName, ensightPTraits<Type>::typeName);
+    }
+
+    return output;
+}
+
+
+// ************************************************************************* //
-- 
GitLab