From 0454f4a04021ac69c8ceabeb33d97a1a95acf075 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Fri, 17 Sep 2021 18:46:30 +0200
Subject: [PATCH] ENH: robustness and functionality improvements for VTK output

- PstreamBuffers for parallel VTK output.
  - avoids MPI overflows for larger meshes

- new vtk::lineWriter for writing edges

- vtk::fileWriter::writeUniform now also supports processor-specific
  uniform values instead of assuming that they are identical everywhere.
---
 .../vtk/output/foamVtkPatchWriterTemplates.C  |   2 +-
 src/fileFormats/Make/files                    |   1 +
 src/fileFormats/vtk/base/foamVtkCore.C        |  24 +-
 src/fileFormats/vtk/base/foamVtkCore.H        |  10 +-
 src/fileFormats/vtk/file/foamVtkFileWriter.C  |  67 ++-
 src/fileFormats/vtk/file/foamVtkFileWriter.H  |  62 +--
 src/fileFormats/vtk/file/foamVtkFileWriterI.H |   6 +-
 .../vtk/file/foamVtkFileWriterTemplates.C     |  19 +-
 src/fileFormats/vtk/output/foamVtkOutput.C    |  83 ++--
 src/fileFormats/vtk/output/foamVtkOutput.H    |  26 +-
 src/fileFormats/vtk/output/foamVtkOutputI.H   |  28 +-
 .../vtk/output/foamVtkOutputTemplates.C       | 352 ++++++++++++----
 src/fileFormats/vtk/write/foamVtkLineWriter.C | 138 ++++++
 src/fileFormats/vtk/write/foamVtkLineWriter.H | 180 ++++++++
 src/fileFormats/vtk/write/foamVtkPolyWriter.C | 392 +++++++++++++++---
 src/fileFormats/vtk/write/foamVtkPolyWriter.H |  52 ++-
 .../vtk/write/foamVtkPolyWriterTemplates.C    |  28 +-
 .../vtk/write/foamVtkSurfaceWriter.C          |   8 +-
 .../vtk/write/foamVtkSurfaceWriter.H          |  15 +-
 .../edgeMeshFormats/vtk/VTKedgeFormat.C       |  85 ++--
 .../edgeMeshFormats/vtk/VTKedgeFormat.H       |  35 +-
 .../vtk/mesh/foamVtkInternalMeshWriter.C      |  58 +--
 .../vtk/mesh/foamVtkInternalMeshWriter.H      |   4 +
 .../mesh/foamVtkInternalMeshWriterTemplates.C |  46 +-
 .../output/vtk/patch/foamVtkPatchMeshWriter.C | 104 +----
 .../output/vtk/patch/foamVtkPatchMeshWriter.H |  10 +-
 .../patch/foamVtkPatchMeshWriterTemplates.C   |  22 +-
 27 files changed, 1347 insertions(+), 510 deletions(-)
 create mode 100644 src/fileFormats/vtk/write/foamVtkLineWriter.C
 create mode 100644 src/fileFormats/vtk/write/foamVtkLineWriter.H

diff --git a/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C b/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C
index 9dc41948e46..0219a3b835c 100644
--- a/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C
+++ b/src/conversion/vtk/output/foamVtkPatchWriterTemplates.C
@@ -149,7 +149,7 @@ void Foam::vtk::patchWriter::write
             << exit(FatalError);
     }
 
-    label nFaces = nLocalFaces_;
+    label nFaces = nLocalPolys_;
 
     if (parallel_)
     {
diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files
index 2686cefc430..f7fd57b8538 100644
--- a/src/fileFormats/Make/files
+++ b/src/fileFormats/Make/files
@@ -59,6 +59,7 @@ vtk/part/foamVtuCells.C
 vtk/part/foamVtuSizing.C
 
 vtk/read/vtkUnstructuredReader.C
+vtk/write/foamVtkLineWriter.C
 vtk/write/foamVtkPolyWriter.C
 vtk/write/foamVtkSurfaceWriter.C
 
diff --git a/src/fileFormats/vtk/base/foamVtkCore.C b/src/fileFormats/vtk/base/foamVtkCore.C
index 14f832fec6e..b9488e8824a 100644
--- a/src/fileFormats/vtk/base/foamVtkCore.C
+++ b/src/fileFormats/vtk/base/foamVtkCore.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2018 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -113,6 +113,8 @@ Foam::vtk::dataArrayAttrNames
 });
 
 
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
 // Legacy
 
 const Foam::word Foam::vtk::legacy::fileExtension("vtk");
@@ -132,11 +134,27 @@ const Foam::Enum
 <
     Foam::vtk::fileTag
 >
-Foam::vtk::legacy::dataTypeNames
+Foam::vtk::legacy::fileTagNames
 ({
-    { vtk::fileTag::CELL_DATA,  "CELL_DATA" },
+    { vtk::fileTag::POINTS, "POINTS" },
+    { vtk::fileTag::CELLS, "CELLS" },
+    { vtk::fileTag::POLYS, "POLYGONS" },
+    { vtk::fileTag::VERTS, "VERTICES" },
+    { vtk::fileTag::LINES, "LINES" },
+    { vtk::fileTag::CELL_DATA, "CELL_DATA" },
     { vtk::fileTag::POINT_DATA, "POINT_DATA" },
 });
 
 
+const Foam::Enum
+<
+    Foam::vtk::dataArrayAttr
+>
+Foam::vtk::legacy::dataArrayAttrNames
+({
+    { vtk::dataArrayAttr::OFFSETS, "OFFSETS" },
+    { vtk::dataArrayAttr::CONNECTIVITY, "CONNECTIVITY" },
+});
+
+
 // ************************************************************************* //
diff --git a/src/fileFormats/vtk/base/foamVtkCore.H b/src/fileFormats/vtk/base/foamVtkCore.H
index 2524964a0af..08615850ecb 100644
--- a/src/fileFormats/vtk/base/foamVtkCore.H
+++ b/src/fileFormats/vtk/base/foamVtkCore.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2018 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -183,8 +183,12 @@ namespace legacy
     //- Legacy content names (POLYDATA, UNSTRUCTURED_GRID)
     extern const Foam::Enum<vtk::fileTag> contentNames;
 
-    //- Legacy data type names (CELL_DATA, POINT_DATA)
-    extern const Foam::Enum<vtk::fileTag> dataTypeNames;
+    //- Legacy file tags (eg, LINES, CELL_DATA, POINT_DATA, ...)
+    extern const Foam::Enum<vtk::fileTag> fileTagNames;
+
+    //- Legacy attributes (eg, OFFSETS)
+    extern const Foam::Enum<dataArrayAttr> dataArrayAttrNames;
+
 
 } // End namespace legacy
 
diff --git a/src/fileFormats/vtk/file/foamVtkFileWriter.C b/src/fileFormats/vtk/file/foamVtkFileWriter.C
index aff45b90177..3cc8b7e72a5 100644
--- a/src/fileFormats/vtk/file/foamVtkFileWriter.C
+++ b/src/fileFormats/vtk/file/foamVtkFileWriter.C
@@ -26,6 +26,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "foamVtkFileWriter.H"
+#include "globalIndex.H"
 #include "OSspecific.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -342,7 +343,7 @@ bool Foam::vtk::fileWriter::open(const fileName& file, bool parallel)
 
     // Open a file and attach a formatter
     // - on master (always)
-    // - on slave if not parallel
+    // - on subproc (if not parallel)
     //
     // This means we can always check if format_ is defined to know if output
     // is desired on any particular process.
@@ -515,7 +516,7 @@ void Foam::vtk::fileWriter::writeTimeValue(scalar timeValue)
             << exit(FatalError);
     }
 
-    // No collectives - can skip on slave processors
+    // No collectives - can skip on sub-procs
     if (!format_) return;
 
     if (legacy())
@@ -529,4 +530,66 @@ void Foam::vtk::fileWriter::writeTimeValue(scalar timeValue)
 }
 
 
+bool Foam::vtk::fileWriter::writeProcIDs(const label nValues)
+{
+    // Write procIDs whenever running in parallel
+
+    if (!Pstream::parRun())
+    {
+        return false;  // Non-parallel: skip
+    }
+
+    if (isState(outputState::CELL_DATA))
+    {
+        ++nCellData_;
+    }
+    else
+    {
+        reportBadState(FatalErrorInFunction, outputState::CELL_DATA)
+            << " for procID field" << nl << endl
+            << exit(FatalError);
+
+        return false;
+    }
+
+
+    const globalIndex procSizes
+    (
+        parallel_
+      ? globalIndex(nValues)
+      : globalIndex()
+    );
+
+    const label totalCount = (parallel_ ? procSizes.size() : nValues);
+
+    this->beginDataArray<label>("procID", totalCount);
+
+    bool good = false;
+
+    if (parallel_)
+    {
+        if (Pstream::master())
+        {
+            // Per-processor ids
+            for (const int proci : Pstream::allProcs())
+            {
+                vtk::write(format(), label(proci), procSizes.localSize(proci));
+            }
+            good = true;
+        }
+    }
+    else
+    {
+        vtk::write(format(), label(Pstream::myProcNo()), totalCount);
+        good = true;
+    }
+
+
+    this->endDataArray();
+
+    // MPI barrier
+    return parallel_ ? returnReduce(good, orOp<bool>()) : good;
+}
+
+
 // ************************************************************************* //
diff --git a/src/fileFormats/vtk/file/foamVtkFileWriter.H b/src/fileFormats/vtk/file/foamVtkFileWriter.H
index 19a1d4e1500..ea25b303232 100644
--- a/src/fileFormats/vtk/file/foamVtkFileWriter.H
+++ b/src/fileFormats/vtk/file/foamVtkFileWriter.H
@@ -107,10 +107,10 @@ protected:
         //- The output file name
         fileName outputFile_;
 
-        //- The VTK formatter in use (master process)
+        //- The VTK formatter in use (only valid on master process)
         autoPtr<vtk::formatter> format_;
 
-        //- The backend ostream in use (master process)
+        //- The backend ostream in use (only opened on master process)
         std::ofstream os_;
 
 
@@ -123,16 +123,16 @@ protected:
         Ostream& reportBadState(Ostream&, outputState, outputState) const;
 
         //- The backend ostream in use
