From 008a4333301fc1000d0424446bdbd204de17ba3b Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Wed, 26 Feb 2020 00:12:14 +0100
Subject: [PATCH] ENH: keep element ids when reading/writing meshed surfaces
 (#1600)

---
 .../sampledMeshedSurface.C                    | 20 ++------
 .../sampledMeshedSurface.H                    | 21 +++-----
 .../sampledSurface/sampledSurface.H           |  7 ---
 .../sampledSurfaces/sampledSurfaces.C         |  7 ++-
 .../sampledSurfaces/sampledSurfaces.H         |  4 +-
 src/surfMesh/MeshedSurface/MeshedSurface.C    | 50 ++++++++++++++++++-
 src/surfMesh/MeshedSurface/MeshedSurface.H    | 20 ++++++++
 .../MeshedSurface/MeshedSurfaceCore.C         | 12 +++--
 .../MeshedSurface/MeshedSurfaceZones.C        | 16 ++++++
 .../MeshedSurfaceProxy/MeshedSurfaceProxy.C   |  6 ++-
 .../MeshedSurfaceProxy/MeshedSurfaceProxy.H   | 23 ++++++++-
 src/surfMesh/mergedSurf/mergedSurf.C          |  7 +++
 src/surfMesh/mergedSurf/mergedSurf.H          |  8 +++
 src/surfMesh/meshedSurf/meshedSurf.H          |  6 +++
 src/surfMesh/meshedSurf/meshedSurfRef.H       | 21 ++++++--
 .../surfaceFormats/nas/NASsurfaceFormat.C     | 48 ++++++++++++++++--
 .../surfaceFormats/obj/OBJsurfaceFormat.C     |  4 +-
 .../starcd/STARCDsurfaceFormat.C              | 38 +++++++++++++-
 .../surfaceFormats/vtk/VTKsurfaceFormat.C     |  6 ++-
 .../writers/nastran/nastranSurfaceWriter.C    | 24 +++++++++
 .../nastran/nastranSurfaceWriterImpl.C        | 22 ++++++++
 .../writers/starcd/starcdSurfaceWriter.C      |  9 +++-
 22 files changed, 318 insertions(+), 61 deletions(-)

diff --git a/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.C b/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.C
index 3198116b9f0..2373d74acb8 100644
--- a/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.C
+++ b/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.C
@@ -118,7 +118,7 @@ inline static IOobject selectReadIO(const word& name, const Time& runTime)
 
 void Foam::sampledMeshedSurface::setZoneMap()
 {
-    // Ensure zoneIds_ are correctly populated
+    // Populate zoneIds_ based on surfZone information
 
     const meshedSurface& s = static_cast<const meshedSurface&>(*this);
 
@@ -279,19 +279,10 @@ bool Foam::sampledMeshedSurface::update(const meshSearch& meshSearcher)
 
     s = surface_.subsetMesh(facesToSubset, pointMap, faceMap);
 
+
     // Ensure zoneIds_ are indeed correct
     setZoneMap();
 
-    // This is currently only partially useful
-    if (keepIds_)
-    {
-        originalIds_ = faceMap;
-    }
-    else
-    {
-        originalIds_.clear();
-    }
-
 
     // Subset cellOrFaceLabels (for compact faces)
     cellOrFaceLabels = labelUIndList(cellOrFaceLabels, faceMap)();
@@ -492,8 +483,7 @@ Foam::sampledMeshedSurface::sampledMeshedSurface
     ),
     sampleSource_(sampleSource),
     needsUpdate_(true),
-    keepIds_(false),
-    originalIds_(),
+    keepIds_(true),
     zoneIds_(),
     sampleElements_(),
     samplePoints_()
@@ -524,8 +514,7 @@ Foam::sampledMeshedSurface::sampledMeshedSurface
     ),
     sampleSource_(samplingSourceNames_.get("source", dict)),
     needsUpdate_(true),
-    keepIds_(dict.lookupOrDefault("keepIds", false)),
-    originalIds_(),
+    keepIds_(dict.lookupOrDefault("keepIds", true)),
     zoneIds_(),
     sampleElements_(),
     samplePoints_()
@@ -610,7 +599,6 @@ bool Foam::sampledMeshedSurface::expire()
     MeshStorage::clear();
     zoneIds_.clear();
 
-    originalIds_.clear();
     sampleElements_.clear();
     samplePoints_.clear();
 
diff --git a/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.H b/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.H
index 05da864184a..bec78a6796b 100644
--- a/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.H
+++ b/src/sampling/sampledSurface/sampledMeshedSurface/sampledMeshedSurface.H
@@ -86,7 +86,7 @@ Usage
         surface  | surface name in triSurface/             | yes      |
         patches  | Limit to named surface regions (wordRes) | no  |
         source   | cells/insideCells/boundaryFaces         | yes      |
-        keepIds  | pass through id numbering               | no       | false
+        keepIds  | pass through id numbering               | no       | true
     \endtable
 
 SourceFiles
@@ -153,10 +153,6 @@ private:
         //- Retain element ids/order of original surface
         bool keepIds_;
 
-        //- List of element ids/order of the original surface,
-        //- when keepIds is active.
-        labelList originalIds_;
-
         //- For compatibility with the meshSurf interface
         labelList zoneIds_;
 
