From 2cb6e5baa9aa5828266d92c338fc32f524bb504d Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Mon, 14 Sep 2020 16:39:36 +0200
Subject: [PATCH] ENH: initial support for abaqus surface sampled output
 (#1600)

- supports geometry and field-specific scaling, separate geometry and
  fields. Beta-feature for suppressing geometry output entirely.
---
 src/surfMesh/Make/files                       |   1 +
 .../writers/abaqus/abaqusSurfaceWriter.C      | 355 ++++++++++++++++++
 .../writers/abaqus/abaqusSurfaceWriter.H      | 239 ++++++++++++
 .../writers/abaqus/abaqusSurfaceWriterImpl.C  | 308 +++++++++++++++
 4 files changed, 903 insertions(+)
 create mode 100644 src/surfMesh/writers/abaqus/abaqusSurfaceWriter.C
 create mode 100644 src/surfMesh/writers/abaqus/abaqusSurfaceWriter.H
 create mode 100644 src/surfMesh/writers/abaqus/abaqusSurfaceWriterImpl.C

diff --git a/src/surfMesh/Make/files b/src/surfMesh/Make/files
index 7c68022eb8b..eac20cc94a2 100644
--- a/src/surfMesh/Make/files
+++ b/src/surfMesh/Make/files
@@ -63,6 +63,7 @@ writers = writers
 
 $(writers)/surfaceWriter.C
 $(writers)/caching/surfaceWriterCaching.C
+$(writers)/abaqus/abaqusSurfaceWriter.C
 $(writers)/boundaryData/boundaryDataSurfaceWriter.C
 $(writers)/ensight/ensightSurfaceWriter.C
 $(writers)/foam/foamSurfaceWriter.C
diff --git a/src/surfMesh/writers/abaqus/abaqusSurfaceWriter.C b/src/surfMesh/writers/abaqus/abaqusSurfaceWriter.C
new file mode 100644
index 00000000000..8932d906849
--- /dev/null
+++ b/src/surfMesh/writers/abaqus/abaqusSurfaceWriter.C
@@ -0,0 +1,355 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "abaqusSurfaceWriter.H"
+#include "ABAQUSCore.H"
+#include "IOmanip.H"
+#include "ListOps.H"
+#include "OSspecific.H"
+#include "surfaceWriterMethods.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace surfaceWriters
+{
+    defineTypeName(abaqusWriter);
+    addToRunTimeSelectionTable(surfaceWriter, abaqusWriter, word);
+    addToRunTimeSelectionTable(surfaceWriter, abaqusWriter, wordDict);
+}
+}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Field writing implementation
+#include "abaqusSurfaceWriterImpl.C"
+
+// Field writing methods
+defineSurfaceWriterWriteFields(Foam::surfaceWriters::abaqusWriter);
+
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Write connectivity as CSV list
+inline static void writeConnectivity
+(
+    Ostream& os,
+    const label elemId,
+    const labelUList& elem
+)
+{
+    os  << "  " << elemId;
+
+    for (const label vert : elem)
+    {
+        os << ", " << (vert + 1);
+    }
+
+    os << nl;
+}
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::surfaceWriters::abaqusWriter::writeFace
+(
+    Ostream& os,
+    const labelUList& f,
+    const label elemId,
+    const label propId,
+    bool header
+) const
+{
+    // Only called with 3 or 4 points!
+
+    if (header)
+    {
+        os  << "*ELEMENT, TYPE=S" << f.size();
+
+        if (propId >= 0)
+        {
+            os  << ", ELSET=_" << propId;
+        }
+
+        os  << nl;
+    }
+
+    writeConnectivity(os, elemId, f);
+}
+
+
+void Foam::surfaceWriters::abaqusWriter::writeGeometry
+(
+    Ostream& os,
+    const meshedSurf& surf,
+    labelList& decompOffsets,
+    DynamicList<face>& decompFaces
+) const
+{
+    const pointField& points = surf.points();
+    const faceList&    faces = surf.faces();
+    const labelList&   zones = surf.zoneIds();
+    const labelList& elemIds = surf.faceIds();
+
+    // Possible to use faceIds?
+    bool useOrigFaceIds =
+    (
+        elemIds.size() == faces.size()
+     && !ListOps::found(elemIds, lessOp1<label>(0))
+    );
+
+    if (useOrigFaceIds)
+    {
+        // Not possible with on-the-fly face decomposition
+        for (const auto& f : faces)
+        {
+            if (f.size() > 4)
+            {
+                useOrigFaceIds = false;
+                break;
+            }
+        }
+    }
+
+
+    os  << "** Geometry" << nl;
+
+    os  << nl
+        << "**" << nl
+        << "** Points" << nl
+        << "**" << nl;
+
+    fileFormats::ABAQUSCore::writePoints(os, points, geometryScale_);
+
+
+    // Write faces, with on-the-fly decomposition (triangulation)
+    decompOffsets.resize(faces.size()+1);
+    decompFaces.clear();
+
+    decompOffsets[0] = 0; // The first offset is always zero
+
+    os  << "**" << nl
+        << "** Faces" << nl
+        << "**" << nl;
+
+    // Simple tracking for change of element type/set
+    labelPair prevOutput(-1, -1);
+
+    label elemId = 0;  // The element-id
+    forAll(faces, facei)
+    {
+        const face& f = faces[facei];
+
+        if (useOrigFaceIds)
+        {
+            // When available and not decomposed
+            elemId = elemIds[facei];
+        }
+
+        // 1-offset for PID
+        const label propId = 1 + (facei < zones.size() ? zones[facei] : 0);
+
+        const label n = f.size();
+
+        bool header =
+            (prevOutput.first() != n || prevOutput.second() != propId);
+
+        if (header)
+        {
+            // Update values
+            prevOutput.first() = n;
+            prevOutput.second() = propId;
+        }
+
+        if (n == 3 || n == 4)
+        {
+            writeFace(os, f, ++elemId, propId, header);
+        }
+        else
+        {
+            // Decompose into tris
+            prevOutput.first() = 3;
+
+            f.triangles(points, decompFaces);
+
+            for
+            (
+                label decompi = decompOffsets[facei];
+                decompi < decompFaces.size();
+                ++decompi
+            )
+            {
+                writeFace
+                (
+                    os,
+                    decompFaces[decompi],
+                    ++elemId,
+                    propId,
+                    header
+                );
+
+                header = false;
+            }
+        }
+
+        // The end offset, which is the next begin offset
+        decompOffsets[facei+1] = decompFaces.size();
+    }
+
+    os  << "**" << nl
+        << "**" << nl;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::surfaceWriters::abaqusWriter::abaqusWriter()
+:
+    surfaceWriter(),
+    geometryScale_(1),
+    fieldScale_(),
+    noGeometry_(false),
+    outputLayout_(outputLayoutType::BY_FIELD)
+{}
+
+
+Foam::surfaceWriters::abaqusWriter::abaqusWriter
+(
+    const dictionary& options
+)
+:
+    surfaceWriter(options),
+    geometryScale_(options.getOrDefault<scalar>("scale", 1)),
+    fieldScale_(options.subOrEmptyDict("fieldScale")),
+    noGeometry_(options.getOrDefault("noGeometry", false)),
+    outputLayout_(outputLayoutType::BY_FIELD)
+{}
+
+
+Foam::surfaceWriters::abaqusWriter::abaqusWriter
+(
+    const meshedSurf& surf,
+    const fileName& outputPath,
+    bool parallel,
+    const dictionary& options
+)
+:
+    abaqusWriter(options)
+{
+    open(surf, outputPath, parallel);
+}
+
+
+Foam::surfaceWriters::abaqusWriter::abaqusWriter
+(
+    const pointField& points,
+    const faceList& faces,
+    const fileName& outputPath,
+    bool parallel,
+    const dictionary& options
+)
+:
+    abaqusWriter(options)
+{
+    open(points, faces, outputPath, parallel);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+Foam::fileName Foam::surfaceWriters::abaqusWriter::write()
+{
+    checkOpen();
+
+    // Geometry:
+    // 1) rootdir/<TIME>/surfaceName.abq
+    // 2) rootdir/geometry/surfaceName_<TIME>.abq
+
+    fileName outputFile;
+
+    switch (outputLayout_)
+    {
+        case outputLayoutType::BY_TIME:
+        {
+            outputFile = outputPath_;
+            if (useTimeDir() && !timeName().empty())
+            {
+                // Splice in time-directory
+                outputFile =
+                    outputPath_.path() / timeName() / outputPath_.name();
+            }
+            break;
+        }
+        case outputLayoutType::BY_FIELD:
+        {
+            outputFile = outputPath_ / "geometry" / outputPath_.name();
+            if (!timeName().empty())
+            {
+                // Append time information to file name
+                outputFile += '_' + timeName();
+            }
+            break;
+        }
+    }
+    outputFile.ext("abq");
+
+    if (verbose_)
+    {
+        Info<< "Writing abaqus geometry to " << outputFile << endl;
+    }
+
+
+    const meshedSurf& surf = surface();
+
+    if (Pstream::master() || !parallel_)
+    {
+        if (!isDir(outputFile.path()))
+        {
+            mkDir(outputFile.path());
+        }
+
+        OFstream os(outputFile);
+
+        labelList decompOffsets;
+        DynamicList<face> decompFaces;
+
+        writeGeometry(os, surf, decompOffsets, decompFaces);
+    }
+
+    wroteGeom_ = true;
+    return outputFile;
+}
+
+
+// ************************************************************************* //
diff --git a/src/surfMesh/writers/abaqus/abaqusSurfaceWriter.H b/src/surfMesh/writers/abaqus/abaqusSurfaceWriter.H
new file mode 100644
index 00000000000..d7409862986
--- /dev/null
+++ b/src/surfMesh/writers/abaqus/abaqusSurfaceWriter.H
@@ -0,0 +1,239 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::surfaceWriters::abaqusWriter
+
+Description
+    A surface writer for the ABAQUS file format - both surface mesh and fields
+
+    The formatOptions for abaqus:
+    \table
+        Property   | Description                            | Required | Default
+        scale      | output geometry scaling                | no  | 1
+        fieldScale | output field scaling (dictionary)      | no  | empty
+        noGeometry | Suppress geometry output (beta feature) | no  | false
+    \endtable
+
+    For example,
+    \verbatim
+    formatOptions
+    {
+        abaqus
+        {
+            scale   1000;     // [m] -> [mm]
+            fieldScale
+            {
+               "p.*"   0.01;  // [Pa] -> [mbar]
+            }
+        }
+    }
+    \endverbatim
+
+    \heading Output file locations
+
+    The \c rootdir normally corresponds to something like
+    \c postProcessing/\<name\>
+
+    \subheading Geometry
+    \verbatim
+    rootdir
+    `-- surfaceName0
+        `-- geometry
+            `-- surfaceName0_<time>.abq
+    \endverbatim
+
+    \subheading Fields
+    \verbatim
+    rootdir
+    `-- surfaceName0
+        `-- field0
+        |   `-- surfaceName0_<time>.{inp}
+        `-- field1
+            `-- surfaceName0_<time>.{inp}
+    \endverbatim
+
+Note
+    Output variable scaling does not apply to integer types such as Ids.
+
+SourceFiles
+    abaqusSurfaceWriter.C
+    abaqusSurfaceWriterImpl.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef abaqusSurfaceWriter_H
+#define abaqusSurfaceWriter_H
+
+#include "surfaceWriter.H"
+#include "ABAQUSCore.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace surfaceWriters
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class abaqusWriter Declaration
+\*---------------------------------------------------------------------------*/
+
+class abaqusWriter
+:
+    public surfaceWriter
+{
+    // Private Data
+
+        //- Output geometry scaling
+        const scalar geometryScale_;
+
+        //- Output field scaling
+        const dictionary fieldScale_;
+
+        //- BETA feature
+        bool noGeometry_;
+
+        //- Output directory layouts
+        enum class outputLayoutType
+        {
+            BY_TIME = 0,
+            BY_FIELD,
+        };
+
+        //- Output directory layout
+        outputLayoutType outputLayout_;
+
+
+    // Private Member Functions
+
+        //- Write face element (tri/quad)
+        void writeFace
+        (
+            Ostream& os,
+            const labelUList& f,
+            const label elemId,     //!< 1-based Element Id
+            const label propId,     //!< 1-based Property Id
+            const bool header = true
+        ) const;
+
+        //- Write the surface mesh geometry, tracking face decomposition
+        //
+        //  \param decompOffsets  begin/end offsets (size+1) into decompFaces
+        //  \param decompFaces    Non tri/quad decomposed into triangles
+        void writeGeometry
+        (
+            Ostream& os,
+            const meshedSurf& surf,
+            labelList& decompOffsets,
+            DynamicList<face>& decompFaces
+        ) const;
+
+        //- Write a face-based value
+        template<class Type>
+        Ostream& writeFaceValue
+        (
+            Ostream& os,
+            const Type& value,
+            const label elemId      //!< 0-based Element Id
+        ) const;
+
+        //- Templated write operation
+        template<class Type>
+        fileName writeTemplate
+        (
+            const word& fieldName,          //!< Name of field
+            const Field<Type>& localValues  //!< Local field values to write
+        );
+
+
+public:
+
+    //- Declare type-name, virtual type (with debug switch)
+    TypeNameNoDebug("abaqus");
+
+
+    // Constructors
+
+        //- Default construct
+        abaqusWriter();
+
+        //- Construct with some output options
+        explicit abaqusWriter(const dictionary& options);
+
+        //- Construct from components
+        abaqusWriter
+        (
+            const meshedSurf& surf,
+            const fileName& outputPath,
+            bool parallel = Pstream::parRun(),
+            const dictionary& options = dictionary()
+        );
+
+        //- Construct from components
+        abaqusWriter
+        (
+            const pointField& points,
+            const faceList& faces,
+            const fileName& outputPath,
+            bool parallel = Pstream::parRun(),
+            const dictionary& options = dictionary()
+        );
+
+
+    //- Destructor
+    virtual ~abaqusWriter() = default;
+
+
+    // Member Functions
+
+        //- Format uses faceIds as part of its output
+        virtual bool usesFaceIds() const // override
+        {
+            return true;
+        }
+
+        //- Write surface geometry to file.
+        virtual fileName write(); // override
+
+        declareSurfaceWriterWriteMethod(label);
+        declareSurfaceWriterWriteMethod(scalar);
+        declareSurfaceWriterWriteMethod(vector);
+        declareSurfaceWriterWriteMethod(sphericalTensor);
+        declareSurfaceWriterWriteMethod(symmTensor);
+        declareSurfaceWriterWriteMethod(tensor);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace surfaceWriters
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/surfMesh/writers/abaqus/abaqusSurfaceWriterImpl.C b/src/surfMesh/writers/abaqus/abaqusSurfaceWriterImpl.C
new file mode 100644
index 00000000000..b8d17ed3067
--- /dev/null
+++ b/src/surfMesh/writers/abaqus/abaqusSurfaceWriterImpl.C
@@ -0,0 +1,308 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "OFstream.H"
+#include "IOmanip.H"
+#include "OSspecific.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+Foam::Ostream& Foam::surfaceWriters::abaqusWriter::writeFaceValue
+(
+    Ostream& os,
+    const Type& value,
+    const label elemId
+) const
+{
+    if (fileFormats::ABAQUSCore::isEncodedSolidId(elemId))
+    {
+        const label solidId =
+            fileFormats::ABAQUSCore::decodeSolidElementId(elemId);
+
+        const label sideNum =
+            fileFormats::ABAQUSCore::decodeSolidSideNum(elemId);
+
+        // Element-id: 0-based to 1-based
+        // Side-id: always 1-based
+        os  << (solidId + 1) << ", P" << sideNum;
+    }
+    else
+    {
+        // Element-id: 0-based to 1-based
+        os  << (elemId + 1) << ", P";
+    }
+
+    os << ", ";
+    if (pTraits<Type>::nComponents == 1)
+    {
+        os << value;
+    }
+    else
+    {
+        os << mag(value);
+    }
+    os  << nl;
+
+    return os;
+}
+
+
+template<class Type>
+Foam::fileName Foam::surfaceWriters::abaqusWriter::writeTemplate
+(
+    const word& fieldName,
+    const Field<Type>& localValues
+)
+{
+    checkOpen();
+
+    // Field:
+    // 1) rootdir/<TIME>/<field>_surfaceName.inp
+    // 2) rootdir/<field>/surfaceName_<TIME>.inp
+
+    fileName outputFile;
+
+    switch (outputLayout_)
+    {
+        case outputLayoutType::BY_TIME:
+        {
+            outputFile = outputPath_;
+            if (useTimeDir() && !timeName().empty())
+            {
+                // Splice in time-directory
+                outputFile =
+                    outputPath_.path() / timeName() / outputPath_.name();
+            }
+
+            // Append <field>_surfaceName
+            outputFile /= fieldName + '_' + outputPath_.name();
+            break;
+        }
+        case outputLayoutType::BY_FIELD:
+        {
+            outputFile = outputPath_ / fieldName / outputPath_.name();
+            if (!timeName().empty())
+            {
+                // Append time information to file name
+                outputFile += '_' + timeName();
+            }
+            break;
+        }
+    }
+    outputFile.ext("inp");
+
+
+    // Output scaling for the variable, but not for integer types.
+    // could also solve with clever templating
+
+    const scalar varScale =
+    (
+        std::is_integral<Type>::value
+      ? scalar(1)
+      : fieldScale_.getOrDefault<scalar>(fieldName, 1)
+    );
+
+    if (verbose_)
+    {
+        Info<< "Writing field " << fieldName;
+        if (!equal(varScale, 1))
+        {
+            Info<< " (scaling " << varScale << ')';
+        }
+        Info<< " to " << outputFile << endl;
+    }
+
+
+    // Implicit geometry merge()
+    tmp<Field<Type>> tfield = mergeField(localValues) * varScale;
+
+    const meshedSurf& surf = surface();
+
+    if (Pstream::master() || !parallel_)
+    {
+        const auto& values = tfield();
+
+        if (!isDir(outputFile.path()))
+        {
+            mkDir(outputFile.path());
+        }
+
+        // const scalar timeValue(0);
+
+
+        // Additional bookkeeping for decomposing non tri/quad
+        labelList decompOffsets;
+        DynamicList<face> decompFaces;
+
+
+        OFstream os(outputFile);
+
+        if (noGeometry_ || wroteGeom_)
+        {
+            // Geometry already written (or suppressed)
+            // - still need decomposition information
+
+            fileFormats::ABAQUSCore::faceDecomposition
+            (
+                surf.points(),
+                surf.faces(),
+                decompOffsets,
+                decompFaces
+            );
+        }
+        else
+        {
+            // Write geometry (separate file)
+
+            OFstream osGeom(outputFile.lessExt().ext("abq"));
+            writeGeometry(osGeom, surf, decompOffsets, decompFaces);
+        }
+
+        // Write field
+        // *DLOAD
+        // Element-based: // elemId, P, 1000
+        // Surface-based: // elemId, P4, 1000
+
+        os  << "**" << nl
+            << "** field = " << fieldName << nl
+            << "** type = " << pTraits<Type>::typeName << nl;
+
+        if (useTimeDir() && !timeName().empty())
+        {
+            os  << "** time = " << timeName() << nl;
+        }
+
+        os  << "**" << nl
+            << "*DLOAD" << nl;
+
+
+        // Regular (undecomposed) faces
+        const faceList&    faces = surf.faces();
+        const labelList& elemIds = surf.faceIds();
+
+        // Possible to use faceIds?
+        const bool useOrigFaceIds =
+        (
+            elemIds.size() == faces.size()
+         && decompFaces.empty()
+        );
+
+
+        label elemId = 0;
+
+        if (this->isPointData())
+        {
+            forAll(faces, facei)
+            {
+                if (useOrigFaceIds)
+                {
+                    // When available and not decomposed
+                    elemId = elemIds[facei];
+                }
+
+                const label beginElemId = elemId;
+
+                // Any face decomposition
+                for
+                (
+                    label decompi = decompOffsets[facei];
+                    decompi < decompOffsets[facei+1];
+                    ++decompi
+                )
+                {
+                    const face& f = decompFaces[decompi];
+
+                    Type v = Zero;
+                    for (const label verti : f)
+                    {
+                        v += values[verti];
+                    }
+                    v /= f.size();
+
+                    writeFaceValue(os, v, elemId);  // 0-based
+                    ++elemId;
+                }
+
+
+                // Face not decomposed
+                if (beginElemId == elemId)
+                {
+                    const face& f = faces[facei];
+
+                    Type v = Zero;
+                    for (const label verti : f)
+                    {
+                        v += values[verti];
+                    }
+                    v /= f.size();
+
+                    writeFaceValue(os, v, elemId);  // 0-based
+                    ++elemId;
+                }
+            }
+        }
+        else
+        {
+            auto valIter = values.cbegin();
+
+            forAll(faces, facei)
+            {
+                if (useOrigFaceIds)
+                {
+                    // When available and not decomposed
+                    elemId = elemIds[facei];
+                }
+
+                const Type v(*valIter);
+                ++valIter;
+
+                label nValues =
+                    max
+                    (
+                        label(1),
+                        (decompOffsets[facei+1] - decompOffsets[facei])
+                    );
+
+                while (nValues--)
+                {
+                    writeFaceValue(os, v, elemId);  // 0-based
+                    ++elemId;
+                }
+            }
+        }
+
+        os  << "**" << nl
+            << "**" << nl;
+    }
+
+    wroteGeom_ = true;
+    return outputFile;
+}
+
+
+// ************************************************************************* //
-- 
GitLab