-        inline std::ofstream& os();
+        inline std::ofstream& os() noexcept;
 
         //- The VTK formatter in use
         inline vtk::formatter& format();
 
-        //- True if the output state corresponds to the test state.
-        inline bool isState(outputState test) const;
+        //- True if output state corresponds to the test state.
+        inline bool isState(outputState test) const noexcept;
 
-        //- True if the output state does not correspond to the test state.
-        inline bool notState(outputState test) const;
+        //- True if output state does not correspond to the test state.
+        inline bool notState(outputState test) const noexcept;
 
         //- Start of a field or DataArray output (legacy or non-legacy).
         template<class Type>
@@ -151,26 +151,6 @@ protected:
         //- End of a POINTS DataArray
         void endPoints();
 
-
-        //- Write uniform field content.
-        //  No context checking (eg, file-open, CellData, PointData, etc)
-        template<class Type>
-        void writeUniform
-        (
-            const word& fieldName,
-            const Type& val,
-            const label nValues
-        );
-
-        //- Write basic (primitive) field content
-        //  No context checking (eg, file-open, CellData, PointData, etc)
-        template<class Type>
-        void writeBasicField
-        (
-            const word& fieldName,
-            const UList<Type>& field
-        );
-
         //- Trigger change state to Piece. Resets nCellData_, nPointData_.
         bool enter_Piece();
 
@@ -194,6 +174,34 @@ protected:
         bool exit_File();
 
 
+    // Field writing
+
+        //- Write uniform field content.
+        //  No context checking (eg, file-open, CellData, PointData, etc)
+        //  The value and count can be different on each processor
+        template<class Type>
+        void writeUniform
+        (
+            const word& fieldName,
+            const Type& val,
+            const label nValues
+        );
+
+        //- Write basic (primitive) field content
+        //  No context checking (eg, file-open, CellData, PointData, etc)
+        template<class Type>
+        void writeBasicField
+        (
+            const word& fieldName,
+            const UList<Type>& field
+        );
+
+        //- Write nValues of processor ids as CellData (no-op in serial)
+        bool writeProcIDs(const label nValues);
+
+
+    // Other
+
         //- No copy construct
         fileWriter(const fileWriter&) = delete;
 
diff --git a/src/fileFormats/vtk/file/foamVtkFileWriterI.H b/src/fileFormats/vtk/file/foamVtkFileWriterI.H
index 62c7d86600e..192eac45a75 100644
--- a/src/fileFormats/vtk/file/foamVtkFileWriterI.H
+++ b/src/fileFormats/vtk/file/foamVtkFileWriterI.H
@@ -27,7 +27,7 @@ License
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-inline std::ofstream& Foam::vtk::fileWriter::os()
+inline std::ofstream& Foam::vtk::fileWriter::os() noexcept
 {
     return os_;
 }
@@ -39,13 +39,13 @@ inline Foam::vtk::formatter& Foam::vtk::fileWriter::format()
 }
 
 
-inline bool Foam::vtk::fileWriter::isState(outputState test) const
+inline bool Foam::vtk::fileWriter::isState(outputState test) const noexcept
 {
     return (test == state_);
 }
 
 
-inline bool Foam::vtk::fileWriter::notState(outputState test) const
+inline bool Foam::vtk::fileWriter::notState(outputState test) const noexcept
 {
     return (test != state_);
 }