@@ -255,6 +251,12 @@ public:
             return zoneIds_;
         }
 
+        //- Per-face identifier (eg, element Id)
+        virtual const labelList& faceIds() const
+        {
+            return MeshStorage::faceIds();
+        }
+
         //- Face area vectors
         virtual const vectorField& Sf() const
         {
@@ -276,14 +278,7 @@ public:
         //- If element ids/order of the original surface are kept
         virtual bool hasFaceIds() const
         {
-            return keepIds_;
-        }
-
-        //- List of element ids/order of the original surface,
-        //  when keepIds is active.
-        virtual const labelList& originalIds() const
-        {
-            return originalIds_;
+            return keepIds_ && !MeshStorage::faceIds().empty();
         }
 
         //- Sampling boundary values instead of cell values
diff --git a/src/sampling/sampledSurface/sampledSurface/sampledSurface.H b/src/sampling/sampledSurface/sampledSurface/sampledSurface.H
index 6dc8d28e69a..b21df8349e1 100644
--- a/src/sampling/sampledSurface/sampledSurface/sampledSurface.H
+++ b/src/sampling/sampledSurface/sampledSurface/sampledSurface.H
@@ -340,13 +340,6 @@ public:
             return false;
         }
 
-        //- List of element ids/order of the original surface,
-        //- when hasFaceIds is true.
-        virtual const labelList& originalIds() const
-        {
-            return labelList::null();
-        }
-
 
     // General registry storage (optional)
 
diff --git a/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.C b/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.C
index 0f133b402c0..a87a22d4801 100644
--- a/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.C
+++ b/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.C
@@ -558,10 +558,15 @@ bool Foam::sampledSurfaces::performAction(unsigned request)
             // Write original ids
             if (s.hasFaceIds() && !s.interpolate())
             {
+                // This is still quite horrible.
+
+                Field<label> ids(s.faceIds());
+                ids += label(1);  // From 0-based to 1-based
+
                 writeSurface
                 (
                     outWriter,
-                    Field<label>(s.originalIds()),
+                    ids,
                     "Ids"
                 );
             }
diff --git a/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.H b/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.H
index a5dd2fe7a5f..641a7bf2bc3 100644
--- a/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.H
+++ b/src/sampling/sampledSurface/sampledSurfaces/sampledSurfaces.H
@@ -69,7 +69,7 @@ Description
         }
 
         surfaces
-        (
+        {
             f0surf
             {
                 type        meshedSurface;
@@ -88,7 +88,7 @@ Description
                 // Optional: registry storage
                 store       true
             }
-        );
+        }
     }
     \endverbatim
 
diff --git a/src/surfMesh/MeshedSurface/MeshedSurface.C b/src/surfMesh/MeshedSurface/MeshedSurface.C
index 48330fb0a1b..27c762a1e15 100644
--- a/src/surfMesh/MeshedSurface/MeshedSurface.C
+++ b/src/surfMesh/MeshedSurface/MeshedSurface.C
@@ -167,6 +167,7 @@ template<class Face>
 Foam::MeshedSurface<Face>::MeshedSurface()
 :
     ParentType(List<Face>(), pointField()),
+    faceIds_(),
     zones_()
 {}
 
@@ -178,6 +179,7 @@ Foam::MeshedSurface<Face>::MeshedSurface
 )
 :
     ParentType(surf.surfFaces(), surf.points()),
+    faceIds_(surf.faceIds_),
     zones_(surf.zones_)
 {}
 
@@ -238,6 +240,7 @@ Foam::MeshedSurface<Face>::MeshedSurface
 )
 :
     ParentType(faceLst, pointLst), // Copy construct
+    faceIds_(),
     zones_(zoneLst)
 {}
 
@@ -251,6 +254,7 @@ Foam::MeshedSurface<Face>::MeshedSurface
 )
 :
     ParentType(faceLst, pointLst, true), // Move construct
+    faceIds_(),
     zones_(zoneLst)
 {}
 
@@ -265,6 +269,7 @@ Foam::MeshedSurface<Face>::MeshedSurface
 )
 :
     ParentType(faceLst, pointLst), // Copy construct
+    faceIds_(),
     zones_()
 {
     if (zoneSizes.size())
@@ -291,6 +296,7 @@ Foam::MeshedSurface<Face>::MeshedSurface
 )
 :
     ParentType(faceLst, pointLst, true), // Move construct
+    faceIds_(),
     zones_()
 {
     if (zoneSizes.size())
@@ -559,6 +565,7 @@ void Foam::MeshedSurface<Face>::clear()
 
     storedPoints().clear();
     storedFaces().clear();
+    storedFaceIds().clear();
     storedZones().clear();
 }
 
@@ -678,6 +685,17 @@ bool Foam::MeshedSurface<Face>::stitchFaces
         faceMap.resize(newFacei);
         faceLst.resize(newFacei);
 
+        // The faceMap is a newToOld mapping and only removes elements
+        if (faceIds_.size())
+        {
+            forAll(faceMap, facei)
+            {
+                faceIds_[facei] = faceIds_[faceMap[facei]];
+            }
+
+            faceIds_.resize(newFacei);
+        }
+
         remapFaces(faceMap);
     }
     faceMap.clear();
