From b476dd92e6e14f40a6e5ff507b1dece09b8c36f0 Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Thu, 27 Feb 2020 13:16:28 +0100
Subject: [PATCH] ENH: improvements for nastran surface writer (#1571)

- avoid face copying.
  Maintain separate offsets/list for non tri/quad face decomposition,
  which eliminates copying for tri/quad types that represent the bulk
  of geometries

- report inappropriate use of PLOAD2 for higher-ranks only once per
  field instead of per face.  For this case, write its magnitude
  instead of 0.

- perform field output scaling prior to calling the write face
  function. This will make it easier to handle different per-field
  scaling in the future (#1612)

BUG: nastran quad written as "CTRIA3" instead of "CQUAD4"
---
 src/fileFormats/nastran/NASCore.C             |  36 +++++-
 src/fileFormats/nastran/NASCore.H             |  26 +++-
 .../surfaceFormats/nas/NASsurfaceFormat.C     |   2 +-
 .../writers/nastran/nastranSurfaceWriter.C    |  62 +++++----
 .../writers/nastran/nastranSurfaceWriter.H    |   8 +-
 .../nastran/nastranSurfaceWriterImpl.C        | 121 +++++++++++++-----
 6 files changed, 191 insertions(+), 64 deletions(-)

diff --git a/src/fileFormats/nastran/NASCore.C b/src/fileFormats/nastran/NASCore.C
index 184eb655650..ab4de5915d7 100644
--- a/src/fileFormats/nastran/NASCore.C
+++ b/src/fileFormats/nastran/NASCore.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -197,4 +197,38 @@ Foam::Ostream& Foam::fileFormats::NASCore::writeKeyword
 }
 
 
+Foam::label Foam::fileFormats::NASCore::faceDecomposition
+(
+    const UList<point>& points,
+    const UList<face>& faces,
+    labelList& decompOffsets,
+    DynamicList<face>& decompFaces
+)
+{
+    // On-demand face decomposition (triangulation)
+
+    decompOffsets.resize(faces.size()+1);
+    decompFaces.clear();
+
+    auto offsetIter = decompOffsets.begin();
+    *offsetIter = 0; // The first offset is always zero
+
+    for (const face& f : faces)
+    {
+        const label n = f.size();
+
+        if (n != 3 && n != 4)
+        {
+            // Decompose non-tri/quad into tris
+            f.triangles(points, decompFaces);
+        }
+
+        // The end offset, which is the next begin offset
+        *(++offsetIter) = decompFaces.size();
+    }
+
+    return decompFaces.size();
+}
+
+
 // ************************************************************************* //
diff --git a/src/fileFormats/nastran/NASCore.H b/src/fileFormats/nastran/NASCore.H
index 24a5afcedef..d059a754b5d 100644
--- a/src/fileFormats/nastran/NASCore.H
+++ b/src/fileFormats/nastran/NASCore.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -40,15 +40,15 @@ SourceFiles
 #include "scalar.H"
 #include "string.H"
 #include "Enum.H"
+#include "face.H"
+#include "point.H"
+#include "DynamicList.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-// Forward Declarations
-class Ostream;
-
 namespace fileFormats
 {
 
@@ -84,7 +84,7 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         NASCore() = default;
 
 
@@ -120,6 +120,22 @@ public:
             const word& keyword,
             const fieldFormat format
         );
+
+        //- Calculate face decomposition for non tri/quad faces
+        //
+        //  \param points the surface points
+        //  \param faces  the surface faces
+        //  \param decompOffsets begin/end offsets (size+1) into decompFaces
+        //  \param decompFaces  List of non-tri/quad decomposed into triangles
+        //
+        //  \return number of decomposed faces
+        static label faceDecomposition
+        (
+            const UList<point>& points,
+            const UList<face>& faces,
+            labelList& decompOffsets,
+            DynamicList<face>& decompFaces
+        );
 };
 
 