diff --git a/src/fileFormats/vtk/file/foamVtkFileWriterTemplates.C b/src/fileFormats/vtk/file/foamVtkFileWriterTemplates.C
index 79a774fce91..21ec488823f 100644
--- a/src/fileFormats/vtk/file/foamVtkFileWriterTemplates.C
+++ b/src/fileFormats/vtk/file/foamVtkFileWriterTemplates.C
@@ -89,14 +89,25 @@ void Foam::vtk::fileWriter::writeUniform
 (
     const word& fieldName,
     const Type& val,
-    const label nValues
+    const label nLocalValues
 )
 {
-    this->beginDataArray<Type>(fieldName, nValues);
+    label nTotal = nLocalValues;
 
-    if (format_)
+    if (parallel_)
+    {
+        reduce(nTotal, sumOp<label>());
+    }
+
+    this->beginDataArray<Type>(fieldName, nTotal);
+
+    if (parallel_)
+    {
+        vtk::writeValueParallel(format_.ref(), val, nLocalValues);
+    }
+    else
     {
-        vtk::write(format(), val, nValues);
+        vtk::write(format(), val, nLocalValues);
     }
 
     this->endDataArray();
diff --git a/src/fileFormats/vtk/output/foamVtkOutput.C b/src/fileFormats/vtk/output/foamVtkOutput.C
index 7e32b87dcf0..08a00cf6e40 100644
--- a/src/fileFormats/vtk/output/foamVtkOutput.C
+++ b/src/fileFormats/vtk/output/foamVtkOutput.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -39,7 +39,7 @@ License
 #include "globalIndex.H"
 #include "instant.H"
 #include "Fstream.H"
-#include "Pstream.H"
+#include "PstreamBuffers.H"
 #include "OSspecific.H"
 
 // * * * * * * * * * * * * * * Global Functions  * * * * * * * * * * * * * * //
@@ -99,7 +99,7 @@ void Foam::vtk::writeIdentity
     label start
 )
 {
-    // No nComponents for label, so use fmt.write() directly
+    // No nComponents for label, can use fmt.write() directly
     for (label i=0; i < len; ++i)
     {
         fmt.write(start);
@@ -114,7 +114,7 @@ void Foam::vtk::writeList
     const UList<uint8_t>& values
 )
 {
-    // No nComponents for char, so use fmt.write() directly
+    // No nComponents for char, can use fmt.write() directly
     for (const uint8_t val : values)
     {
         fmt.write(val);
@@ -125,85 +125,59 @@ void Foam::vtk::writeList
 void Foam::vtk::writeListParallel
 (
     vtk::formatter& fmt,
-    const UList<uint8_t>& values
+    const labelUList& values,
+    const globalIndex& procOffset
 )
 {
-    if (Pstream::master())
-    {
-        vtk::writeList(fmt, values);
-
-        List<uint8_t> recv;
-
-        // Receive and write
-        for (const int slave : Pstream::subProcs())
-        {
-            IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+    // List sizes
+    const globalIndex sizes(values.size());
 
-            fromSlave >> recv;
+    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
 
-            vtk::writeList(fmt, recv);
-        }
-    }
-    else
+    // Send to master
+    if (!Pstream::master())
     {
-        // Send to master
-        OPstream toMaster
+        UOPstream os(Pstream::masterNo(), pBufs);
+        os.write
         (
-            Pstream::commsTypes::blocking,
-            Pstream::masterNo()
+            reinterpret_cast<const char*>(values.cdata()),
+            values.size_bytes()
         );
-
-        toMaster << values;
     }
-}
 
+    pBufs.finishedSends();
 
-void Foam::vtk::writeListParallel
-(
-    vtk::formatter& fmt,
-    const labelUList& values,
-    const globalIndex& procOffset
-)
-{
     if (Pstream::master())
     {
+        // Master data
+
         // Write with offset
         const label offsetId = procOffset.offset(0);
-
         for (const label val : values)
         {
             vtk::write(fmt, val + offsetId);
         }
 
-        labelList recv;
-
         // Receive and write
-        for (const int slave : Pstream::subProcs())
+        for (const int proci : Pstream::subProcs())
         {
-            IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+            List<label> recv(sizes.localSize(proci));
 
-            fromSlave >> recv;
-
-            const label offsetId = procOffset.offset(slave);
+            UIPstream is(proci, pBufs);
+            is.read
+            (
+                reinterpret_cast<char*>(recv.data()),
+                recv.size_bytes()
+            );
 
             // Write with offset
+            const label offsetId = procOffset.offset(proci);
             for (const label val : recv)
             {
                 vtk::write(fmt, val + offsetId);
             }
         }
     }
-    else
-    {
-        // Send to master
-        OPstream toMaster
-        (
-            Pstream::commsTypes::blocking,
-            Pstream::masterNo()
-        );
-
-        toMaster << values;
-    }
 }
 
 
@@ -219,6 +193,9 @@ void Foam::vtk::legacy::fileHeader
     // Line 1:
     os  << "# vtk DataFile Version 2.0" << nl;
 
+    // OR
+    // os  << "# vtk DataFile Version 5.1" << nl;
+
     // Line 2: title
 
     const auto truncate = title.find('\n');
diff --git a/src/fileFormats/vtk/output/foamVtkOutput.H b/src/fileFormats/vtk/output/foamVtkOutput.H
index ac8c3b68520..ad7d6bf7747 100644
--- a/src/fileFormats/vtk/output/foamVtkOutput.H
+++ b/src/fileFormats/vtk/output/foamVtkOutput.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -59,7 +59,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class instant;
 class globalIndex;
 
@@ -94,14 +94,20 @@ namespace vtk
     //  The output does not include the payload size.
     void writeList(vtk::formatter& fmt, const UList<uint8_t>& values);
 
-    //- Write a list of uint8_t values.
-    //  The output does not include the payload size.
-    void writeListParallel(vtk::formatter& fmt, const UList<uint8_t>& values);
-
     //- Component-wise write of a value (N times)
     template<class Type>
     inline void write(vtk::formatter& fmt, const Type& val, const label n=1);
 
+    //- Component-wise write of a value (N times) in parallel
+    //  The value and count may differ on each processor
+    template<class Type>
+    inline void writeValueParallel
+    (
+        vtk::formatter& fmt,
+        const Type& val,
+        const label count=1
+    );
+
 
     //- Write a list of values.
     //  The output does not include the payload size.
@@ -215,7 +221,7 @@ namespace legacy
 
 // Functions
 
-    //- Emit header for legacy file.
+    //- Emit header for legacy file (vtk DataFile Version 2.0)
     //  Writes "ASCII" or "BINARY" depending on specified type.
     void fileHeader(std::ostream& os, const std::string& title, bool binary);
 
@@ -248,6 +254,12 @@ namespace legacy
     //- Emit header for POINTS (with trailing newline).
     inline void beginPoints(std::ostream& os, label nPoints);
 
+    //- Emit header for LINES (with trailing newline).
+    //  The nConnectivity is the sum of all connectivity points used,
+    //  but \b without additional space for the size prefixes.
+    //  The additional prefix sizes are added internally.
+    inline void beginLines(std::ostream& os, label nLines, label nConnectivity);
+
     //- Emit header for POLYGONS (with trailing newline).
     //  The nConnectivity is the sum of all connectivity points used,
     //  but \b without additional space for the size prefixes.
diff --git a/src/fileFormats/vtk/output/foamVtkOutputI.H b/src/fileFormats/vtk/output/foamVtkOutputI.H
index 036432e3c09..9789eecc41f 100644
--- a/src/fileFormats/vtk/output/foamVtkOutputI.H
+++ b/src/fileFormats/vtk/output/foamVtkOutputI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -113,7 +113,23 @@ inline void Foam::vtk::legacy::fileHeader
 inline void Foam::vtk::legacy::beginPoints(std::ostream& os, label nPoints)
 {
     os  << nl
-        << "POINTS " << nPoints << " float" << nl;
+        << legacy::fileTagNames[vtk::fileTag::POINTS]
+        << ' ' << nPoints
+        << " float" << nl;
+}
+
+
+inline void Foam::vtk::legacy::beginLines
+(
+    std::ostream& os,
+    label nLines,
+    label nConnectivity
+)
+{
+    os  << nl
+        << legacy::fileTagNames[vtk::fileTag::LINES]
+        << ' ' << nLines
+        << ' ' << (nLines + nConnectivity) << nl;
 }
 
 
@@ -125,7 +141,9 @@ inline void Foam::vtk::legacy::beginPolys
 )
 {
     os  << nl
-        << "POLYGONS " << nPolys << ' ' << (nPolys + nConnectivity) << nl;
+        << legacy::fileTagNames[vtk::fileTag::POLYS]
+        << ' ' << nPolys
+        << ' ' << (nPolys + nConnectivity) << nl;
 }
 
 
@@ -159,7 +177,7 @@ inline void Foam::vtk::legacy::beginCellData
 {
     fmt.os()
         << nl
-        << legacy::dataTypeNames[vtk::fileTag::CELL_DATA]
+        << legacy::fileTagNames[vtk::fileTag::CELL_DATA]
         << ' ' << nCells << nl;
     legacy::fieldData(fmt, nFields);
 }
@@ -174,7 +192,7 @@ inline void Foam::vtk::legacy::beginPointData
 {
     fmt.os()
         << nl
-        << legacy::dataTypeNames[vtk::fileTag::POINT_DATA]
+        << legacy::fileTagNames[vtk::fileTag::POINT_DATA]
         << ' ' << nPoints << nl;
     legacy::fieldData(fmt, nFields);
 }
diff --git a/src/fileFormats/vtk/output/foamVtkOutputTemplates.C b/src/fileFormats/vtk/output/foamVtkOutputTemplates.C
index e296a2b2b56..59bdb0e88fe 100644
--- a/src/fileFormats/vtk/output/foamVtkOutputTemplates.C
+++ b/src/fileFormats/vtk/output/foamVtkOutputTemplates.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,7 +25,8 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "Pstream.H"
+#include "globalIndex.H"
+#include "PstreamBuffers.H"
 #include "ListOps.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -123,38 +124,39 @@ void Foam::vtk::writeLists
 
 
 template<class Type>
-void Foam::vtk::writeListParallel
+void Foam::vtk::writeValueParallel
 (
     vtk::formatter& fmt,
-    const UList<Type>& values
+    const Type& val,
+    const label count
 )
 {
     if (Pstream::master())
     {
-        vtk::writeList(fmt, values);
+        vtk::write(fmt, val, count);
 
-        List<Type> recv;
+        label subCount;
+        Type subValue;
 
-        // Receive and write
-        for (const int slave : Pstream::subProcs())
+        // Receive each [size, value] tuple
+        for (const int proci : Pstream::subProcs())
         {
-            IPstream fromSlave(Pstream::commsTypes::blocking, slave);
-
-            fromSlave >> recv;
+            IPstream is(Pstream::commsTypes::blocking, proci);
+            is >> subCount >> subValue;
 
-            vtk::writeList(fmt, recv);
+            vtk::write(fmt, subValue, subCount);
         }
     }
     else
     {
-        // Send to master
-        OPstream toMaster
+        OPstream os
         (
             Pstream::commsTypes::blocking,
             Pstream::masterNo()
         );
 
-        toMaster << values;
+        // Send [size, value] tuple
+        os << count << val;
     }
 }
 
@@ -163,36 +165,106 @@ template<class Type>
 void Foam::vtk::writeListParallel
 (
     vtk::formatter& fmt,
-    const UList<Type>& values,
-    const labelUList& addressing
+    const UList<Type>& values
 )
 {
-    if (Pstream::master())
+    // List sizes
+    const globalIndex sizes(values.size());
+
+    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
+
+    // Send to master
+    if (!Pstream::master())
     {
-        vtk::writeList(fmt, values, addressing);
+        UOPstream os(Pstream::masterNo(), pBufs);
+        if (is_contiguous<Type>::value)
+        {
+            os.write
+            (
+                reinterpret_cast<const char*>(values.cdata()),
+                values.size_bytes()
+            );
+        }
+        else
+        {
+            os << values;
+        }
+    }
 
-        List<Type> recv;
+    pBufs.finishedSends();
+
+    if (Pstream::master())
+    {
+        // Write master data
+        vtk::writeList(fmt, values);
 
         // Receive and write
-        for (const int slave : Pstream::subProcs())
+        for (const int proci : Pstream::subProcs())
         {
-            IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+            UIPstream is(proci, pBufs);
+
+            {
+                List<Type> recv(sizes.localSize(proci));
+
+                if (is_contiguous<Type>::value)
+                {
+                    is.read
+                    (
+                        reinterpret_cast<char*>(recv.data()),
+                        recv.size_bytes()
+                    );
+                }
+                else
+                {
+                    is >> recv;
+                }
+                vtk::writeList(fmt, recv);
+            }
+        }
+    }
+}
 
-            fromSlave >> recv;
 
-            vtk::writeList(fmt, recv);
-        }
+template<class Type>
+void Foam::vtk::writeListParallel
+(
+    vtk::formatter& fmt,
+    const UList<Type>& values,
+    const labelUList& addressing
+)
+{
+    UIndirectList<Type> send(values, addressing);
+
+    // List sizes
+    const globalIndex sizes(send.size());
+
+    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
+
+    // Send to master
+    if (!Pstream::master())
+    {
+        UOPstream os(Pstream::masterNo(), pBufs);
+        os << send;
     }
-    else
+
+    pBufs.finishedSends();
+
+    if (Pstream::master())
     {
-        // Send to master
-        OPstream toMaster
-        (
-            Pstream::commsTypes::blocking,
-            Pstream::masterNo()
-        );
+        // Write master data
+        vtk::writeList(fmt, values, addressing);
+
+        // Receive and write
+        for (const int proci : Pstream::subProcs())
+        {
+            UIPstream is(proci, pBufs);
 
-        toMaster << List<Type>(values, addressing);
+            {
+                List<Type> recv;
+                is >> recv;
+                vtk::writeList(fmt, recv);
+            }
+        }
     }
 }
 
@@ -205,32 +277,66 @@ void Foam::vtk::writeListParallel
     const bitSet& selected
 )
 {
-    if (Pstream::master())
+    List<Type> send;
+    if (!Pstream::master())
     {
-        vtk::writeList(fmt, values, selected);
-
-        List<Type> recv;
+        send = subset(selected, values);
+    }
 
-        // Receive and write
-        for (const int slave : Pstream::subProcs())
-        {
-            IPstream fromSlave(Pstream::commsTypes::blocking, slave);
+    // List sizes.
+    // NOTE okay to skip proc0 since we only need sizes (not offsets)
+    const globalIndex sizes(send.size());
 
-            fromSlave >> recv;
+    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
 
-            vtk::writeList(fmt, recv);
+    // Send to master
+    if (!Pstream::master())
+    {
+        UOPstream os(Pstream::masterNo(), pBufs);
+        if (is_contiguous<Type>::value)
+        {
+            os.write
+            (
+                reinterpret_cast<const char*>(send.cdata()),
+                send.size_bytes()
+            );
+        }
+        else
+        {
+            os << send;
         }
     }
-    else
+
+    pBufs.finishedSends();
+
+    if (Pstream::master())
     {
-        // Send to master
-        OPstream toMaster
-        (
-            Pstream::commsTypes::blocking,
-            Pstream::masterNo()
-        );
+        // Write master data
+        vtk::writeList(fmt, values, selected);
 
-        toMaster << subset(selected, values);
+        // Receive and write
+        for (const int proci : Pstream::subProcs())
+        {
+            UIPstream is(proci, pBufs);
+
+            {
+                List<Type> recv(sizes.localSize(proci));
+
+                if (is_contiguous<Type>::value)
+                {
+                    is.read
+                    (
+                        reinterpret_cast<char*>(recv.data()),
+                        recv.size_bytes()
+                    );
+                }
+                else
+                {
+                    is >> recv;
+                }
+                vtk::writeList(fmt, recv);
+            }
+        }
     }
 }
 
@@ -243,34 +349,90 @@ void Foam::vtk::writeListsParallel
     const UList<Type>& values2
 )
 {
-    if (Pstream::master())
-    {
-        vtk::writeList(fmt, values1);
-        vtk::writeList(fmt, values2);
+    // List sizes
+    const globalIndex sizes1(values1.size());
+    const globalIndex sizes2(values2.size());
 
-        List<Type> recv1, recv2;
+    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
 
-        // Receive and write
-        for (const int slave : Pstream::subProcs())
+    // Send to master
+    if (!Pstream::master())
+    {
+        UOPstream os(Pstream::masterNo(), pBufs);
+        if (is_contiguous<Type>::value)
         {
-            IPstream fromSlave(Pstream::commsTypes::blocking, slave);
-
-            fromSlave >> recv1 >> recv2;
-
-            vtk::writeList(fmt, recv1);
-            vtk::writeList(fmt, recv2);
+            os.write
+            (
+                reinterpret_cast<const char*>(values1.cdata()),
+                values1.size_bytes()
+            );
+            os.write
+            (
+                reinterpret_cast<const char*>(values2.cdata()),
+                values2.size_bytes()
+            );
+        }
+        else
+        {
+            os << values1 << values2;
         }
     }
-    else
+
+    pBufs.finishedSends();
+
+    if (Pstream::master())
     {
-        // Send to master
-        OPstream toMaster
+        // Write master data
+        vtk::writeList(fmt, values1);
+        vtk::writeList(fmt, values2);
+
+        // Reserve max receive size
+        DynamicList<Type> recv
         (
-            Pstream::commsTypes::blocking,
-            Pstream::masterNo()
+            max(sizes1.maxNonLocalSize(), sizes2.maxNonLocalSize())
         );
 
-        toMaster << values1 << values2;
+        // Receive and write
+        for (const int proci : Pstream::subProcs())
+        {
+            UIPstream is(proci, pBufs);
+
+            // values1
+            {
+                List<Type> recv(sizes1.localSize(proci));
+                if (is_contiguous<Type>::value)
+                {
+                    is.read
+                    (
+                        reinterpret_cast<char*>(recv.data()),
+                        recv.size_bytes()
+                    );
+                }
+                else
+                {
+                    is >> recv;
+                }
+                vtk::writeList(fmt, recv);
+            }
+
+            // values2
+            {
+                List<Type> recv(sizes2.localSize(proci));
+                if (is_contiguous<Type>::value)
+                {
+                    is.read
+                    (
+                        reinterpret_cast<char*>(recv.data()),
+                        recv.size_bytes()
+                    );
+                }
+                else
+                {
+                    is >> recv;
+                }
+                vtk::writeList(fmt, recv);
+            }
+        }
     }
 }
 
@@ -284,35 +446,45 @@ void Foam::vtk::writeListsParallel
     const labelUList& addressing
 )
 {
+    UIndirectList<Type> send2(values2, addressing);
+
+    PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
+
+    // Send to master
+    if (!Pstream::master())
+    {
+        UOPstream os(Pstream::masterNo(), pBufs);
+        os << values1 << send2;
+    }
+
+    pBufs.finishedSends();
+
     if (Pstream::master())
     {
+        // Write master data
         vtk::writeList(fmt, values1);
         vtk::writeList(fmt, values2, addressing);
 
-        List<Type> recv1, recv2;
-
         // Receive and write
-        for (const int slave : Pstream::subProcs())
+        for (const int proci : Pstream::subProcs())
         {
-            IPstream fromSlave(Pstream::commsTypes::blocking, slave);
-
-            fromSlave >> recv1 >> recv2;
-
-            vtk::writeList(fmt, recv1);
-            vtk::writeList(fmt, recv2);
+            UIPstream is(proci, pBufs);
+
+            // values1
+            {
+                List<Type> recv;
+                is >> recv;
+                vtk::writeList(fmt, recv);
+            }
+
+            // values2 (send2)
+            {
+                List<Type> recv;
+                is >> recv;
+                vtk::writeList(fmt, recv);
+            }
         }
     }
-    else
-    {
-        // Send to master
-        OPstream toMaster
-        (
-            Pstream::commsTypes::blocking,
-            Pstream::masterNo()
-        );
-
-        toMaster << values1 << List<Type>(values2, addressing);
-    }
 }
 
 