@@ -837,6 +855,17 @@ bool Foam::MeshedSurface<Face>::checkFaces
         faceMap.resize(newFacei);
         faceLst.resize(newFacei);
 
+        // The faceMap is a newToOld mapping and only removes elements
+        if (faceIds_.size())
+        {
+            forAll(faceMap, facei)
+            {
+                faceIds_[facei] = faceIds_[faceMap[facei]];
+            }
+
+            faceIds_.resize(newFacei);
+        }
+
         remapFaces(faceMap);
     }
     faceMap.clear();
@@ -967,6 +996,8 @@ Foam::label Foam::MeshedSurface<Face>::triangulate
         return 0;
     }
 
+    this->storedFaceIds().clear();  // Invalid or misleading
+
     List<Face>  newFaces(nTri);
     List<label> faceMap;
 
@@ -1106,11 +1137,20 @@ Foam::MeshedSurface<Face>::subsetMeshImpl
         zone.size() = newFacei - zone.start();
     }
 
+
+    // Subset of faceIds. Can be empty.
+    labelList newFaceIds;
+    if (faceIds_.size())
+    {
+        newFaceIds = labelUIndList(faceIds_, faceMap);
+    }
+
     // Construct the sub-surface
     MeshedSurface<Face> newSurf;
     newSurf.storedFaces().transfer(newFaces);
     newSurf.storedPoints().transfer(newPoints);
     newSurf.storedZones().transfer(newZones);
+    newSurf.storedFaceIds().transfer(newFaceIds);
 
     return newSurf;
 }
@@ -1212,6 +1252,7 @@ void Foam::MeshedSurface<Face>::swap
     this->storedPoints().swap(surf.storedPoints());
     this->storedFaces().swap(surf.storedFaces());
     this->storedZones().swap(surf.storedZones());
+    this->storedFaceIds().swap(surf.storedFaceIds());
 }
 
 
@@ -1227,6 +1268,7 @@ void Foam::MeshedSurface<Face>::transfer
     this->storedPoints().transfer(pointLst);
     this->storedFaces().transfer(faceLst);
     this->storedZones().clear();
+    this->storedFaceIds().clear();  // Likely to be invalid
 }
 
 
@@ -1246,6 +1288,7 @@ void Foam::MeshedSurface<Face>::transfer
     this->storedPoints().transfer(surf.storedPoints());
     this->storedFaces().transfer(surf.storedFaces());
     this->storedZones().transfer(surf.storedZones());
+    this->storedFaceIds().transfer(surf.storedFaceIds());
 
     surf.clear();
 }
@@ -1304,6 +1347,8 @@ void Foam::MeshedSurface<Face>::swapFaces(List<Face>& faces)
 {
     ParentType::clearOut();  // Topology changes
 
+    this->storedFaceIds().clear();  // Likely to be invalid
+
     this->storedFaces().swap(faces);
 }
 
@@ -1373,6 +1418,7 @@ void Foam::MeshedSurface<Face>::operator=(const MeshedSurface<Face>& surf)
 
     this->storedPoints() = surf.points();
     this->storedFaces()  = surf.surfFaces();
+    this->storedFaceIds() = surf.faceIds();
     this->storedZones()  = surf.surfZones();
 }
 
@@ -1391,7 +1437,9 @@ Foam::MeshedSurface<Face>::operator Foam::MeshedSurfaceProxy<Face>() const
     (
         this->points(),
         this->surfFaces(),
-        this->surfZones()
+        this->surfZones(),
+        labelUList::null(), // faceMap = none
+        this->faceIds()
     );
 }
 
diff --git a/src/surfMesh/MeshedSurface/MeshedSurface.H b/src/surfMesh/MeshedSurface/MeshedSurface.H
index f6b60673831..d132ae34571 100644
--- a/src/surfMesh/MeshedSurface/MeshedSurface.H
+++ b/src/surfMesh/MeshedSurface/MeshedSurface.H
@@ -120,6 +120,11 @@ private:
 
     // Private Data
 
+        //- Face ids.
+        //  If these exist, they are typically arise from reading a mesh
+        //  format from another CAE software (eg, NASTRAN, STARCD, ...)
+        labelList faceIds_;
+
         //- Zone information
         // (face ordering nFaces/startFace only used during reading/writing)
         surfZoneList zones_;
@@ -169,6 +174,12 @@ protected:
             return static_cast<List<Face>&>(*this);
         }
 
+        //- Non-const access to face ids
+        labelList& storedFaceIds()
+        {
+            return faceIds_;
+        }
+
         //- Non-const access to the zones
         surfZoneList& storedZones()
         {
@@ -180,6 +191,7 @@ protected:
         (
             DynamicList<Face>& unsortedFaces,
             DynamicList<label>& zoneIds,
+            DynamicList<label>& elemIds,
             bool sorted
         );
 
@@ -391,6 +403,14 @@ public:
             return static_cast<const List<Face>&>(*this);
         }
 
+        //- Return const access to faces ids
+        //  If these exist, they are typically arise from reading a mesh
+        //  format from another CAE software (eg, NASTRAN, STARCD, ...)
+        const labelList& faceIds() const
+        {
+            return faceIds_;
+        }
+
         //- Const access to the surface zones.
         //  If zones are defined, they must be contiguous and cover the
         //  entire surface