diff --git a/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C b/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
index da3e45694ac..5e0f2b16662 100644
--- a/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
+++ b/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
@@ -55,7 +55,7 @@ inline Foam::label Foam::fileFormats::NASsurfaceFormat<Face>::writeShell
     }
     else if (n == 4)
     {
-        os  << "CTRIA3" << ','
+        os  << "CQUAD4" << ','
             << ++elementId << ','
             << (groupId + 1) << ','
             << (f[0] + 1) << ','
diff --git a/src/surfMesh/writers/nastran/nastranSurfaceWriter.C b/src/surfMesh/writers/nastran/nastranSurfaceWriter.C
index 69d4ae84d44..9592e5bba92 100644
--- a/src/surfMesh/writers/nastran/nastranSurfaceWriter.C
+++ b/src/surfMesh/writers/nastran/nastranSurfaceWriter.C
@@ -204,7 +204,8 @@ void Foam::surfaceWriters::nastranWriter::writeGeometry
 (
     Ostream& os,
     const meshedSurf& surf,
-    List<faceList>& decomposedFaces
+    labelList& decompOffsets,
+    DynamicList<face>& decompFaces
 ) const
 {
     const pointField& points = surf.points();
@@ -213,9 +214,9 @@ void Foam::surfaceWriters::nastranWriter::writeGeometry
 
     // Write points
 
-    os  << "$" << nl
+    os  << '$' << nl
         << "$ Points" << nl
-        << "$" << nl;
+        << '$' << nl;
 
     forAll(points, pointi)
     {
@@ -223,18 +224,19 @@ void Foam::surfaceWriters::nastranWriter::writeGeometry
     }
 
     // Write faces, with on-the-fly decomposition (triangulation)
-    decomposedFaces.clear();
-    decomposedFaces.resize(faces.size());
+    decompOffsets.resize(faces.size()+1);
+    decompFaces.clear();
 
-    os  << "$" << nl
+    decompOffsets[0] = 0; // The first offset is always zero
+
+    os  << '$' << nl
         << "$ Faces" << nl
-        << "$" << nl;
+        << '$' << nl;
 
-    label elemId = 0; // The element-id
+    label elemId = 0;  // The element-id
     forAll(faces, facei)
     {
         const face& f = faces[facei];
-        faceList& decomp = decomposedFaces[facei];
 
         // 1-offset for PID
         const label propId = 1 + (facei < zones.size() ? zones[facei] : 0);
@@ -242,28 +244,36 @@ void Foam::surfaceWriters::nastranWriter::writeGeometry
         if (f.size() == 3)
         {
             writeFace(os, "CTRIA3", f, ++elemId, propId);
-            decomp.resize(1);
-            decomp[0] = f;
         }
         else if (f.size() == 4)
         {
             writeFace(os, "CQUAD4", f, ++elemId, propId);
-            decomp.resize(1);
-            decomp[0] = f;
         }
         else
         {
-            // Decompose poly face into tris
-            decomp.resize(f.nTriangles());
-
-            label nTri = 0;
-            f.triangles(points, nTri, decomp);
-
-            for (const face& f2 : decomp)
+            // Decompose into tris
+            f.triangles(points, decompFaces);
+
+            for
+            (
+                label decompi = decompOffsets[facei];
+                decompi < decompFaces.size();
+                ++decompi
+            )
             {
-                writeFace(os, "CTRIA3", f2, ++elemId, propId);
+                writeFace
+                (
+                    os,
+                    "CTRIA3",
+                    decompFaces[decompi],
+                    ++elemId,
+                    propId
+                );
             }
         }
+
+        // The end offset, which is the next begin offset
+        decompOffsets[facei+1] = decompFaces.size();
     }
 }
 
@@ -323,7 +333,7 @@ Foam::surfaceWriters::nastranWriter::nastranWriter()
     surfaceWriter(),
     writeFormat_(fieldFormat::SHORT),
     fieldMap_(),
-    scale_(1.0),
+    scale_(1),
     separator_()
 {}
 
@@ -432,11 +442,13 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::write()
 
         os  << "TITLE=OpenFOAM " << outputPath_.name()
             << " mesh" << nl
-            << "$" << nl
+            << '$' << nl
             << "BEGIN BULK" << nl;
 
-        List<faceList> decomposedFaces;
-        writeGeometry(os, surf, decomposedFaces);
+        labelList decompOffsets;
+        DynamicList<face> decompFaces;
+
+        writeGeometry(os, surf, decompOffsets, decompFaces);
 
         writeFooter(os, surf)
             << "ENDDATA" << nl;
diff --git a/src/surfMesh/writers/nastran/nastranSurfaceWriter.H b/src/surfMesh/writers/nastran/nastranSurfaceWriter.H
index b27831174f4..e2ddba4ac74 100644
--- a/src/surfMesh/writers/nastran/nastranSurfaceWriter.H
+++ b/src/surfMesh/writers/nastran/nastranSurfaceWriter.H
@@ -155,12 +155,16 @@ private:
             const label propId      //!< 1-based Property Id
         ) const;
 
-        //- Main driver to write the surface mesh geometry
+        //- 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,
-            List<faceList>& decomposedFaces
+            labelList& decompOffsets,
+            DynamicList<face>& decompFaces
         ) const;
 
         //- Write the formatted keyword to the output stream
diff --git a/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C b/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C
index 3b29d8a2c22..737240262c9 100644
--- a/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C
+++ b/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C
@@ -89,8 +89,6 @@ Foam::Ostream& Foam::surfaceWriters::nastranWriter::writeFaceValue
 
     const label setId = 1;
 
-    Type scaledValue = scale_*value;
-
     // Write keyword
     writeKeyword(os, fileFormats::NASCore::loadFormatNames[format])
         << separator_;