diff --git a/src/fileFormats/vtk/write/foamVtkLineWriter.C b/src/fileFormats/vtk/write/foamVtkLineWriter.C
new file mode 100644
index 00000000000..a5f42b5ad98
--- /dev/null
+++ b/src/fileFormats/vtk/write/foamVtkLineWriter.C
@@ -0,0 +1,138 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 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 "foamVtkLineWriter.H"
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::vtk::lineWriter::lineWriter
+(
+    const pointField& points,
+    const edgeList& edges,
+    const vtk::outputOptions opts
+)
+:
+    vtk::polyWriter(opts),
+
+    points_(std::cref<pointField>(points)),
+    edges_(std::cref<edgeList>(edges)),
+    instant_()
+{}
+
+
+Foam::vtk::lineWriter::lineWriter
+(
+    const pointField& points,
+    const edgeList& edges,
+    const fileName& file,
+    bool parallel
+)
+:
+    lineWriter(points, edges)
+{
+    open(file, parallel);
+}
+
+
+Foam::vtk::lineWriter::lineWriter
+(
+    const pointField& points,
+    const edgeList& edges,
+    const vtk::outputOptions opts,
+    const fileName& file,
+    bool parallel
+)
+:
+    lineWriter(points, edges, opts)
+{
+    open(file, parallel);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::vtk::lineWriter::setTime(const instant& inst)
+{
+    instant_ = inst;
+}
+
+
+bool Foam::vtk::lineWriter::beginFile(std::string title)
+{
+    if (title.size())
+    {
+        return vtk::fileWriter::beginFile(title);
+    }
+
+    if (!instant_.name().empty())
+    {
+        return vtk::fileWriter::beginFile
+        (
+            "time='" + instant_.name() + "'"
+        );
+    }
+
+    // Provide default title
+    return vtk::fileWriter::beginFile("edges");
+}
+
+
+bool Foam::vtk::lineWriter::writeGeometry()
+{
+    return writeLineGeometry(points_.get(), edges_.get());
+}
+
+
+void Foam::vtk::lineWriter::writeTimeValue()
+{
+    if (!instant_.name().empty())
+    {
+        vtk::fileWriter::writeTimeValue(instant_.value());
+    }
+}
+
+
+void Foam::vtk::lineWriter::piece
+(
+    const pointField& points,
+    const edgeList& edges
+)
+{
+    endPiece();
+
+    points_ = std::cref<pointField>(points);
+    edges_ = std::cref<edgeList>(edges);
+}
+
+
+bool Foam::vtk::lineWriter::writeProcIDs()
+{
+    return vtk::fileWriter::writeProcIDs(nLocalLines_);
+}
+
+
+// ************************************************************************* //
diff --git a/src/fileFormats/vtk/write/foamVtkLineWriter.H b/src/fileFormats/vtk/write/foamVtkLineWriter.H
new file mode 100644
index 00000000000..36245558f23
--- /dev/null
+++ b/src/fileFormats/vtk/write/foamVtkLineWriter.H
@@ -0,0 +1,180 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 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::vtk::lineWriter
+
+Description
+    Write edge/points (optionally with fields)
+    as a vtp file or a legacy vtk file.
+
+    The file output states are managed by the Foam::vtk::fileWriter class.
+    FieldData (eg, TimeValue) must appear before any geometry pieces.
+
+Note
+    Parallel output is combined into a single Piece without point merging,
+    which is similar to using multi-piece data sets, but allows more
+    convenient creation as a streaming process.
+    In the future, the duplicate points at processor connections
+    may be addressed using ghost points.
+
+SourceFiles
+    foamVtkLineWriter.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_vtk_lineWriter_H
+#define Foam_vtk_lineWriter_H
+
+#include "foamVtkPolyWriter.H"
+#include "instant.H"
+#include <functional>
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace vtk
+{
+
+/*---------------------------------------------------------------------------*\
+                       Class vtk::lineWriter Declaration
+\*---------------------------------------------------------------------------*/
+
+class lineWriter
+:
+    public vtk::polyWriter
+{
+    // Private Member Data
+
+        //- Reference to the points
+        std::reference_wrapper<const pointField> points_;
+
+        //- Reference to the edges
+        std::reference_wrapper<const edgeList> edges_;
+
+        //- Time name/value
+        instant instant_;
+
+
+    // Private Member Functions
+
+        //- No copy construct
+        lineWriter(const lineWriter&) = delete;
+
+        //- No copy assignment
+        void operator=(const lineWriter&) = delete;
+
+
+public:
+
+    // Constructors
+
+        //- Construct from components (default format INLINE_BASE64)
+        lineWriter
+        (
+            const pointField& pts,
+            const edgeList& edges,
+            const vtk::outputOptions opts = vtk::formatType::INLINE_BASE64
+        );
+
+        //- Construct from components (default format INLINE_BASE64),
+        //- and open the file for writing.
+        //  The file name is with/without an extension.
+        lineWriter
+        (
+            const pointField& pts,
+            const edgeList& edges,
+            const fileName& file,
+            bool parallel = Pstream::parRun()
+        );
+
+        //- Construct from components and open the file for writing.
+        //  The file name is with/without an extension.
+        lineWriter
+        (
+            const pointField& pts,
+            const edgeList& edges,
+            const vtk::outputOptions opts,
+            const fileName& file,
+            bool parallel = Pstream::parRun()
+        );
+
+
+    //- Destructor
+    virtual ~lineWriter() = default;
+
+
+    // Member Functions
+
+        //- Define a time name/value for the output
+        virtual void setTime(const instant& inst);
+
+        //- Write file header (non-collective)
+        //  \note Expected calling states: (OPENED).
+        virtual bool beginFile(std::string title = "");
+
+        //- Write patch topology
+        //  Also writes the file header if not previously written.
+        //  \note Must be called prior to writing CellData or PointData
+        virtual bool writeGeometry();
+
+        //- Write "TimeValue" FieldData (name as per Catalyst output)
+        //  Must be called within the FIELD_DATA state.
+        //  \note As a convenience this can also be called from
+        //      (OPENED | DECLARED) states, in which case it invokes
+        //      beginFieldData(1) internally.
+        using vtk::fileWriter::writeTimeValue;
+
+        //- Write the currently set time as "TimeValue" FieldData
+        void writeTimeValue();
+
+        //- Reset point/edge references to begin a new piece
+        void piece(const pointField& points, const edgeList& edges);
+
+
+        //- Write processor ids for each line as CellData
+        //- (no-op in serial)
+        bool writeProcIDs();
+
+        //- Write a uniform field of Cell (Line) or Point values
+        template<class Type>
+        void writeUniform(const word& fieldName, const Type& val)
+        {
+            polyWriter::writeUniformValue<Type>(nLocalLines_, fieldName, val);
+        }
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace vtk
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/fileFormats/vtk/write/foamVtkPolyWriter.C b/src/fileFormats/vtk/write/foamVtkPolyWriter.C
index f11894a554d..28de456d9c5 100644
--- a/src/fileFormats/vtk/write/foamVtkPolyWriter.C
+++ b/src/fileFormats/vtk/write/foamVtkPolyWriter.C
@@ -29,26 +29,86 @@ License
 #include "foamVtkOutput.H"
 #include "globalIndex.H"
 
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// The connectivity count for a list of edges
+static inline label countConnectivity(const edgeList& edges)
+{
+    return 2 * edges.size();  // An edge always has two ends
+}
+
+
+// The connectivity count for a list of faces
+static label countConnectivity(const faceList& faces)
+{
+    label nConnectivity = 0;
+
+    for (const face& f : faces)
+    {
+        nConnectivity += f.size();
+    }
+
+    return nConnectivity;
+}
+
+} // End namespace Foam
+
+
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
 void Foam::vtk::polyWriter::beginPiece
 (
     const pointField& points,
-    const faceList& faces
+    const edgeList& edges
 )
 {
     // Basic sizes
     nLocalPoints_ = points.size();
-    nLocalFaces_  = faces.size();
-    nLocalVerts_  = 0;
+    nLocalLines_  = edges.size();
+    nLocalPolys_  = 0;
 
-    for (const face& f : faces)
+    numberOfPoints_ = nLocalPoints_;
+    numberOfCells_  = nLocalLines_;
+
+    if (parallel_)
+    {
+        reduce(numberOfPoints_, sumOp<label>());
+        reduce(numberOfCells_,  sumOp<label>());
+    }
+
+
+    // Nothing else to do for legacy
+    if (legacy()) return;
+
+    if (format_)
     {
-        nLocalVerts_ += f.size();
+        format().tag
+        (
+            vtk::fileTag::PIECE,
+            vtk::fileAttr::NUMBER_OF_POINTS, numberOfPoints_,
+            vtk::fileAttr::NUMBER_OF_LINES,  numberOfCells_
+            // AND: vtk::fileAttr::NUMBER_OF_POLYS,  0
+        );
     }
+}
+
+
+void Foam::vtk::polyWriter::beginPiece
+(
+    const pointField& points,
+    const faceList& faces
+)
+{
+    // Basic sizes
+    nLocalPoints_ = points.size();
+    nLocalLines_  = 0;
+    nLocalPolys_  = faces.size();
 
     numberOfPoints_ = nLocalPoints_;
-    numberOfCells_  = nLocalFaces_;
+    numberOfCells_  = nLocalPolys_;
 
     if (parallel_)
     {
@@ -67,6 +127,7 @@ void Foam::vtk::polyWriter::beginPiece
             vtk::fileTag::PIECE,
             vtk::fileAttr::NUMBER_OF_POINTS, numberOfPoints_,
             vtk::fileAttr::NUMBER_OF_POLYS,  numberOfCells_
+            // AND: vtk::fileAttr::NUMBER_OF_LINES,  0
         );
     }
 }
@@ -79,48 +140,219 @@ void Foam::vtk::polyWriter::writePoints
 {
     this->beginPoints(numberOfPoints_);
 
-    if (parallel_ ? Pstream::master() : true)
+    if (parallel_)
+    {
+        vtk::writeListParallel(format_.ref(), points);
+    }
+    else
     {
+        vtk::writeList(format(), points);
+
+    }
+
+    this->endPoints();
+}
+
+
+void Foam::vtk::polyWriter::writeLinesLegacy
+(
+    const edgeList& edges,
+    const label pointOffset
+)
+{
+    // Connectivity count without additional storage (done internally)
+    const label nLocalConns = countConnectivity(edges);
+
+    label nLines = nLocalLines_;
+    label nConns = nLocalConns;
+
+    if (parallel_)
+    {
+        reduce(nLines, sumOp<label>());
+        reduce(nConns, sumOp<label>());
+    }
+
+    if (nLines != numberOfCells_)
+    {
+        FatalErrorInFunction
+            << "Expecting " << numberOfCells_
+            << " edges, but found " << nLines
+            << exit(FatalError);
+    }
+
+    legacy::beginLines(os_, nLines, nConns);
+
+    labelList vertLabels(nLocalLines_ + nLocalConns);
+
+    {
+        // Legacy: size + connectivity together
+        // [nPts, id1, id2, ..., nPts, id1, id2, ...]
+
+        auto iter = vertLabels.begin();
+
+        const label off = pointOffset;
+
+        for (const edge& e : edges)
         {
-            vtk::writeList(format(), points);
+            *iter = e.size();   // The size prefix (always 2 for an edge)
+            ++iter;
+
+            *iter = off + e.first();    // Vertex labels
+            ++iter;
+
+            *iter = off + e.second();
+            ++iter;
         }
     }
 
+
     if (parallel_)
     {
-        if (Pstream::master())
+        vtk::writeListParallel(format_.ref(), vertLabels);
+    }
+    else
+    {
+        vtk::writeList(format(), vertLabels);
+    }
+
+    if (format_)
+    {
+        format().flush();
+    }
+}
+
+
+void Foam::vtk::polyWriter::writeLines
+(
+    const edgeList& edges,
+    const label pointOffset
+)
+{
+    // Connectivity count without additional storage (done internally)
+    const label nLocalConns = countConnectivity(edges);
+
+    if (format_)
+    {
+        format().tag(vtk::fileTag::LINES);
+    }
+
+    //
+    // 'connectivity'
+    //
+    {
+        labelList vertLabels(nLocalConns);
+
+        label nConns = nLocalConns;
+
+        if (parallel_)
         {
-            pointField recv;
+            reduce(nConns, sumOp<label>());
+        }
 
-            // Receive each point field and write
-            for (const int subproci : Pstream::subProcs())
-            {
-                IPstream fromProc(Pstream::commsTypes::blocking, subproci);
+        if (format_)
+        {
+            const uint64_t payLoad = vtk::sizeofData<label>(nConns);
 
-                {
-                    fromProc >> recv;
+            format().beginDataArray<label>(vtk::dataArrayAttr::CONNECTIVITY);
+            format().writeSize(payLoad * sizeof(label));
+        }
 
-                    vtk::writeList(format(), recv);
-                }
+        {
+            // XML: connectivity only
+            // [id1, id2, ..., id1, id2, ...]
+
+            auto iter = vertLabels.begin();
+
+            const label off = pointOffset;
+
+            for (const edge& e : edges)
+            {
+                // Edge vertex labels
+                *iter = off + e.first();
+                ++iter;
+
+                *iter = off + e.second();
+                ++iter;
             }
         }
+
+
+        if (parallel_)
+        {
+            vtk::writeListParallel(format_.ref(), vertLabels);
+        }
         else
         {
-            // Send
-            OPstream toProc
-            (
-                Pstream::commsTypes::blocking,
-                Pstream::masterNo()
-            );
+            vtk::writeList(format(), vertLabels);
+        }
 
-            {
-                toProc << points;
-            }
+        if (format_)
+        {
+            format().flush();
+            format().endDataArray();
         }
     }
 
 
-    this->endPoints();
+    //
+    // 'offsets'  (connectivity offsets)
+    //
+    {
+        labelList vertOffsets(nLocalLines_);
+        label nOffs = vertOffsets.size();
+
+        if (parallel_)
+        {
+            reduce(nOffs, sumOp<label>());
+        }
+
+        if (format_)
+        {
+            const uint64_t payLoad = vtk::sizeofData<label>(nOffs);
+
+            format().beginDataArray<label>(vtk::dataArrayAttr::OFFSETS);
+            format().writeSize(payLoad);
+        }
+
+
+        // processor-local connectivity offsets
+        label off =
+        (
+            parallel_ ? globalIndex(nLocalConns).localStart() : 0
+        );
+
+
+        auto iter = vertOffsets.begin();
+
+        for (const edge& e : edges)
+        {
+            off += e.size();   // End offset
+            *iter = off;
+            ++iter;
+        }
+
+
+        if (parallel_)
+        {
+            vtk::writeListParallel(format_.ref(), vertOffsets);
+        }
+        else
+        {
+            vtk::writeList(format_.ref(), vertOffsets);
+        }
+
+
+        if (format_)
+        {
+            format().flush();
+            format().endDataArray();
+        }
+    }
+
+    if (format_)
+    {
+        format().endTag(vtk::fileTag::LINES);
+    }
 }
 
 
@@ -131,27 +363,28 @@ void Foam::vtk::polyWriter::writePolysLegacy
 )
 {
     // Connectivity count without additional storage (done internally)
+    const label nLocalConns = countConnectivity(faces);
 
-    label nFaces = nLocalFaces_;
-    label nVerts = nLocalVerts_;
+    label nPolys = nLocalPolys_;
+    label nConns = nLocalConns;
 
     if (parallel_)
     {
-        reduce(nFaces, sumOp<label>());
-        reduce(nVerts, sumOp<label>());
+        reduce(nPolys, sumOp<label>());
+        reduce(nConns, sumOp<label>());
     }
 
-    if (nFaces != numberOfCells_)
+    if (nPolys != numberOfCells_)
     {
         FatalErrorInFunction
             << "Expecting " << numberOfCells_
-            << " faces, but found " << nFaces
+            << " faces, but found " << nPolys
             << exit(FatalError);
     }
 
-    legacy::beginPolys(os_, nFaces, nVerts);
+    legacy::beginPolys(os_, nPolys, nConns);
 
-    labelList vertLabels(nLocalFaces_ + nLocalVerts_);
+    labelList vertLabels(nLocalPolys_ + nLocalConns);
 
     {
         // Legacy: size + connectivity together
@@ -159,21 +392,18 @@ void Foam::vtk::polyWriter::writePolysLegacy
 
         auto iter = vertLabels.begin();
 
-        label off = pointOffset;
+        const label off = pointOffset;
 
+        for (const face& f : faces)
         {
-            for (const face& f : faces)
+            *iter = f.size();       // The size prefix
+            ++iter;
+
+            for (const label id : f)
             {
-                *iter = f.size();       // The size prefix
+                *iter = id + off;   // Vertex label
                 ++iter;
-
-                for (const label pfi : f)
-                {
-                    *iter = pfi + off;  // Face vertex label
-                    ++iter;
-                }
             }
-            // off += points.size();
         }
     }
 
@@ -200,6 +430,9 @@ void Foam::vtk::polyWriter::writePolys
     const label pointOffset
 )
 {
+    // Connectivity count without additional storage (done internally)
+    const label nLocalConns = countConnectivity(faces);
+
     if (format_)
     {
         format().tag(vtk::fileTag::POLYS);
@@ -209,18 +442,18 @@ void Foam::vtk::polyWriter::writePolys
     // 'connectivity'
     //
     {
-        labelList vertLabels(nLocalVerts_);
+        labelList vertLabels(nLocalConns);
 
-        label nVerts = nLocalVerts_;
+        label nConns = nLocalConns;
 
         if (parallel_)
         {
-            reduce(nVerts, sumOp<label>());
+            reduce(nConns, sumOp<label>());
         }
 
         if (format_)
         {
-            const uint64_t payLoad = vtk::sizeofData<label>(nVerts);
+            const uint64_t payLoad = vtk::sizeofData<label>(nConns);
 
             format().beginDataArray<label>(vtk::dataArrayAttr::CONNECTIVITY);
             format().writeSize(payLoad * sizeof(label));
@@ -234,16 +467,13 @@ void Foam::vtk::polyWriter::writePolys
 
             label off = pointOffset;
 
+            for (const face& f : faces)
             {
-                for (const face& f : faces)
+                for (const label id : f)
                 {
-                    for (const label pfi : f)
-                    {
-                        *iter = pfi + off;  // Face vertex label
-                        ++iter;
-                    }
+                    *iter = id + off;  // Face vertex label
+                    ++iter;
                 }
-                // off += points.size();
             }
         }
 
@@ -269,7 +499,7 @@ void Foam::vtk::polyWriter::writePolys
     // 'offsets'  (connectivity offsets)
     //
     {
-        labelList vertOffsets(nLocalFaces_);
+        labelList vertOffsets(nLocalPolys_);
         label nOffs = vertOffsets.size();
 
         if (parallel_)
@@ -289,19 +519,17 @@ void Foam::vtk::polyWriter::writePolys
         // processor-local connectivity offsets
         label off =
         (
-            parallel_ ? globalIndex(nLocalVerts_).localStart() : 0
+            parallel_ ? globalIndex(nLocalConns).localStart() : 0
         );
 
 
         auto iter = vertOffsets.begin();
 
+        for (const face& f : faces)
         {
-            for (const face& f : faces)
-            {
-                off += f.size();   // End offset
-                *iter = off;
-                ++iter;
-            }
+            off += f.size();  // End offset
+            *iter = off;
+            ++iter;
         }
 
 
@@ -340,8 +568,8 @@ Foam::vtk::polyWriter::polyWriter
     numberOfPoints_(0),
     numberOfCells_(0),
     nLocalPoints_(0),
-    nLocalFaces_(0),
-    nLocalVerts_(0)
+    nLocalLines_(0),
+    nLocalPolys_(0)
 {
     // We do not currently support append mode
     opts_.append(false);
@@ -387,6 +615,36 @@ bool Foam::vtk::polyWriter::writeGeometry()
 }
 
 
+bool Foam::vtk::polyWriter::writeLineGeometry
+(
+    const pointField& points,
+    const edgeList& edges
+)
+{
+    enter_Piece();
+
+    beginPiece(points, edges);
+
+    writePoints(points);
+
+    const label pointOffset =
+    (
+        parallel_ ? globalIndex(nLocalPoints_).localStart() : 0
+    );
+
+    if (legacy())
+    {
+        writeLinesLegacy(edges, pointOffset);
+    }
+    else
+    {
+        writeLines(edges, pointOffset);
+    }
+
+    return true;
+}
+
+
 bool Foam::vtk::polyWriter::writePolyGeometry
 (
     const pointField& points,
diff --git a/src/fileFormats/vtk/write/foamVtkPolyWriter.H b/src/fileFormats/vtk/write/foamVtkPolyWriter.H
index 8dadb17f754..4c10b0f2bf5 100644
--- a/src/fileFormats/vtk/write/foamVtkPolyWriter.H
+++ b/src/fileFormats/vtk/write/foamVtkPolyWriter.H
@@ -51,6 +51,7 @@ SourceFiles
 
 #include "foamVtkFileWriter.H"
 #include "pointField.H"
+#include "edgeList.H"
 #include "faceList.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -75,30 +76,53 @@ protected:
         //- The number of field points for the current Piece
         label numberOfPoints_;
 
-        //- The number of field cells (faces) for the current Piece
+        //- The number of field cells (edges or faces) for the current Piece
         label numberOfCells_;
 
         //- Local number of points
         label nLocalPoints_;
 
-        //- Local number of faces
-        label nLocalFaces_;
+        //- Local number of lines (edges)
+        label nLocalLines_;
 
-        //- Local face vertices (connectivity) count. Sum of face sizes.
-        label nLocalVerts_;
+        //- Local number of polys (faces)
+        label nLocalPolys_;
 
 
+    // Protected Member Functions
+
+        //- Write a uniform field of Cell (Poly or Line) or Point values
+        template<class Type>
+        void writeUniformValue
+        (
+            const label nCellValues,  // Could be Poly or Line!
+            const word& fieldName,
+            const Type& val
+        );
+
 private:
 
     // Private Member Functions
 
-        //- Determine sizes (nLocalPoints_, nLocalFaces_, nLocalVerts_),
+        //- Determine sizes (nLocalPoints_, nLocalLines_),
+        //- and begin piece
+        void beginPiece(const pointField& points, const edgeList& edges);
+
+        //- Determine sizes (nLocalPoints_, nLocalPolys_),
         //- and begin piece
         void beginPiece(const pointField& points, const faceList& faces);
 
         //- Write points
         void writePoints(const pointField& points);
 
+        //- Write lines, legacy format
+        //  \param pointOffset processor-local point offset
+        void writeLinesLegacy(const edgeList& edges, const label pointOffset);
+
+        //- Write lines
+        //  \param pointOffset processor-local point offset
+        void writeLines(const edgeList& edges, const label pointOffset);
+
         //- Write faces, legacy format
         //  \param pointOffset processor-local point offset
         void writePolysLegacy(const faceList& faces, const label pointOffset);
@@ -163,6 +187,16 @@ public:
         //  their own geomety, but use writePolyGeometry() directly
         virtual bool writeGeometry();
 
+        //- Low-level write edge/point topology.
+        //- Normally used by writeGeometry() in a derived class
+        //  Also writes the file header if not previously written.
+        //  \note Must be called prior to writing CellData or PointData
+        bool writeLineGeometry
+        (
+            const pointField& points,
+            const edgeList& edges
+        );
+
         //- Low-level write face/point topology.
         //- Normally used by writeGeometry() in a derived class
         //  Also writes the file header if not previously written.
@@ -194,11 +228,7 @@ public:
 
     // Write
 
-        //- Write a uniform field of Cell (Face) or Point values
-        template<class Type>
-        void writeUniform(const word& fieldName, const Type& val);
-
-        //- Write a list of Cell (Face) or Point values
+        //- Write a list of Cell (Poly or Line) or Point values
         template<class Type>
         void write(const word& fieldName, const UList<Type>& field);
 };
diff --git a/src/fileFormats/vtk/write/foamVtkPolyWriterTemplates.C b/src/fileFormats/vtk/write/foamVtkPolyWriterTemplates.C
index b0db6d1cfec..319836b4637 100644
--- a/src/fileFormats/vtk/write/foamVtkPolyWriterTemplates.C
+++ b/src/fileFormats/vtk/write/foamVtkPolyWriterTemplates.C
@@ -25,24 +25,27 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
 template<class Type>
-void Foam::vtk::polyWriter::writeUniform
+void Foam::vtk::polyWriter::writeUniformValue
 (
+    const label nCellValues,
     const word& fieldName,
     const Type& val
 )
 {
+    label nValues(0);
+
     if (isState(outputState::CELL_DATA))
     {
         ++nCellData_;
-        vtk::fileWriter::writeUniform<Type>(fieldName, val, numberOfCells_);
+        nValues = nCellValues;
     }
     else if (isState(outputState::POINT_DATA))
     {
         ++nPointData_;
-        vtk::fileWriter::writeUniform<Type>(fieldName, val, numberOfPoints_);
+        nValues = nLocalPoints_;
     }
     else
     {
@@ -54,10 +57,16 @@ void Foam::vtk::polyWriter::writeUniform
         )
             << " for uniform field " << fieldName << nl << endl
             << exit(FatalError);
+
+        return;
     }
+
+    vtk::fileWriter::writeUniform<Type>(fieldName, val, nValues);
 }
 
 
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
 template<class Type>
 void Foam::vtk::polyWriter::write
 (
@@ -66,18 +75,18 @@ void Foam::vtk::polyWriter::write
 )
 {
     // Could check sizes:
-    //     nValues == nLocalFaces (CELL_DATA)
-    //     nValues == nLocalPoints (POINT_DATA)
+    // CELL_DATA:   nValues == (nLocalPolys | nLocalLines)
+    // POINT_DATA:  nValues == nLocalPoints
+
+    // const label nValues = field.size();
 
     if (isState(outputState::CELL_DATA))
     {
         ++nCellData_;
-        vtk::fileWriter::writeBasicField<Type>(fieldName, field);
     }
     else if (isState(outputState::POINT_DATA))
     {
         ++nPointData_;
-        vtk::fileWriter::writeBasicField<Type>(fieldName, field);
     }
     else
     {
@@ -89,7 +98,10 @@ void Foam::vtk::polyWriter::write
         )
             << " for field " << fieldName << nl << endl
             << exit(FatalError);
+        return;
     }
+
+    vtk::fileWriter::writeBasicField<Type>(fieldName, field);
 }
 
 
diff --git a/src/fileFormats/vtk/write/foamVtkSurfaceWriter.C b/src/fileFormats/vtk/write/foamVtkSurfaceWriter.C
index 9ee9709d861..7b07cace360 100644
--- a/src/fileFormats/vtk/write/foamVtkSurfaceWriter.C
+++ b/src/fileFormats/vtk/write/foamVtkSurfaceWriter.C
@@ -88,7 +88,7 @@ bool Foam::vtk::surfaceWriter::beginFile(std::string title)
         return vtk::fileWriter::beginFile(title);
     }
 
-    if (instant_.name().size())
+    if (!instant_.name().empty())
     {
         return vtk::fileWriter::beginFile
         (
@@ -129,4 +129,10 @@ void Foam::vtk::surfaceWriter::piece
 }
 
 
+bool Foam::vtk::surfaceWriter::writeProcIDs()
+{
+    return vtk::fileWriter::writeProcIDs(nLocalPolys_);
+}
+
+
 // ************************************************************************* //
diff --git a/src/fileFormats/vtk/write/foamVtkSurfaceWriter.H b/src/fileFormats/vtk/write/foamVtkSurfaceWriter.H
index 830ee6132d2..0a9d2ccdf36 100644
--- a/src/fileFormats/vtk/write/foamVtkSurfaceWriter.H
+++ b/src/fileFormats/vtk/write/foamVtkSurfaceWriter.H
@@ -42,7 +42,6 @@ Note
 
 SourceFiles
     foamVtkSurfaceWriter.C
-    foamVtkSurfaceWriterTemplates.C
 
 \*---------------------------------------------------------------------------*/
 
@@ -152,8 +151,20 @@ public:
         //- Write the currently set time as "TimeValue" FieldData
         void writeTimeValue();
 
-        //- Reset point, face references to begin a new piece
+        //- Reset point/face references to begin a new piece
         void piece(const pointField& points, const faceList& faces);
+
+
+        //- Write processor ids for each poly as CellData
+        //- (no-op in serial)
+        bool writeProcIDs();
+
+        //- Write a uniform field of Cell (Poly) or Point values
+        template<class Type>
+        void writeUniform(const word& fieldName, const Type& val)
+        {
+            polyWriter::writeUniformValue<Type>(nLocalPolys_, fieldName, val);
+        }
 };
 
 
diff --git a/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.C b/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.C
index 7f1951a9711..782ce50b0ea 100644
--- a/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.C
+++ b/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,50 +28,9 @@ License
 
 #include "VTKedgeFormat.H"
 #include "Fstream.H"
-#include "clock.H"
-#include "vtkUnstructuredReader.H"
 #include "Time.H"
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void Foam::fileFormats::VTKedgeFormat::writeHeader
-(
-    Ostream& os,
-    const pointField& pointLst
-)
-{
-    // Write header
-    os  << "# vtk DataFile Version 2.0" << nl
-        << "featureEdgeMesh written " << clock::dateTime().c_str() << nl
-        << "ASCII" << nl
-        << nl
-        << "DATASET POLYDATA" << nl;
-
-    // Write vertex coords
-    os  << "POINTS " << pointLst.size() << " double" << nl;
-    for (const point& pt : pointLst)
-    {
-        os  << float(pt.x()) << ' '
-            << float(pt.y()) << ' '
-            << float(pt.z()) << nl;
-    }
-}
-
-
-void Foam::fileFormats::VTKedgeFormat::writeEdges
-(
-    Ostream& os,
-    const UList<edge>& edgeLst
-)
-{
-    os  << "LINES " << edgeLst.size() << ' ' << 3*edgeLst.size() << nl;
-
-    for (const edge& e : edgeLst)
-    {
-        os  << "2 " << e[0] << ' ' << e[1] << nl;
-    }
-}
-
+#include "foamVtkLineWriter.H"
+#include "vtkUnstructuredReader.H"
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
@@ -122,19 +81,21 @@ bool Foam::fileFormats::VTKedgeFormat::read
     storedPoints().transfer(reader.points());
 
     label nEdges = 0;
-    forAll(reader.lines(), lineI)
+    for (const auto& lineVerts : reader.lines())
     {
-        nEdges += reader.lines()[lineI].size()-1;
+        if (lineVerts.size() > 1)
+        {
+            nEdges += (lineVerts.size()-1);
+        }
     }
-    storedEdges().setSize(nEdges);
+    storedEdges().resize(nEdges);
 
     nEdges = 0;
-    forAll(reader.lines(), lineI)
+    for (const auto& lineVerts : reader.lines())
     {
-        const labelList& verts = reader.lines()[lineI];
-        for (label i = 1; i < verts.size(); i++)
+        for (label i = 1; i < lineVerts.size(); ++i)
         {
-            storedEdges()[nEdges++] = edge(verts[i-1], verts[i]);
+            storedEdges()[nEdges++] = edge(lineVerts[i-1], lineVerts[i]);
         }
     }
 
@@ -148,16 +109,20 @@ void Foam::fileFormats::VTKedgeFormat::write
     const edgeMesh& eMesh
 )
 {
-    OFstream os(filename);
-    if (!os.good())
-    {
-        FatalErrorInFunction
-            << "Cannot open file for writing " << filename
-            << exit(FatalError);
-    }
+    // NB: restrict output to legacy ascii so that we are still able
+    // to read it with vtkUnstructuredReader
+
+    vtk::lineWriter writer
+    (
+        eMesh.points(),
+        eMesh.edges(),
+        vtk::formatType::LEGACY_ASCII,
+        filename,
+        false  // non-parallel write (edgeMesh already serialized)
+    );
 
-    writeHeader(os, eMesh.points());
-    writeEdges(os, eMesh.edges());
+    writer.beginFile("OpenFOAM edgeMesh");
+    writer.writeGeometry();
 }
 
 
diff --git a/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.H b/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.H
index ac88e0bc8db..084828d639c 100644
--- a/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.H
+++ b/src/meshTools/edgeMesh/edgeMeshFormats/vtk/VTKedgeFormat.H
@@ -54,37 +54,12 @@ class VTKedgeFormat
 :
     public edgeMesh
 {
-    // Private Member Functions
-
-        //- No copy construct
-        VTKedgeFormat(const VTKedgeFormat&) = delete;
-
-        //- No copy assignment
-        void operator=(const VTKedgeFormat&) = delete;
-
-
-protected:
-
-    // Protected Member Functions
-
-        //- Write header information with points
-        static void writeHeader
-        (
-            Ostream&,
-            const pointField&
-        );
-
-        //- Write edges
-        static void writeEdges(Ostream&, const UList<edge>&);
-
-
 public:
 
-
     // Constructors
 
-        //- Construct from file name
-        VTKedgeFormat(const fileName&);
+        //- Read construct from file name
+        explicit VTKedgeFormat(const fileName& filename);
 
 
     // Selectors
@@ -103,12 +78,12 @@ public:
     // Member Functions
 
         //- Write surface mesh components by proxy
-        static void write(const fileName&, const edgeMesh&);
+        static void write(const fileName& name, const edgeMesh&);
 
         //- Read from file
-        virtual bool read(const fileName&);
+        virtual bool read(const fileName& name);
 
-        //- Write object file
+        //- Write object to file
         virtual void write(const fileName& name) const
         {
             write(name, *this);
diff --git a/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.C b/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.C
index be2bf99fa49..a5ee5abd525 100644
--- a/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.C
+++ b/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.C
@@ -45,16 +45,24 @@ void Foam::vtk::internalMeshWriter::beginPiece()
 
     if (parallel_)
     {
+        if (debug > 1)
+        {
+            PoutInFunction
+                << ": nPoints=" << numberOfPoints_
+                << " nCells=" << numberOfCells_ << nl;
+        }
+
         reduce(numberOfPoints_, sumOp<label>());
         reduce(numberOfCells_,  sumOp<label>());
     }
 
+    DebugInFunction
+        << "nPoints=" << numberOfPoints_
+        << " nCells=" << numberOfCells_ << nl;
+
     // Nothing else to do for legacy
     if (legacy()) return;
 
-    DebugInFunction
-        << "nPoints=" << numberOfPoints_ << " nCells=" << numberOfCells_ << nl;
-
     if (format_)
     {
         format()
@@ -178,7 +186,10 @@ void Foam::vtk::internalMeshWriter::writeCellsLegacy(const label pointOffset)
 }
 
 
-void Foam::vtk::internalMeshWriter::writeCellsConnectivity(const label pointOffset)
+void Foam::vtk::internalMeshWriter::writeCellsConnectivity
+(
+    const label pointOffset
+)
 {
     //
     // 'connectivity'
@@ -316,7 +327,10 @@ void Foam::vtk::internalMeshWriter::writeCellsConnectivity(const label pointOffs
 }
 
 
-void Foam::vtk::internalMeshWriter::writeCellsFaces(const label pointOffset)
+void Foam::vtk::internalMeshWriter::writeCellsFaces
+(
+    const label pointOffset
+)
 {
     label nFaceLabels = vtuCells_.faceLabels().size();
 
@@ -628,39 +642,7 @@ bool Foam::vtk::internalMeshWriter::writeProcIDs()
         return false;
     }
 
-    if (isState(outputState::CELL_DATA))
-    {
-        ++nCellData_;
-    }
-    else
-    {
-        reportBadState(FatalErrorInFunction, outputState::CELL_DATA)
-            << " for procID field" << nl << endl
-            << exit(FatalError);
-    }
-
-    const globalIndex procMaps(vtuCells_.nFieldCells());
-
-    this->beginDataArray<label>("procID", procMaps.size());
-
-    bool good = false;
-
-    if (Pstream::master())
-    {
-        // Per-processor ids
-        for (const int proci : Pstream::allProcs())
-        {
-            vtk::write(format(), label(proci), procMaps.localSize(proci));
-        }
-
-        good = true;
-    }
-
-    this->endDataArray();
-
-
-    // MPI barrier
-    return returnReduce(good, orOp<bool>());
+    return vtk::fileWriter::writeProcIDs(vtuCells_.nFieldCells());
 }
 
 
diff --git a/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.H b/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.H
index 17c69893038..00da9739a7a 100644
--- a/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.H
+++ b/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriter.H
@@ -225,6 +225,10 @@ public:
         //- Write primitive field of CellData
         template<class Type>
         void writeCellData(const word& fieldName, const UList<Type>& field);
+
+        //- Write primitive field of PointData
+        template<class Type>
+        void writePointData(const word& fieldName, const UList<Type>& field);
 };
 
 
diff --git a/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriterTemplates.C b/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriterTemplates.C
index 866117286a7..ecf419c903e 100644
--- a/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriterTemplates.C
+++ b/src/meshTools/output/vtk/mesh/foamVtkInternalMeshWriterTemplates.C
@@ -37,15 +37,17 @@ void Foam::vtk::internalMeshWriter::writeUniform
     const Type& val
 )
 {
+    label nValues(0);
+
     if (isState(outputState::CELL_DATA))
     {
         ++nCellData_;
-        vtk::fileWriter::writeUniform<Type>(fieldName, val, numberOfCells_);
+        nValues = vtuCells_.nFieldCells();
     }
     else if (isState(outputState::POINT_DATA))
     {
         ++nPointData_;
-        vtk::fileWriter::writeUniform<Type>(fieldName, val, numberOfPoints_);
+        nValues = vtuCells_.nFieldPoints();
     }
     else
     {
@@ -54,9 +56,14 @@ void Foam::vtk::internalMeshWriter::writeUniform
             FatalErrorInFunction,
             outputState::CELL_DATA,
             outputState::POINT_DATA
-        )   << " for field " << fieldName << nl << endl
+        )
+            << " for uniform field " << fieldName << nl << endl
             << exit(FatalError);
+
+        return;
     }