diff --git a/src/surfMesh/MeshedSurface/MeshedSurfaceCore.C b/src/surfMesh/MeshedSurface/MeshedSurfaceCore.C
index 20961c4f9af..b90331b4a2f 100644
--- a/src/surfMesh/MeshedSurface/MeshedSurfaceCore.C
+++ b/src/surfMesh/MeshedSurface/MeshedSurfaceCore.C
@@ -52,9 +52,11 @@ namespace Foam
         MeshedSurface<face>& surf
     )
     {
-        // First triangulate
-        // - slightly wasteful for space, but adjusts the zones too!
+        // First triangulate.
+        // Potentially wasteful of space, but adjusts zones and
+        // invalidates the faceIds
         surf.triangulate();
+
         this->storedPoints().transfer(surf.storedPoints());
         this->storedZones().transfer(surf.storedZones());
 
@@ -83,9 +85,11 @@ namespace Foam
         MeshedSurface<face>& surf
     )
     {
-        // First triangulate
-        // - slightly wasteful for space, but adjusts the zones too!
+        // First triangulate.
+        // Potentially wasteful of space, but adjusts zones and
+        // invalidates the faceIds
         surf.triangulate();
+
         this->storedPoints().transfer(surf.storedPoints());
         this->storedZones().transfer(surf.storedZones());
 
diff --git a/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C b/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C
index 223c28afcd6..051a413cbec 100644
--- a/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C
+++ b/src/surfMesh/MeshedSurface/MeshedSurfaceZones.C
@@ -71,6 +71,7 @@ void Foam::MeshedSurface<Face>::sortFacesAndStore
 (
     DynamicList<Face>& unsortedFaces,
     DynamicList<label>& zoneIds,
+    DynamicList<label>& elemIds,
     bool sorted
 )
 {
@@ -84,10 +85,16 @@ void Foam::MeshedSurface<Face>::sortFacesAndStore
         sorted = true;
     }
 
+    if (elemIds.size() != nInputFaces)
+    {
+        elemIds.clear();
+    }
+
     if (sorted)
     {
         // No additional sorting required
         this->storedFaces().transfer(unsortedFaces);
+        this->storedFaceIds().transfer(elemIds);
         return;
     }
 
@@ -106,6 +113,15 @@ void Foam::MeshedSurface<Face>::sortFacesAndStore
         // Can use transfer, faceMap is unique
         newFaces[facei].transfer(unsortedFaces[faceMap[facei]]);
     }
+
+    auto& newFaceIds = this->storedFaceIds();
+    newFaceIds.resize(elemIds.size());
+
+    // Element ids in sorted order
+    forAll(newFaceIds, facei)
+    {
+        newFaceIds[facei] = elemIds[faceMap[facei]];
+    }
 }
 
 
diff --git a/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.C b/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.C
index c2058125636..bec7309364c 100644
--- a/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.C
+++ b/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.C
@@ -223,13 +223,15 @@ Foam::MeshedSurfaceProxy<Face>::MeshedSurfaceProxy
     const pointField& pointLst,
     const UList<Face>& faceLst,
     const UList<surfZone>& zoneLst,
-    const labelUList& faceMap
+    const labelUList& faceMap,
+    const labelUList& faceIdsLst
 )
 :
     points_(pointLst),
     faces_(faceLst),
     zones_(zoneLst),
-    faceMap_(faceMap)
+    faceMap_(faceMap),
+    faceIds_(faceIdsLst)
 {}
 
 
diff --git a/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.H b/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.H
index 12e4891b180..7aeda7f0fca 100644
--- a/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.H
+++ b/src/surfMesh/MeshedSurfaceProxy/MeshedSurfaceProxy.H
@@ -31,6 +31,9 @@ Description
     A proxy for writing MeshedSurface, UnsortedMeshedSurface and surfMesh
     to various file formats.
 
+    The constructor interface is fat and ugly, but is largely encapsulated
+    by conversion operators in other classes.
+
 SourceFiles
     MeshedSurfaceProxy.C
     MeshedSurfaceProxys.C
@@ -73,6 +76,9 @@ class MeshedSurfaceProxy
 
         const UList<label>& faceMap_;
 
+        const UList<label>& faceIds_;
+
+
 public:
 
     // Public Typedefs
@@ -101,8 +107,9 @@ public:
         (
             const pointField& pointLst,
             const UList<Face>& faceLst,
-            const UList<surfZone>& zoneLst = List<surfZone>(),
-            const labelUList& faceMap = labelUList::null()
+            const UList<surfZone>& zoneLst = UList<surfZone>::null(),
+            const labelUList& faceMap = labelUList::null(),
+            const labelUList& faceIdLst = labelUList::null()
         );
 
 
@@ -183,12 +190,24 @@ public:
                 return faceMap_;
             }
 
+            //- Const access to the faceIds, zero-sized when unused
+            const labelUList& faceIds() const
+            {
+                return faceIds_;
+            }
+
             //- Can/should use faceMap?
             bool useFaceMap() const
             {
                 return faceMap_.size() == faces_.size();
             }
 
+            //- Possible to use faceIds?
+            bool useFaceIds() const
+            {
+                return faceIds_.size() == faces_.size();
+            }
+
             //- Count number of triangles.
             inline label nTriangles() const;
 
