diff --git a/applications/test/vtmWriter/Make/files b/applications/test/vtmWriter/Make/files
new file mode 100644
index 0000000000..4a16ec0216
--- /dev/null
+++ b/applications/test/vtmWriter/Make/files
@@ -0,0 +1,3 @@
+Test-vtmWriter.C
+
+EXE = $(FOAM_APPBIN)/Test-vtmWriter
diff --git a/applications/test/vtmWriter/Make/options b/applications/test/vtmWriter/Make/options
new file mode 100644
index 0000000000..7ce182425d
--- /dev/null
+++ b/applications/test/vtmWriter/Make/options
@@ -0,0 +1,5 @@
+EXE_INC = \
+ -I$(LIB_SRC)/fileFormats/lnInclude
+
+EXE_LIBS = \
+ -lfileFormats
diff --git a/applications/test/vtmWriter/Test-vtmWriter.C b/applications/test/vtmWriter/Test-vtmWriter.C
new file mode 100644
index 0000000000..234268d779
--- /dev/null
+++ b/applications/test/vtmWriter/Test-vtmWriter.C
@@ -0,0 +1,159 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 .
+
+Application
+ Test-vtmWriter
+
+Description
+ Basic functionality tests for vtk::vtmWriter
+
+\*---------------------------------------------------------------------------*/
+
+#include "foamVtmWriter.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+ vtk::vtmWriter writer1;
+ {
+ fileName base = "region1_0001";
+
+ writer1.beginBlock("internal");
+ writer1.append_vtu
+ (
+ base/"internal"
+ );
+ writer1.endBlock("internal");
+
+ {
+ writer1.beginBlock("boundary");
+ writer1.append_vtp
+ (
+ base/"patch0"
+ );
+ writer1.append(""); // bad entry
+ writer1.append_vtp
+ (
+ base/"patch1"
+ );
+ writer1.append_vtp
+ (
+ base/"patch2"
+ );
+ }
+
+ writer1.endBlock("boundary");
+
+ {
+ writer1.beginBlock("empty");
+ writer1.endBlock("empty");
+ }
+ {
+ writer1.beginBlock("dangling1");
+ writer1.beginBlock("dangling2");
+ }
+ }
+
+ Info<< nl << "vtm information" << nl;
+ writer1.dump(Info),
+ Info<< nl;
+
+// writer1.repair();
+//
+// Info<< nl << "vtm information - after repair" << nl;
+// writer1.dump(Info),
+// Info<< nl;
+
+ writer1.repair(true);
+
+// Info<< nl << "vtm information - after repair(collapse)" << nl;
+// writer1.dump(Info),
+// Info<< nl;
+//
+// Info<< nl << "vtm information - after repair(collapse)" << nl;
+// writer1.dump(Info),
+// Info<< nl;
+
+ Info<< nl << "Write to file" << nl;
+ writer1.write("vtmWriter1.vtm");
+
+
+ vtk::vtmWriter writer2;
+ {
+ fileName base = "region2_0001";
+
+ writer2.beginBlock("internal");
+ writer2.append_vtu
+ (
+ base/"internal"
+ );
+ writer2.endBlock("internal");
+
+ {
+ writer2.beginBlock("boundary");
+ writer2.append_vtp
+ (
+ base/"patch0"
+ );
+ writer2.append(""); // bad entry
+ writer2.append_vtp
+ (
+ base/"patch1"
+ );
+ writer2.append_vtp
+ (
+ base/"patch2"
+ );
+ }
+
+ writer2.endBlock("boundary");
+
+ // These should be automatically skiped
+ writer2.endBlock();
+ writer2.endBlock();
+ writer2.endBlock();
+ writer2.endBlock();
+ }
+
+ Info<< nl << "vtm information" << nl;
+ writer2.dump(Info);
+
+ writer2.repair(true);
+
+
+ vtk::vtmWriter writer3;
+
+ writer3.add("some-region1", writer1);
+ writer3.add("some-region2", writer2);
+
+ Info<< nl << "Combined:" << nl;
+ writer3.dump(Info);
+
+ return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 678c9c418e..640d98882b 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -17,6 +17,7 @@ stl/STLAsciiParseManual.C
stl/STLAsciiParseRagel.C
vtk/file/foamVtkFileWriter.C
+vtk/file/foamVtmWriter.C
vtk/core/foamVtkCore.C
vtk/core/foamVtkPTraits.C
diff --git a/src/fileFormats/vtk/file/foamVtmWriter.C b/src/fileFormats/vtk/file/foamVtmWriter.C
new file mode 100644
index 0000000000..d935c22c76
--- /dev/null
+++ b/src/fileFormats/vtk/file/foamVtmWriter.C
@@ -0,0 +1,694 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 .
+
+\*---------------------------------------------------------------------------*/
+
+#include
+#include "foamVtmWriter.H"
+#include "Time.H"
+#include "OSspecific.H"
+
+// * * * * * * * * * * * * * * * * Local Class * * * * * * * * * * * * * * * //
+
+void Foam::vtk::vtmWriter::vtmEntry::clear()
+{
+ type_ = NONE;
+ name_.clear();
+ file_.clear();
+}
+
+
+bool Foam::vtk::vtmWriter::vtmEntry::good() const
+{
+ return
+ (
+ type_ == vtmEntry::BEGIN_BLOCK
+ || type_ == vtmEntry::END_BLOCK
+ || (type_ == vtmEntry::DATA && file_.size())
+ );
+}
+
+
+bool Foam::vtk::vtmWriter::vtmEntry::write(vtk::formatter& format) const
+{
+ if (type_ == vtmEntry::BEGIN_BLOCK)
+ {
+ format.openTag(vtk::fileTag::BLOCK);
+ if (name_.size())
+ {
+ format.xmlAttr("name", name_);
+ }
+ format.closeTag();
+
+ return true;
+ }
+ else if (type_ == vtmEntry::END_BLOCK)
+ {
+ format.endBlock();
+ return true;
+ }
+ else if (type_ == vtmEntry::DATA && file_.size())
+ {
+ format.openTag(vtk::fileTag::DATA_SET);
+
+ if (name_.size())
+ {
+ format.xmlAttr("name", name_);
+ }
+
+ format.xmlAttr("file", file_);
+
+ format.closeTag(true); // Empty tag. ie,
+ return true;
+ }
+
+ return false;
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+bool Foam::vtk::vtmWriter::pruneEmpty()
+{
+ const label nEntries = entries_.size();
+
+ label dst=0;
+
+ for (label src=0; src < nEntries; ++src)
+ {
+ if (entries_[src].good())
+ {
+ if (dst != src)
+ {
+ entries_[dst] = std::move(entries_[src]);
+ }
+ ++dst;
+ }
+ }
+
+ const bool changed = (dst != nEntries);
+ entries_.resize(dst);
+
+ return changed;
+}
+
+
+bool Foam::vtk::vtmWriter::pruneEmptyBlocks()
+{
+ bool pruned = false;
+
+ const label nEntries = entries_.size();
+
+ while (true)
+ {
+ bool changed = false;
+
+ for (label i=0; i < nEntries; ++i)
+ {
+ vtmEntry& e = entries_[i];
+
+ if (e.isType(vtmEntry::BEGIN_BLOCK))
+ {
+ for (label j=i+1; j < nEntries; ++j)
+ {
+ if (entries_[j].isType(vtmEntry::END_BLOCK))
+ {
+ e.clear();
+ entries_[j].clear();
+
+ changed = true;
+ break;
+ }
+ else if (!entries_[j].isType(vtmEntry::NONE))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if (changed)
+ {
+ pruned = true;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // Collapse single-entry blocks when the names allow it
+
+ // Transcribe, removing NONE entries
+ pruneEmpty();
+
+ return pruned;
+}
+
+
+bool Foam::vtk::vtmWriter::collapseBlocks()
+{
+ bool collapsed = false;
+
+ const label nEntries = entries_.size();
+
+ for (label i=0; i < nEntries-2; ++i)
+ {
+ vtmEntry& b = entries_[i]; // begin
+ vtmEntry& d = entries_[i+1]; // data
+ vtmEntry& e = entries_[i+2]; // end
+
+ if
+ (
+ b.isType(vtmEntry::BEGIN_BLOCK)
+ && e.isType(vtmEntry::END_BLOCK)
+ && d.isType(vtmEntry::DATA)
+ && (d.name_.empty() || d.name_ == b.name_)
+ )
+ {
+ d.name_ = std::move(b.name_);
+
+ b.clear();
+ e.clear();
+
+ collapsed = true;
+ }
+ }
+
+ pruneEmpty();
+
+ return collapsed;
+}
+
+
+void Foam::vtk::vtmWriter::repair(bool collapse)
+{
+ // Add or remove END_BLOCK
+
+ label depth = 0;
+ label nEntries = 0;
+
+ for (vtmEntry& e : entries_)
+ {
+ if (e.isType(vtmEntry::BEGIN_BLOCK))
+ {
+ ++depth;
+ }
+ else if (e.isType(vtmEntry::END_BLOCK))
+ {
+ --depth;
+
+ if (depth < 0)
+ {
+ // Truncate now and exit
+ entries_.resize(nEntries);
+ break;
+ }
+ }
+ else if (e.isType(vtmEntry::DATA))
+ {
+ if (e.file_.empty())
+ {
+ // Bad entry - reset to NONE
+ e.clear();
+ }
+ }
+
+ ++nEntries;
+ }
+
+ // Close any dangling blocks
+ while (depth--)
+ {
+ entries_.append(vtmEntry::endblock());
+ }
+
+ blocks_.clear();
+ pruneEmpty();
+
+ if (collapse)
+ {
+ pruneEmptyBlocks();
+ collapseBlocks();
+ }
+}
+
+
+void Foam::vtk::vtmWriter::dump(Ostream& os) const
+{
+ label depth = 0;
+
+ // Output format is a mix of dictionary and JSON
+ // the only purpose being for diagnostics
+
+ for (const vtmEntry& e : entries_)
+ {
+ switch (e.type_)
+ {
+ case vtmEntry::NONE:
+ {
+ os.indent();
+ os << "none" << nl;
+ break;
+ }
+ case vtmEntry::DATA:
+ {
+ os.indent();
+ os << "{ \"name\" : " << e.name_
+ << ", \"file\" : " << e.file_ << " }" << nl;
+ break;
+ }
+ case vtmEntry::BEGIN_BLOCK:
+ {
+ ++depth;
+ os.beginBlock(e.name_);
+ break;
+ }
+ case vtmEntry::END_BLOCK:
+ {
+ --depth;
+ os.endBlock();
+ os << nl;
+ break;
+ }
+ }
+ }
+
+ for (label i=0; i < depth; ++i)
+ {
+ os.decrIndent();
+ }
+
+ if (depth > 0)
+ {
+ os << "# Had " << depth << " unclosed blocks" << nl;
+ }
+ if (depth < 0)
+ {
+ os << "# Had " << (-depth) << " too many end blocks" << nl;
+ }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::vtk::vtmWriter::vtmWriter()
+:
+ vtmWriter(true, false)
+{}
+
+
+Foam::vtk::vtmWriter::vtmWriter(bool autoName)
+:
+ vtmWriter(autoName, false)
+{}
+
+
+Foam::vtk::vtmWriter::vtmWriter(bool autoName, bool autoCollapse)
+:
+ autoName_(autoName),
+ autoCollapse_(autoCollapse),
+ hasTime_(false),
+ entries_(),
+ blocks_(),
+ timeValue_(Zero)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+void Foam::vtk::vtmWriter::clear()
+{
+ entries_.clear();
+ blocks_.clear();
+
+ timeValue_ = Zero;
+ hasTime_ = false;
+}
+
+
+bool Foam::vtk::vtmWriter::empty() const
+{
+ for (const auto& e : entries_)
+ {
+ if (e.isType(vtmEntry::DATA) && e.name_.size())
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+Foam::label Foam::vtk::vtmWriter::size() const
+{
+ label ndata = 0;
+
+ for (const auto& e : entries_)
+ {
+ if (e.isType(vtmEntry::DATA) && e.file_.size())
+ {
+ ++ndata;
+ }
+ }
+
+ return ndata;
+}
+
+
+void Foam::vtk::vtmWriter::setTime(scalar timeValue)
+{
+ timeValue_ = timeValue;
+ hasTime_ = true;
+}
+
+
+void Foam::vtk::vtmWriter::setTime(const Time& t)
+{
+ timeValue_ = t.value();
+ hasTime_ = true;
+}
+
+
+Foam::label Foam::vtk::vtmWriter::beginBlock(const word& blockName)
+{
+ entries_.append(vtmEntry::block(blockName));
+ blocks_.append(blockName);
+
+ return blocks_.size();
+}
+
+
+Foam::label Foam::vtk::vtmWriter::endBlock(const word& blockName)
+{
+ label nblock = blocks_.size();
+
+ if (nblock)
+ {
+ const word curr(blocks_.remove());
+
+ // Verify expected end tag
+ if (!blockName.empty() && blockName != curr)
+ {
+ WarningInFunction
+ << "expecting to end block '" << blockName
+ << "' but found '" << curr << "' instead"
+ << endl;
+ }
+
+ entries_.append(vtmEntry::endblock());
+ }
+
+ return blocks_.size();
+}
+
+
+bool Foam::vtk::vtmWriter::append(const fileName& file)
+{
+ if (autoName_)
+ {
+ return append(fileName::nameLessExt(file), file);
+ }
+ else
+ {
+ return append(word::null, file);
+ }
+}
+
+
+bool Foam::vtk::vtmWriter::append
+(
+ const fileName& file,
+ vtk::fileTag contentType
+)
+{
+ if (autoName_)
+ {
+ return append(fileName::nameLessExt(file), file, contentType);
+ }
+ else
+ {
+ return append(word::null, file, contentType);
+ }
+}
+
+
+bool Foam::vtk::vtmWriter::append
+(
+ const word& name,
+ const fileName& file
+)
+{
+ if (file.empty())
+ {
+ return false;
+ }
+
+ entries_.append(vtmEntry::entry(name, file));
+ return true;
+}
+
+
+bool Foam::vtk::vtmWriter::append
+(
+ const word& name,
+ const fileName& file,
+ vtk::fileTag contentType
+)
+{
+ if (file.empty())
+ {
+ return false;
+ }
+
+ if (file.hasExt(vtk::fileExtension[contentType]))
+ {
+ entries_.append(vtmEntry::entry(name, file));
+ }
+ else
+ {
+ entries_.append
+ (
+ vtmEntry::entry
+ (
+ name,
+ file + "." + vtk::fileExtension[contentType]
+ )
+ );
+ }
+
+ return true;
+}
+
+
+void Foam::vtk::vtmWriter::add
+(
+ const word& blockName,
+ const fileName& prefix,
+ const vtmWriter& other
+)
+{
+ // Standard sanity repair (block ending), prune empty entries
+ repair();
+
+ beginBlock(blockName);
+
+ label depth = 0;
+ bool good = true;
+
+ for (const vtmEntry& e : other.entries_)
+ {
+ switch (e.type_)
+ {
+ case vtmEntry::NONE:
+ {
+ break;
+ }
+ case vtmEntry::DATA:
+ {
+ if (e.good())
+ {
+ entries_.append(e);
+
+ if (prefix.size())
+ {
+ fileName& f = entries_.last().file_;
+
+ f = prefix/f;
+ }
+ }
+
+ break;
+ }
+ case vtmEntry::BEGIN_BLOCK:
+ {
+ ++depth;
+ entries_.append(e);
+ break;
+ }
+ case vtmEntry::END_BLOCK:
+ {
+ good = (depth > 0);
+ --depth;
+ if (good)
+ {
+ entries_.append(e);
+ }
+ break;
+ }
+ }
+
+ if (!good) break;
+ }
+
+ while (depth--)
+ {
+ entries_.append(vtmEntry::endblock());
+ }
+
+ entries_.append(vtmEntry::endblock());
+
+ if (!hasTime_ && other.hasTime_)
+ {
+ hasTime_ = true;
+ timeValue_ = other.timeValue_;
+ }
+}
+
+
+void Foam::vtk::vtmWriter::add
+(
+ const word& blockName,
+ const vtmWriter& other
+)
+{
+ add(blockName, fileName::null, other);
+}
+
+
+Foam::label Foam::vtk::vtmWriter::write(const fileName& file)
+{
+ std::ofstream os_;
+
+ mkDir(file.path());
+
+ if (file.hasExt(ext()))
+ {
+ os_.open(file);
+ }
+ else
+ {
+ os_.open(file + "." + ext());
+ }
+
+ auto format = vtk::newFormatter(os_, formatType::INLINE_ASCII);
+
+
+ // Contents Header
+ {
+ format().xmlHeader();
+
+ if (hasTime_)
+ {
+ format().xmlComment
+ (
+ "time='" + Foam::name(timeValue_) + "'"
+ );
+ }
+
+ format().beginVTKFile();
+ }
+
+
+ // Walk the block and dataset contents
+
+ label depth = 0;
+ label ndata = 0;
+
+ for (const vtmEntry& e : entries_)
+ {
+ switch (e.type_)
+ {
+ case vtmEntry::DATA:
+ {
+ if (e.file_.empty())
+ {
+ continue; // Empty dataset is junk - skip
+ }
+ ++ndata;
+ break;
+ }
+ case vtmEntry::BEGIN_BLOCK:
+ {
+ ++depth;
+ break;
+ }
+ case vtmEntry::END_BLOCK:
+ {
+ --depth;
+ break;
+ }
+ default:
+ {
+ continue;
+ break;
+ }
+ }
+
+ if (depth < 0)
+ {
+ // Too many end blocks - stop output now. Should we warn?
+ break;
+ }
+ e.write(format());
+ }
+
+ // Close any dangling blocks
+ while (depth--)
+ {
+ format().endBlock();
+ }
+
+ format().endTag(vtk::fileTag::MULTI_BLOCK);
+
+
+ // FieldData for TimeValue
+ if (hasTime_)
+ {
+ format()
+ .beginFieldData()
+ .writeTimeValue(timeValue_)
+ .endFieldData();
+ }
+
+ format().endVTKFile();
+
+ format.clear();
+ os_.close();
+
+ return ndata;
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/vtk/file/foamVtmWriter.H b/src/fileFormats/vtk/file/foamVtmWriter.H
new file mode 100644
index 0000000000..24b9bed0fd
--- /dev/null
+++ b/src/fileFormats/vtk/file/foamVtmWriter.H
@@ -0,0 +1,342 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 .
+
+Class
+ Foam::vtk::vtmWriter
+
+Description
+ Provides a means of accumulating file entries for generating
+ a vtkMultiBlockDataSet (.vtm) file.
+
+ For example, to generate the following content:
+ \verbatim
+
+
+
+
+
+
+
+
+
+
+
+ 12.345
+
+
+
+ \endverbatim
+
+ The following code would be used:
+ \code
+ vtm.clear();
+ vtm.setTime(12.345);
+
+ vtm.append("internal", "internal.vtu");
+
+ vtm.beginBlock("boundary");
+ vtm.append("boundary/inlet.vtp");
+ vtm.append("boundary/outlet.vtp");
+
+ vtm.write("outputName");
+ \endcode
+
+SourceFiles
+ foamVtmWriter.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef foamVtmWriter_H
+#define foamVtmWriter_H
+
+#include "foamVtkOutputOptions.H"
+#include "DynamicList.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward declarations
+class Time;
+
+namespace vtk
+{
+
+/*---------------------------------------------------------------------------*\
+ Class vtk::vtmWriter Declaration
+\*---------------------------------------------------------------------------*/
+
+class vtmWriter
+{
+ //- Simple structure for containing entries
+ struct vtmEntry
+ {
+ enum Type
+ {
+ NONE = 0,
+ DATA = 'D',
+ BEGIN_BLOCK = '{',
+ END_BLOCK = '}'
+ };
+
+ //- The entry type
+ int type_;
+
+ //- The 'name' entry (to describe block or data)
+ string name_;
+
+ //- The 'file' entry (data only)
+ fileName file_;
+
+ // Constructors
+
+ vtmEntry(const vtmEntry&) = default;
+ vtmEntry(vtmEntry&&) = default;
+ vtmEntry& operator=(const vtmEntry&) = default;
+ vtmEntry& operator=(vtmEntry&&) = default;
+
+ //- Construct null
+ vtmEntry()
+ :
+ type_(NONE)
+ {}
+
+ //- Construct from components
+ vtmEntry(int what, const string& name, const fileName& file)
+ :
+ type_(what), name_(name), file_(file)
+ {}
+
+
+ // Factory Methods
+
+ static vtmEntry block(const string& name)
+ {
+ return vtmEntry(BEGIN_BLOCK, name, "");
+ }
+
+ static vtmEntry endblock()
+ {
+ return vtmEntry(END_BLOCK, "", "");
+ }
+
+ static vtmEntry entry(const fileName& file)
+ {
+ return vtmEntry(DATA, "", file);
+ }
+
+ static vtmEntry entry(const string& name, const fileName& file)
+ {
+ return vtmEntry(DATA, name, file);
+ }
+
+
+ // Member Functions
+
+ //- Test the type
+ bool isType(Type what) const
+ {
+ return type_ == what;
+ }
+
+ //- Reset to NONE
+ void clear();
+
+ //- True if the entry is good.
+ bool good() const;
+
+ //- Output valid entry as XML
+ bool write(vtk::formatter& format) const;
+ };
+
+
+ // Private Member Data
+
+ //- Auto-generate names from 'file' entry?
+ bool autoName_;
+
+ //- Collapse empty blocks and combine block/dataset etc.
+ bool autoCollapse_;
+
+ //- Has a TimeValue for FieldData?
+ bool hasTime_;
+
+ //- A vtm file entry: begin/end block, dataset
+ DynamicList entries_;
+
+ //- LIFO stack of current block names
+ DynamicList blocks_;
+
+ //- TimeValue for FieldData
+ scalar timeValue_;
+
+
+ // Private Member Functions
+
+ //- Remove NONE entries
+ bool pruneEmpty();
+
+ //- Remove empty blocks
+ bool pruneEmptyBlocks();
+
+ //- Collapse block if it has a single dataset and the names allow it
+ bool collapseBlocks();
+
+
+public:
+
+ // Constructors
+
+ //- Construct null, with autoName on
+ vtmWriter();
+
+ //- Construct with specified behaviour for autoName
+ explicit vtmWriter(bool autoName);
+
+ //- Construct with specified behaviour for autoName, autoCollapse
+ vtmWriter(bool autoName, bool autoCollapse);
+
+
+ //- Destructor
+ ~vtmWriter() = default;
+
+
+ // Member Functions
+
+ //- File extension (always "vtm")
+ inline static word ext();
+
+ //- If there are no data sets
+ bool empty() const;
+
+ //- The number of data sets
+ label size() const;
+
+
+ // Content Management
+
+ //- Clear all entries and reset output
+ void clear();
+
+ //- Define "TimeValue" for FieldData (name as per Catalyst output)
+ void setTime(scalar timeValue);
+
+ //- Define "TimeValue" for FieldData (name as per Catalyst output)
+ void setTime(const Time& t);
+
+
+ //- Start a new block, optionally with a name
+ // \return block depth
+ label beginBlock(const word& blockName = word::null);
+
+ //- End the previous block, optionally with name checking
+ // \return block depth
+ label endBlock(const word& blockName = word::null);
+
+
+ //- Add a file. The name is either empty or created with autoName
+ // \return True if file is non-empty
+ bool append(const fileName& file);
+
+ //- Add a file with name
+ // \return True if file is non-empty
+ bool append(const word& name, const fileName& file);
+
+ //- Add a file with given contentType extension
+ //- The name is either empty or created with autoName
+ // \return True if file is non-empty
+ bool append(const fileName& file, vtk::fileTag contentType);
+
+ //- Add a file with name, with given contentType extension
+ // \return True if file is non-empty
+ bool append
+ (
+ const word& name,
+ const fileName& file,
+ vtk::fileTag contentType
+ );
+
+ //- Add a (.vtp) file
+ // \return True if file is non-empty
+ inline bool append_vtp(const fileName& file);
+
+ //- Add a (.vtp) file with name
+ // \return True if file is non-empty
+ inline bool append_vtp(const word& name, const fileName& file);
+
+ //- Add a (.vtu) file
+ // \return True if file is non-empty
+ inline bool append_vtu(const fileName& file);
+
+ //- Add a (.vtu) file with name
+ // \return True if file is non-empty
+ inline bool append_vtu(const word& name, const fileName& file);
+
+
+ // Content Management
+
+ //- Sanity fixes on the data
+ void repair(bool collapse=false);
+
+ //- Add in content from another vtm and place under the given block
+ //- name.
+ void add(const word& blockName, const vtmWriter& other);
+
+ //- Add in content from another vtm and place under the given block
+ //- name. Adjust the added 'file' entries to include the given prefix.
+ void add
+ (
+ const word& blockName,
+ const fileName& prefix,
+ const vtmWriter& other
+ );
+
+ // Writing
+
+ //- Open file for writing (creates parent directory) and write the
+ //- blocks and TimeValue.
+ // The file name is with/without an extension.
+ // \return number of data sets
+ label write(const fileName& file);
+
+ //- Print debug view of block and dataset contents
+ void dump(Ostream& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace vtk
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "foamVtmWriterI.H"
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/vtk/file/foamVtmWriterI.H b/src/fileFormats/vtk/file/foamVtmWriterI.H
new file mode 100644
index 0000000000..90351d70ee
--- /dev/null
+++ b/src/fileFormats/vtk/file/foamVtmWriterI.H
@@ -0,0 +1,66 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 .
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+inline Foam::word Foam::vtk::vtmWriter::ext()
+{
+ return vtk::fileExtension[vtk::fileTag::MULTI_BLOCK];
+}
+
+
+inline bool Foam::vtk::vtmWriter::append_vtp(const fileName& file)
+{
+ return append(file, vtk::fileTag::POLY_DATA);
+}
+
+
+inline bool Foam::vtk::vtmWriter::append_vtp
+(
+ const word& name,
+ const fileName& file
+)
+{
+ return append(name, file, vtk::fileTag::POLY_DATA);
+}
+
+
+inline bool Foam::vtk::vtmWriter::append_vtu(const fileName& file)
+{
+ return append(file, vtk::fileTag::UNSTRUCTURED_GRID);
+}
+
+
+inline bool Foam::vtk::vtmWriter::append_vtu
+(
+ const word& name,
+ const fileName& file
+)
+{
+ return append(name, file, vtk::fileTag::UNSTRUCTURED_GRID);
+}
+
+
+// ************************************************************************* //