+
+    vtk::fileWriter::writeUniform<Type>(fieldName, val, nValues);
 }
 
 
@@ -95,4 +102,37 @@ void Foam::vtk::internalMeshWriter::writeCellData
 }
 
 
+template<class Type>
+void Foam::vtk::internalMeshWriter::writePointData
+(
+    const word& fieldName,
+    const UList<Type>& field
+)
+{
+    if (isState(outputState::POINT_DATA))
+    {
+        ++nPointData_;
+    }
+    else
+    {
+        reportBadState(FatalErrorInFunction, outputState::POINT_DATA)
+            << " for field " << fieldName << nl << endl
+            << exit(FatalError);
+    }
+
+    this->beginDataArray<Type>(fieldName, numberOfPoints_);
+
+    if (parallel_)
+    {
+        vtk::writeListParallel(format_.ref(), field);
+    }
+    else
+    {
+        vtk::writeList(format(), field);
+    }
+
+    this->endDataArray();
+}
+
+
 // ************************************************************************* //
diff --git a/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.C b/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.C
index acec853d6b3..63f85246a6d 100644
--- a/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.C
+++ b/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.C
@@ -38,14 +38,15 @@ void Foam::vtk::patchMeshWriter::beginPiece()
     // Basic sizes
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
-    nLocalPoints_ = nLocalFaces_ = nLocalVerts_ = 0;
+    nLocalPoints_ = nLocalPolys_ = 0;
+    nLocalVerts_ = 0;
 
     for (const label patchId : patchIDs_)
     {
         const polyPatch& pp = patches[patchId];
 
         nLocalPoints_ += pp.nPoints();
-        nLocalFaces_  += pp.size();
+        nLocalPolys_  += pp.size();
 
         for (const face& f : pp)
         {
@@ -54,7 +55,7 @@ void Foam::vtk::patchMeshWriter::beginPiece()
     }
 
     numberOfPoints_ = nLocalPoints_;
-    numberOfCells_ = nLocalFaces_;
+    numberOfCells_ = nLocalPolys_;
 
     if (parallel_)
     {
@@ -148,26 +149,26 @@ void Foam::vtk::patchMeshWriter::writePolysLegacy(const label pointOffset)
 
     // Connectivity count without additional storage (done internally)
 
-    label nFaces = nLocalFaces_;
+    label nPolys = nLocalPolys_;
     label nVerts = nLocalVerts_;
 
     if (parallel_)
     {
-        reduce(nFaces, sumOp<label>());
+        reduce(nPolys, sumOp<label>());
         reduce(nVerts, sumOp<label>());
     }
 
-    if (nFaces != numberOfCells_)
+    if (nPolys != numberOfCells_)
     {
         FatalErrorInFunction
             << "Expecting " << numberOfCells_
-            << " faces, but found " << nFaces
+            << " faces, but found " << nPolys
             << exit(FatalError);
     }
 
-    legacy::beginPolys(os_, nFaces, nVerts);
+    legacy::beginPolys(os_, nPolys, nVerts);
 
-    labelList vertLabels(nLocalFaces_ + nLocalVerts_);
+    labelList vertLabels(nLocalPolys_ + nLocalVerts_);
 
     {
         // Legacy: size + connectivity together
@@ -186,9 +187,9 @@ void Foam::vtk::patchMeshWriter::writePolysLegacy(const label pointOffset)
                 *iter = f.size();       // The size prefix
                 ++iter;
 
-                for (const label pfi : f)
+                for (const label id : f)
                 {
-                    *iter = pfi + off;  // Face vertex label
+                    *iter = id + off;   // Face vertex label
                     ++iter;
                 }
             }
@@ -258,9 +259,9 @@ void Foam::vtk::patchMeshWriter::writePolys(const label pointOffset)
 
                 for (const face& f : pp.localFaces())
                 {
-                    for (const label pfi : f)
+                    for (const label id : f)
                     {
-                        *iter = pfi + off;  // Face vertex label
+                        *iter = id + off;   // Face vertex label
                         ++iter;
                     }
                 }
@@ -290,7 +291,7 @@ void Foam::vtk::patchMeshWriter::writePolys(const label pointOffset)
     // 'offsets'  (connectivity offsets)
     //
     {
-        labelList vertOffsets(nLocalFaces_);
+        labelList vertOffsets(nLocalPolys_);
         label nOffs = vertOffsets.size();
 
         if (parallel_)
@@ -367,7 +368,7 @@ Foam::vtk::patchMeshWriter::patchMeshWriter
     numberOfPoints_(0),
     numberOfCells_(0),
     nLocalPoints_(0),
-    nLocalFaces_(0),
+    nLocalPolys_(0),
     nLocalVerts_(0),
 
     mesh_(mesh),
@@ -512,15 +513,15 @@ void Foam::vtk::patchMeshWriter::writePatchIDs()
 
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
-    label nFaces = nLocalFaces_;
+    label nPolys = nLocalPolys_;
 
     if (parallel_)
     {
-        reduce(nFaces, sumOp<label>());
+        reduce(nPolys, sumOp<label>());
     }
 
 
-    this->beginDataArray<label>("patchID", nFaces);
+    this->beginDataArray<label>("patchID", nPolys);
 
     if (parallel_ ? Pstream::master() : true)
     {
@@ -584,66 +585,7 @@ void Foam::vtk::patchMeshWriter::writePatchIDs()
 
 bool Foam::vtk::patchMeshWriter::writeProcIDs()
 {
-    // This is different than for internalWriter.
-    // Here we allow procIDs whenever running in parallel, even if the
-    // output is serial. This allow diagnosis of processor patches.
-
-    if (!Pstream::parRun())
-    {
-        // Skip in non-parallel
-        return false;
-    }
-
-    if (isState(outputState::CELL_DATA))
-    {
-        ++nCellData_;
-    }
-    else
-    {
-        reportBadState(FatalErrorInFunction, outputState::CELL_DATA)
-            << " for patchID field" << nl << endl
-            << exit(FatalError);
-    }
-
-    label nFaces = nLocalFaces_;
-
-    if (parallel_)
-    {
-        reduce(nFaces, sumOp<label>());
-    }
-
-
-    this->beginDataArray<label>("procID", nFaces);
-
-    bool good = false;
-
-    if (parallel_)
-    {
-        globalIndex procSizes(nLocalFaces_);
-
-        if (Pstream::master())
-        {
-            // Per-processor ids
-            for (const int proci : Pstream::allProcs())
-            {
-                vtk::write(format(), label(proci), procSizes.localSize(proci));
-            }
-
-            good = true;
-        }
-    }
-    else
-    {
-        vtk::write(format(), label(Pstream::myProcNo()), nLocalFaces_);
-
-        good = true;
-    }
-
-
-    this->endDataArray();
-
-    // MPI barrier
-    return parallel_ ? returnReduce(good, orOp<bool>()) : good;
+    return vtk::fileWriter::writeProcIDs(nLocalPolys_);
 }
 
 
@@ -668,15 +610,15 @@ bool Foam::vtk::patchMeshWriter::writeNeighIDs()
 
     const polyBoundaryMesh& patches = mesh_.boundaryMesh();
 
-    label nFaces = nLocalFaces_;
+    label nPolys = nLocalPolys_;
 
     if (parallel_)
     {
-        reduce(nFaces, sumOp<label>());
+        reduce(nPolys, sumOp<label>());
     }
 
 
-    this->beginDataArray<label>("neighID", nFaces);
+    this->beginDataArray<label>("neighID", nPolys);
 
     bool good = false;
 
diff --git a/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.H b/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.H
index 2a26482be14..0622d66d802 100644
--- a/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.H
+++ b/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriter.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -82,8 +82,8 @@ protected:
         //- Local number of points
         label nLocalPoints_;
 
-        //- Local number of faces
-        label nLocalFaces_;
+        //- Local number of polys (faces)
+        label nLocalPolys_;
 
         //- Local face vertices (connectivity) count. Sum of face sizes.
         label nLocalVerts_;
@@ -97,7 +97,7 @@ protected:
 
     // Private Member Functions
 
-        //- Determine sizes (nLocalPoints_, nLocalFaces_, nLocalVerts_),
+        //- Determine sizes (nLocalPoints_, nLocalPolys_),
         //- and begin piece.
         void beginPiece();
 
@@ -172,7 +172,7 @@ public:
         }
 
         //- The patch IDs
-        inline const labelList& patchIDs() const
+        const labelList& patchIDs() const noexcept
         {
             return patchIDs_;
         }
diff --git a/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriterTemplates.C b/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriterTemplates.C
index ef81dcb7822..349a6b28fcd 100644
--- a/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriterTemplates.C
+++ b/src/meshTools/output/vtk/patch/foamVtkPatchMeshWriterTemplates.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -37,23 +37,33 @@ void Foam::vtk::patchMeshWriter::writeUniform
     const Type& val
 )
 {
+    label nValues(0);
+
     if (isState(outputState::CELL_DATA))
     {
         ++nCellData_;
-        vtk::fileWriter::writeUniform<Type>(fieldName, val, numberOfCells_);
+        nValues = nLocalPolys_;
     }
     else if (isState(outputState::POINT_DATA))
     {
         ++nPointData_;
-        vtk::fileWriter::writeUniform<Type>(fieldName, val, numberOfPoints_);
+        nValues = nLocalPoints_;
     }
     else
     {
-        WarningInFunction
-            << "Ignore bad writer state (" << stateNames[state_]
-            << ") for field " << fieldName << nl << endl
+        reportBadState
+        (
+            FatalErrorInFunction,
+            outputState::CELL_DATA,
+            outputState::POINT_DATA
+        )
+            << " for uniform field " << fieldName << nl << endl
             << exit(FatalError);
+
+        return;
     }
+
+    vtk::fileWriter::writeUniform<Type>(fieldName, val, nValues);
 }
 
 
-- 
GitLab