diff --git a/src/surfMesh/mergedSurf/mergedSurf.C b/src/surfMesh/mergedSurf/mergedSurf.C
index fc0f2ed500c..2db9ada9b22 100644
--- a/src/surfMesh/mergedSurf/mergedSurf.C
+++ b/src/surfMesh/mergedSurf/mergedSurf.C
@@ -61,6 +61,7 @@ Foam::mergedSurf::mergedSurf
     const pointField& unmergedPoints,
     const faceList& unmergedFaces,
     const labelList& origZoneIds,
+    const labelList& origFaceIds,
     const scalar mergeDim
 )
 :
@@ -71,6 +72,7 @@ Foam::mergedSurf::mergedSurf
         unmergedPoints,
         unmergedFaces,
         origZoneIds,
+        origFaceIds,
         mergeDim
     );
 }
@@ -91,6 +93,7 @@ void Foam::mergedSurf::clear()
     pointsMap_.clear();
 
     zoneIds_.clear();
+    faceIds_.clear();
 }
 
 
@@ -106,6 +109,7 @@ bool Foam::mergedSurf::merge
             unmergedSurface.points(),
             unmergedSurface.faces(),
             unmergedSurface.zoneIds(),
+            unmergedSurface.faceIds(),
             mergeDim
         );
 }
@@ -124,6 +128,7 @@ bool Foam::mergedSurf::merge
             unmergedPoints,
             unmergedFaces,
             labelList(),
+            labelList(),
             mergeDim
         );
 }
@@ -134,6 +139,7 @@ bool Foam::mergedSurf::merge
     const pointField& unmergedPoints,
     const faceList& unmergedFaces,
     const labelList& origZoneIds,
+    const labelList& origFaceIds,
     const scalar mergeDim
 )
 {
@@ -160,6 +166,7 @@ bool Foam::mergedSurf::merge
     // Now handle per-face information
 
     globalIndex::gatherOp(origZoneIds, zoneIds_);
+    globalIndex::gatherOp(origFaceIds, faceIds_);
 
     return true;
 }
diff --git a/src/surfMesh/mergedSurf/mergedSurf.H b/src/surfMesh/mergedSurf/mergedSurf.H
index f2adba45295..1b03e8e8743 100644
--- a/src/surfMesh/mergedSurf/mergedSurf.H
+++ b/src/surfMesh/mergedSurf/mergedSurf.H
@@ -96,6 +96,7 @@ public:
             const pointField& unmergedPoints,
             const faceList& unmergedFaces,
             const labelList& origZoneIds,
+            const labelList& origFaceIds,
             const scalar mergeDim
         );
 
@@ -133,6 +134,12 @@ public:
             return zoneIds_;
         }
 
+        //- Per-face identifier (eg, element Id)
+        virtual const labelList& faceIds() const
+        {
+            return faceIds_;
+        }
+
         //- Map for reordered points (old-to-new)
         const labelList& pointsMap() const
         {
@@ -166,6 +173,7 @@ public:
             const pointField& unmergedPoints,
             const faceList& unmergedFaces,
             const labelList& origZoneIds,
+            const labelList& origFaceIds,
             const scalar mergeDim
         );
 
diff --git a/src/surfMesh/meshedSurf/meshedSurf.H b/src/surfMesh/meshedSurf/meshedSurf.H
index 36420372e36..6933b5eed21 100644
--- a/src/surfMesh/meshedSurf/meshedSurf.H
+++ b/src/surfMesh/meshedSurf/meshedSurf.H
@@ -79,6 +79,12 @@ public:
         {
             return labelList::null();
         }
+
+        //- Per-face identifier (eg, element Id)
+        virtual const labelList& faceIds() const
+        {
+            return labelList::null();
+        }
 };
 
 
diff --git a/src/surfMesh/meshedSurf/meshedSurfRef.H b/src/surfMesh/meshedSurf/meshedSurfRef.H
index 7ec13c203b8..c52d35363b3 100644
--- a/src/surfMesh/meshedSurf/meshedSurfRef.H
+++ b/src/surfMesh/meshedSurf/meshedSurfRef.H
@@ -53,6 +53,7 @@ class meshedSurfRef
     std::reference_wrapper<const pointField> points_;
     std::reference_wrapper<const faceList> faces_;
     std::reference_wrapper<const labelList> zoneIds_;
+    std::reference_wrapper<const labelList> faceIds_;
 
 
 public:
@@ -64,7 +65,8 @@ public:
         :
             points_(std::cref<pointField>(pointField::null())),
             faces_(std::cref<faceList>(faceList::null())),
-            zoneIds_(std::cref<labelList>(labelList::null()))
+            zoneIds_(std::cref<labelList>(labelList::null())),
+            faceIds_(std::cref<labelList>(labelList::null()))
         {}
 
 
@@ -73,12 +75,14 @@ public:
         (
             const pointField& pointLst,
             const faceList& faceLst,
-            const labelList& zoneIdLst = labelList::null()
+            const labelList& zoneIdLst = labelList::null(),
+            const labelList& faceIdLst = labelList::null()
         )
         :
             points_(std::cref<pointField>(pointLst)),
             faces_(std::cref<faceList>(faceLst)),
-            zoneIds_(std::cref<labelList>(zoneIdLst))
+            zoneIds_(std::cref<labelList>(zoneIdLst)),
+            faceIds_(std::cref<labelList>(faceIdLst))
         {}
 
 