@@ -106,19 +104,14 @@ Foam::Ostream& Foam::surfaceWriters::nastranWriter::writeFaceValue
         {
             if (pTraits<Type>::nComponents == 1)
             {
-                writeValue(os, scaledValue) << separator_;
+                writeValue(os, value);
             }
             else
             {
-                WarningInFunction
-                    << fileFormats::NASCore::loadFormatNames[format]
-                    << " requires scalar values"
-                    << " - it cannot be used for higher rank values"
-                    << endl;
-
-                writeValue(os, scalar(0)) << separator_;
+                writeValue(os, mag(value));
             }
 
+            os << separator_;
             writeValue(os, elemId);
             break;
         }
@@ -130,7 +123,7 @@ Foam::Ostream& Foam::surfaceWriters::nastranWriter::writeFaceValue
             for (direction d = 0; d < pTraits<Type>::nComponents; ++d)
             {
                 os  << separator_;
-                writeValue(os, component(scaledValue, d));
+                writeValue(os, component(value, d));
             }
             break;
         }
@@ -164,7 +157,7 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::writeTemplate
         return fileName::null;
     }
 
-    const loadFormat& format(fieldMap_[fieldName]);
+    const loadFormat format(fieldMap_[fieldName]);
 
     // Field:  rootdir/<TIME>/field/surfaceName.nas
 
@@ -177,9 +170,27 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::writeTemplate
     outputFile /= fieldName / outputPath_.name();
     outputFile.ext("nas");
 
+
+    // Currently the same scaling for all variables
+    const scalar varScale = scale_;
+
     if (verbose_)
     {
-        Info<< "Writing field " << fieldName << " to " << outputFile << endl;
+        Info<< "Writing field " << fieldName;
+        if (!equal(varScale, 1))
+        {
+            Info<< " (scaling " << varScale << ')';
+        }
+        Info<< " to " << outputFile << endl;
+    }
+
+    // Emit any common warnings
+    if (format == loadFormat::PLOAD2 && pTraits<Type>::nComponents != 1)
+    {
+        WarningInFunction
+            << fileFormats::NASCore::loadFormatNames[format]
+            << " cannot be used for higher rank values"
+            << " - reverting to mag()" << endl;
     }
 
 
@@ -199,43 +210,84 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::writeTemplate
 
         const scalar timeValue(0);
 
+        // Additional bookkeeping for decomposing non tri/quad
+        labelList decompOffsets;
+        DynamicList<face> decompFaces;
+
+
+        // Could handle separate geometry here
+
         OFstream os(outputFile);
         fileFormats::NASCore::setPrecision(os, writeFormat_);
 
-        if (verbose_)
+        os  << "TITLE=OpenFOAM " << outputFile.name()
+            << token::SPACE << fieldName << " data" << nl;
+
+        if (useTimeDir() && !timeName().empty())
         {
-            Info<< "Writing nastran file to " << os.name() << endl;
+            os  << '$' << nl
+                << "$ TIME " << timeName() << nl;
         }
 
-        os  << "TITLE=OpenFOAM " << outputFile.name()
-            << " " << fieldName << " data" << nl
-            << "$" << nl
+        os  << '$' << nl
             << "TIME " << timeValue << nl
-            << "$" << nl
+            << '$' << nl
             << "BEGIN BULK" << nl;
 
-        List<faceList> decomposedFaces;
-        writeGeometry(os, surf, decomposedFaces);
 
-        os  << "$" << nl
+        writeGeometry(os, surf, decompOffsets, decompFaces);
+
+
+        // Write field
+
+        os  << '$' << nl
             << "$ Field data" << nl
-            << "$" << nl;
+            << '$' << nl;
+
+
+        // Regular (undecomposed) faces
+        const faceList& faces = surf.faces();
 
         label elemId = 0;
 
         if (this->isPointData())
         {
-            for (const faceList& dFaces : decomposedFaces)
+            forAll(faces, facei)
             {
-                for (const face& f : dFaces)
+                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 *= (varScale / f.size());
+
+                    writeFaceValue(os, format, v, ++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();
+                    v *= (varScale / f.size());
 
                     writeFaceValue(os, format, v, ++elemId);
                 }
@@ -245,13 +297,22 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::writeTemplate
         {
             auto valIter = values.cbegin();
 
-            for (const faceList& dFaces : decomposedFaces)
+            forAll(faces, facei)
             {
-                forAll(dFaces, facei)
+                const Type v(varScale * *valIter);
+                ++valIter;
+
+                label nValues =
+                    max
+                    (
+                        label(1),
+                        (decompOffsets[facei+1] - decompOffsets[facei])
+                    );
+
+                while (nValues--)
                 {
-                    writeFaceValue(os, format, *valIter, ++elemId);
+                    writeFaceValue(os, format, v, ++elemId);
                 }
-                ++valIter;
             }
         }
 
-- 
GitLab