@@ -106,12 +110,19 @@ public:
             return zoneIds_.get();
         }
 
+        //- Per-face identifier (eg, element Id)
+        virtual const labelList& faceIds() const
+        {
+            return faceIds_.get();
+        }
+
         //- Remove all references by redirecting to null objects
         void clear()
         {
             points_ = std::cref<pointField>(pointField::null());
             faces_ = std::cref<faceList>(faceList::null());
             zoneIds_ = std::cref<labelList>(labelList::null());
+            faceIds_ = std::cref<labelList>(labelList::null());
         }
 
         //- Reset components
@@ -119,12 +130,14 @@ public:
         (
             const pointField& pointLst,
             const faceList& faceLst,
-            const labelList& zoneIdLst = labelList::null()
+            const labelList& zoneIdLst = labelList::null(),
+            const labelList& faceIdLst = labelList::null()
         )
         {
             points_ = std::cref<pointField>(pointLst);
             faces_ = std::cref<faceList>(faceLst);
             zoneIds_ = std::cref<labelList>(zoneIdLst);
+            faceIds_ = std::cref<labelList>(faceIdLst);
         }
 };
 
diff --git a/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C b/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
index 34632ffa60c..b76b3562e7a 100644
--- a/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
+++ b/src/surfMesh/surfaceFormats/nas/NASsurfaceFormat.C
@@ -117,6 +117,8 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
 
     DynamicList<label>  pointId;    // Nastran point id (1-based)
     DynamicList<point>  dynPoints;
+
+    DynamicList<label>  dynElemId;  // Nastran element id (1-based)
     DynamicList<Face>   dynFaces;
     DynamicList<label>  dynZones;
     DynamicList<label>  dynSizes;
@@ -127,6 +129,9 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
     label zoneId = 0;
     bool sorted = true;
 
+    // Element id gets trashed with decompose into a triangle!
+    bool ignoreElemId = false;
+
     // Name for face group
     Map<word> nameLookup;
 
@@ -222,7 +227,7 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
 
         if (cmd == "CTRIA3")
         {
-            (void) nextNasField(line, linei, 8); // 8-16
+            label elemId = readLabel(nextNasField(line, linei, 8)); // 8-16
             label groupId = readLabel(nextNasField(line, linei, 8)); // 16-24
             const auto a = readLabel(nextNasField(line, linei, 8)); // 24-32
             const auto b = readLabel(nextNasField(line, linei, 8)); // 32-40
@@ -247,13 +252,15 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
                 // Info<< "zone" << zoneId << " => group " << groupId <<nl;
             }
 
+            --elemId;   // Convert 1-based -> 0-based
+            dynElemId.append(elemId);
             dynFaces.append(Face{a, b, c});
             dynZones.append(zoneId);
             dynSizes[zoneId]++;
         }
         else if (cmd == "CQUAD4")
         {
-            (void) nextNasField(line, linei, 8); // 8-16
+            label elemId = readLabel(nextNasField(line, linei, 8)); // 8-16
             label groupId = readLabel(nextNasField(line, linei, 8)); // 16-24
             const auto a = readLabel(nextNasField(line, linei, 8)); // 24-32
             const auto b = readLabel(nextNasField(line, linei, 8)); // 32-40
@@ -281,6 +288,9 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
 
             if (faceTraits<Face>::isTri())
             {
+                ignoreElemId = true;
+                dynElemId.clear();
+
                 dynFaces.append(Face{a, b, c});
                 dynFaces.append(Face{c, d, a});
                 dynZones.append(zoneId);
@@ -289,6 +299,9 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
             }
             else
             {
+                --elemId;   // Convert 1-based -> 0-based
+
+                dynElemId.append(elemId);
                 dynFaces.append(Face{a,b,c,d});
                 dynZones.append(zoneId);
                 dynSizes[zoneId]++;
@@ -358,6 +371,10 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
     //        << " points:" << dynPoints.size()
     //        << endl;
 
+    if (ignoreElemId)
+    {
+        dynElemId.clear();
+    }
 
     // Transfer to normal lists
     this->storedPoints().transfer(dynPoints);
@@ -403,7 +420,7 @@ bool Foam::fileFormats::NASsurfaceFormat<Face>::read
         }
     }
 
-    this->sortFacesAndStore(dynFaces, dynZones, sorted);
+    this->sortFacesAndStore(dynFaces, dynZones, dynElemId, sorted);
 
     // Add zones (retaining empty ones)
     this->addZones(dynSizes, names);
@@ -428,6 +445,7 @@ void Foam::fileFormats::NASsurfaceFormat<Face>::write
     const UList<point>& pointLst = surf.points();
     const UList<Face>&  faceLst  = surf.surfFaces();
     const UList<label>& faceMap  = surf.faceMap();
+    const UList<label>& elemIds  = surf.faceIds();
 
     // for no zones, suppress the group name
     const surfZoneList zones =
@@ -439,6 +457,25 @@ void Foam::fileFormats::NASsurfaceFormat<Face>::write
 
     const bool useFaceMap = (surf.useFaceMap() && zones.size() > 1);
 
+    // Possible to use faceIds?
+    bool useOrigFaceIds =
+        (!useFaceMap && elemIds.size() == faceLst.size());
+
+    if (useOrigFaceIds)
+    {
+        // Not possible with on-the-fly face decomposition
+
+        for (const auto& f : faceLst)
+        {
+            if (f.size() > 4)
+            {
+                useOrigFaceIds = false;
+                break;
+            }
+        }
+    }
+
+
     OFstream os(filename, streamOpt);
     if (!os.good())
     {
@@ -489,6 +526,11 @@ void Foam::fileFormats::NASsurfaceFormat<Face>::write
 
             const Face& f = faceLst[facei];
 
+            if (useOrigFaceIds)
+            {
+                elemId = elemIds[facei];
+            }
+
             elemId = writeShell(os, f, elemId, zoneIndex);
         }
 
diff --git a/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C b/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C
index 25928e9732c..a2b43c28ead 100644
--- a/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C
+++ b/src/surfMesh/surfaceFormats/obj/OBJsurfaceFormat.C
@@ -71,6 +71,8 @@ bool Foam::fileFormats::OBJsurfaceFormat<Face>::read
 
     DynamicList<point> dynPoints;
     DynamicList<label> dynVerts;
+
+    DynamicList<label> dynElemId; // unused
     DynamicList<Face>  dynFaces;
 
     DynamicList<word>  dynNames;
@@ -207,7 +209,7 @@ bool Foam::fileFormats::OBJsurfaceFormat<Face>::read
     // Transfer to normal lists
     this->storedPoints().transfer(dynPoints);
 
-    this->sortFacesAndStore(dynFaces, dynZones, sorted);
+    this->sortFacesAndStore(dynFaces, dynZones, dynElemId, sorted);
 
     // Add zones (retaining empty ones)
     this->addZones(dynSizes, dynNames);
diff --git a/src/surfMesh/surfaceFormats/starcd/STARCDsurfaceFormat.C b/src/surfMesh/surfaceFormats/starcd/STARCDsurfaceFormat.C
index 797cee86520..d1db77d26f7 100644
--- a/src/surfMesh/surfaceFormats/starcd/STARCDsurfaceFormat.C
+++ b/src/surfMesh/surfaceFormats/starcd/STARCDsurfaceFormat.C
@@ -127,7 +127,9 @@ bool Foam::fileFormats::STARCDsurfaceFormat<Face>::read
 
     readHeader(is, STARCDCore::HEADER_CEL);
 
+    DynamicList<label> dynElemId;  // STARCD element id (1-based)
     DynamicList<Face>  dynFaces;
+
     DynamicList<label> dynZones;
     DynamicList<word>  dynNames;
     DynamicList<label> dynSizes;
@@ -137,6 +139,9 @@ bool Foam::fileFormats::STARCDsurfaceFormat<Face>::read
     bool sorted = true;
     label zoneId = 0;
 
+    // Element id gets trashed with decompose into a triangle!
+    bool ignoreElemId = false;
+
     label ignoredLabel, shapeId, nLabels, cellTableId, typeId;
     DynamicList<label> vertexLabels(64);
 
@@ -144,7 +149,9 @@ bool Foam::fileFormats::STARCDsurfaceFormat<Face>::read
 
     while (is.read(tok).good() && tok.isLabel())
     {
-        // const label starCellId = tok.labelToken();
+        // First token is the element id (1-based)
+        label elemId = tok.labelToken();
+
         is  >> shapeId
             >> nLabels
             >> cellTableId
@@ -203,6 +210,8 @@ bool Foam::fileFormats::STARCDsurfaceFormat<Face>::read
             if (faceTraits<Face>::isTri() && nLabels > 3)
             {
                 // The face needs triangulation
+                ignoreElemId = true;
+                dynElemId.clear();
 
                 face f(vertices);
 
@@ -220,6 +229,9 @@ bool Foam::fileFormats::STARCDsurfaceFormat<Face>::read
             }
             else if (nLabels >= 3)
             {
+                --elemId;   // Convert 1-based -> 0-based
+                dynElemId.append(elemId);
+
                 dynFaces.append(Face(vertices));
                 dynZones.append(zoneId);
                 dynSizes[zoneId]++;
@@ -228,7 +240,14 @@ bool Foam::fileFormats::STARCDsurfaceFormat<Face>::read
     }
     mapPointId.clear();
 
-    this->sortFacesAndStore(dynFaces, dynZones, sorted);
+
+    if (ignoreElemId)
+    {
+        dynElemId.clear();
+    }
+
+
+    this->sortFacesAndStore(dynFaces, dynZones, dynElemId, sorted);
 
     // Add zones (retaining empty ones)
     this->addZones(dynSizes, dynNames);
@@ -253,6 +272,7 @@ void Foam::fileFormats::STARCDsurfaceFormat<Face>::write
     const UList<point>& pointLst = surf.points();
     const UList<Face>&  faceLst  = surf.surfFaces();
     const UList<label>& faceMap  = surf.faceMap();
+    const UList<label>& elemIds  = surf.faceIds();
 
     const surfZoneList zones =
     (
@@ -263,6 +283,15 @@ void Foam::fileFormats::STARCDsurfaceFormat<Face>::write
 
     const bool useFaceMap = (surf.useFaceMap() && zones.size() > 1);
 
+    // Possible to use faceIds?
+    const bool useOrigFaceIds =
+    (
+        !useFaceMap
+     && surf.useFaceIds()
+     && elemIds.size() == faceLst.size()
+    );
+
+
     fileName baseName = filename.lessExt();
 
     // The .vrt file
@@ -287,6 +316,11 @@ void Foam::fileFormats::STARCDsurfaceFormat<Face>::write
 
             const Face& f = faceLst[facei];
 
+            if (useOrigFaceIds)
+            {
+                elemId = elemIds[facei];
+            }
+
             writeShell(os, f, elemId, zoneIndex);
             ++elemId;
         }
diff --git a/src/surfMesh/surfaceFormats/vtk/VTKsurfaceFormat.C b/src/surfMesh/surfaceFormats/vtk/VTKsurfaceFormat.C
index 068c6c77f23..38f19dc70e8 100644
--- a/src/surfMesh/surfaceFormats/vtk/VTKsurfaceFormat.C
+++ b/src/surfMesh/surfaceFormats/vtk/VTKsurfaceFormat.C
@@ -175,6 +175,8 @@ bool Foam::fileFormats::VTKsurfaceFormat<Face>::read
         }
     }
 
+    DynamicList<label> dynElemId; // unused
+
     if (nTri > faces.size())
     {
         // We are here if the target surface needs triangles and
@@ -203,7 +205,7 @@ bool Foam::fileFormats::VTKsurfaceFormat<Face>::read
             zoneSizes[zonei]++;
         }
 
-        this->sortFacesAndStore(dynFaces, dynZones, sorted);
+        this->sortFacesAndStore(dynFaces, dynZones, dynElemId, sorted);
 
         // Add zones (retaining empty ones)
         this->addZones(zoneSizes, zoneNames);
@@ -225,7 +227,7 @@ bool Foam::fileFormats::VTKsurfaceFormat<Face>::read
             zoneSizes[zonei]++;
         }
 
-        this->sortFacesAndStore(dynFaces, dynZones, sorted);
+        this->sortFacesAndStore(dynFaces, dynZones, dynElemId, sorted);
 
         // Add zones (retaining empty ones)
         this->addZones(zoneSizes, zoneNames);
diff --git a/src/surfMesh/writers/nastran/nastranSurfaceWriter.C b/src/surfMesh/writers/nastran/nastranSurfaceWriter.C
index 9592e5bba92..fcfae7d7607 100644
--- a/src/surfMesh/writers/nastran/nastranSurfaceWriter.C
+++ b/src/surfMesh/writers/nastran/nastranSurfaceWriter.C
@@ -211,6 +211,24 @@ void Foam::surfaceWriters::nastranWriter::writeGeometry
     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());
+
+    if (useOrigFaceIds)
+    {
+        // Not possible with on-the-fly face decomposition
+        for (const auto& f : faces)
+        {
+            if (f.size() > 4)
+            {
+                useOrigFaceIds = false;
+                break;
+            }
+        }
+    }
+
 
     // Write points
 
@@ -238,6 +256,12 @@ void Foam::surfaceWriters::nastranWriter::writeGeometry
     {
         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);
 
diff --git a/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C b/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C
index 737240262c9..56fee262501 100644
--- a/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C
+++ b/src/surfMesh/writers/nastran/nastranSurfaceWriterImpl.C
@@ -247,6 +247,16 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::writeTemplate
 
         // Regular (undecomposed) faces
         const faceList& faces = surf.faces();
+        const labelList& elemIds = surf.faceIds();
+
+        // Possible to use faceIds?
+        // Not possible with on-the-fly face decomposition
+        const bool useOrigFaceIds =
+        (
+            elemIds.size() == faces.size()
+         && decompFaces.empty()
+        );
+
 
         label elemId = 0;
 
@@ -254,6 +264,12 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::writeTemplate
         {
             forAll(faces, facei)
             {
+                if (useOrigFaceIds)
+                {
+                    // When available and not decomposed
+                    elemId = elemIds[facei];
+                }
+
                 const label beginElemId = elemId;
 
                 // Any face decomposition
@@ -299,6 +315,12 @@ Foam::fileName Foam::surfaceWriters::nastranWriter::writeTemplate
 
             forAll(faces, facei)
             {
+                if (useOrigFaceIds)
+                {
+                    // When available and not decomposed
+                    elemId = elemIds[facei];
+                }
+
                 const Type v(varScale * *valIter);
                 ++valIter;
 
diff --git a/src/surfMesh/writers/starcd/starcdSurfaceWriter.C b/src/surfMesh/writers/starcd/starcdSurfaceWriter.C
index 2f31924bed5..789d7cbb538 100644
--- a/src/surfMesh/writers/starcd/starcdSurfaceWriter.C
+++ b/src/surfMesh/writers/starcd/starcdSurfaceWriter.C
@@ -145,7 +145,14 @@ Foam::fileName Foam::surfaceWriters::starcdWriter::write()
             mkDir(outputFile.path());
         }
 
-        MeshedSurfaceProxy<face>(surf.points(), surf.faces()).write
+        MeshedSurfaceProxy<face>
+        (
+            surf.points(),
+            surf.faces(),
+            UList<surfZone>::null(), // one zone
+            labelUList::null(),      // no faceMap
+            surf.faceIds()           // with face ids (if possible)
+        ).write
         (
             outputFile,
             "inp",
-- 
GitLab