LCOV - code coverage report
Current view: top level - utils - meshio.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 0.0 % 432 0
Test Date: 2025-10-23 22:45:05 Functions: 0.0 % 13 0
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : #include "meshio.hpp"
       5              : 
       6              : #include <unordered_map>
       7              : #include <vector>
       8              : #include <mfem.hpp>
       9              : 
      10              : #define GMSH_BIN  // Use binary Gmsh format
      11              : 
      12              : namespace palace
      13              : {
      14              : 
      15              : namespace
      16              : {
      17              : 
      18            0 : inline int ElemTypeComsol(const std::string &type)
      19              : {
      20            0 :   if (!type.compare("tri"))  // 3-node triangle
      21              :   {
      22              :     return 2;
      23              :   }
      24            0 :   if (!type.compare("quad"))  // 4-node quadrangle
      25              :   {
      26              :     return 3;
      27              :   }
      28            0 :   if (!type.compare("tet"))  // 4-node tetrahedron
      29              :   {
      30              :     return 4;
      31              :   }
      32            0 :   if (!type.compare("hex"))  // 8-node hexahedron
      33              :   {
      34              :     return 5;
      35              :   }
      36            0 :   if (!type.compare("prism"))  // 6-node prism
      37              :   {
      38              :     return 6;
      39              :   }
      40            0 :   if (!type.compare("pyr"))  // 5-node pyramid
      41              :   {
      42              :     return 7;
      43              :   }
      44            0 :   if (!type.compare("tri2"))  // 6-node triangle
      45              :   {
      46              :     return 9;
      47              :   }
      48            0 :   if (!type.compare("quad2"))  // 9-node quadrangle
      49              :   {
      50              :     return 10;
      51              :   }
      52            0 :   if (!type.compare("tet2"))  // 10-node tetrahedron
      53              :   {
      54              :     return 11;
      55              :   }
      56            0 :   if (!type.compare("hex2"))  // 27-node hexahedron
      57              :   {
      58              :     return 12;
      59              :   }
      60            0 :   if (!type.compare("prism2"))  // 18-node prism
      61              :   {
      62              :     return 13;
      63              :   }
      64            0 :   if (!type.compare("pyr2"))  // 14-node pyramid
      65              :   {
      66            0 :     return 14;
      67              :   }
      68              :   return 0;  // Skip this element type
      69              : }
      70              : 
      71            0 : inline int ElemTypeNastran(const std::string &type)
      72              : {
      73              :   // Returns only the low-order type for a given keyword.
      74            0 :   if (!type.compare(0, 5, "CTRIA"))  // 3-node triangle
      75              :   {
      76              :     return 2;
      77              :   }
      78            0 :   if (!type.compare(0, 5, "CQUAD"))  // 4-node quadrangle
      79              :   {
      80              :     return 3;
      81              :   }
      82            0 :   if (!type.compare(0, 6, "CTETRA"))  // 4-node tetrahedron
      83              :   {
      84              :     return 4;
      85              :   }
      86            0 :   if (!type.compare(0, 5, "CHEXA"))  // 8-node hexahedron
      87              :   {
      88              :     return 5;
      89              :   }
      90            0 :   if (!type.compare(0, 6, "CPENTA"))  // 6-node prism
      91              :   {
      92              :     return 6;
      93              :   }
      94            0 :   if (!type.compare(0, 6, "CPYRAM"))  // 5-node pyramid
      95              :   {
      96            0 :     return 7;
      97              :   }
      98              :   return 0;  // Skip this element type
      99              : }
     100              : 
     101            0 : inline int HOElemTypeNastran(const int lo_type, const int num_nodes)
     102              : {
     103              :   // Get high-order element type for corresponding low-order type.
     104            0 :   if (lo_type == 2 && num_nodes > 3)  // 6-node triangle
     105              :   {
     106            0 :     MFEM_VERIFY(num_nodes == 6, "Invalid high-order Nastran element!");
     107              :     return 9;
     108              :   }
     109            0 :   if (lo_type == 3)
     110              :   {
     111            0 :     if (num_nodes == 9)  // 9-node quadrangle
     112              :     {
     113              :       return 10;
     114              :     }
     115            0 :     if (num_nodes == 8)  // 8-node quadrangle
     116              :     {
     117              :       return 16;
     118              :     }
     119            0 :     MFEM_VERIFY(num_nodes == 4, "Invalid high-order Nastran element!");
     120              :     return lo_type;
     121              :   }
     122            0 :   if (lo_type == 4 && num_nodes > 4)  // 10-node tetrahedron
     123              :   {
     124            0 :     MFEM_VERIFY(num_nodes == 10, "Invalid high-order Nastran element!");
     125              :     return 11;
     126              :   }
     127            0 :   if (lo_type == 5 && num_nodes > 8)  // 20-node hexahedron
     128              :   {
     129            0 :     MFEM_VERIFY(num_nodes == 20, "Invalid high-order Nastran element!");
     130              :     return 17;
     131              :   }
     132            0 :   if (lo_type == 6 && num_nodes > 6)  // 15-node prism
     133              :   {
     134            0 :     MFEM_VERIFY(num_nodes == 15, "Invalid high-order Nastran element!");
     135              :     return 18;
     136              :   }
     137            0 :   if (lo_type == 7 && num_nodes > 5)  // 13-node pyramid
     138              :   {
     139            0 :     MFEM_VERIFY(num_nodes == 13, "Invalid high-order Nastran element!");
     140              :     return 19;
     141              :   }
     142              :   return lo_type;
     143              : }
     144              : 
     145            0 : inline int LOElemTypeGmsh(int ho_type)
     146              : {
     147              :   if (ho_type == 9)  // 6-node triangle
     148              :   {
     149            0 :     return 2;
     150              :   }
     151              :   if (ho_type == 10 || ho_type == 16)  // 9- or 8-node quadrangle
     152              :   {
     153            0 :     return 3;
     154              :   }
     155              :   if (ho_type == 11)  // 10-node tetrahedron
     156              :   {
     157            0 :     return 4;
     158              :   }
     159              :   if (ho_type == 12 || ho_type == 17)  // 27- or 20-node hexahedron
     160              :   {
     161            0 :     return 5;
     162              :   }
     163              :   if (ho_type == 13 || ho_type == 18)  // 18- or 15-node prism
     164              :   {
     165            0 :     return 6;
     166              :   }
     167              :   if (ho_type == 14 || ho_type == 19)  // 14- or 13-node pyramid
     168              :   {
     169            0 :     return 7;
     170              :   }
     171              :   return ho_type;
     172              : }
     173              : 
     174              : constexpr int ElemNumNodes[] = {-1,  // 2-node edge
     175              :                                 3,  4,  4,  8,  6,  5,
     176              :                                 -1,  // 3-node edge
     177              :                                 6,  9,  10, 27, 18, 14,
     178              :                                 -1,  // 1-node node
     179              :                                 8,  20, 15, 13};
     180              : 
     181              : // From COMSOL or Nastran to Gmsh ordering. See:
     182              : //   - https://gmsh.info/doc/texinfo/gmsh.html#Node-ordering
     183              : //   - https://tinyurl.com/yezswzfv
     184              : //   - https://tinyurl.com/4d32zxtn
     185              : constexpr int SkipElem[] = {-1};
     186              : constexpr int Msh3[] = {0, 1, 2};
     187              : constexpr int Msh4[] = {0, 1, 2, 3};
     188              : constexpr int Msh5[] = {0, 1, 2, 3, 4};
     189              : constexpr int Msh6[] = {0, 1, 2, 3, 4, 5};
     190              : constexpr int Msh8[] = {0, 1, 2, 3, 4, 5, 6, 7};
     191              : constexpr int Msh9[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
     192              : 
     193              : constexpr int MphQuad4[] = {0, 1, 3, 2};
     194              : constexpr int MphHex8[] = {0, 1, 3, 2, 4, 5, 7, 6};
     195              : constexpr int MphPyr5[] = {0, 1, 3, 2, 4};
     196              : constexpr int MphTri6[] = {0, 1, 2, 3, 5, 4};
     197              : constexpr int MphQuad9[] = {0, 1, 3, 2, 4, 7, 8, 5, 6};
     198              : constexpr int MphTet10[] = {0, 1, 2, 3, 4, 6, 5, 7, 9, 8};
     199              : constexpr int MphHex27[] = {0,  1,  3,  2,  4,  5,  7,  6,  8,  9,  20, 11, 13, 10,
     200              :                             21, 12, 22, 26, 23, 15, 24, 14, 16, 17, 25, 18, 19};
     201              : constexpr int MphWdg18[] = {0, 1, 2, 3, 4, 5, 6, 7, 9, 8, 15, 10, 16, 17, 11, 12, 13, 14};
     202              : constexpr int MphPyr14[] = {0, 1, 3, 2, 4, 5, 6, 13, 8, 10, 7, 9, 12, 11};
     203              : 
     204              : constexpr int NasTet10[] = {0, 1, 2, 3, 4, 5, 6, 7, 9, 8};
     205              : constexpr int NasHex20[] = {0,  1, 2,  3,  4,  5,  6,  7,  8,  11,
     206              :                             13, 9, 10, 12, 14, 15, 16, 18, 19, 17};
     207              : constexpr int NasWdg15[] = {0, 1, 2, 3, 4, 5, 6, 9, 7, 8, 10, 11, 12, 14, 13};
     208              : constexpr int NasPyr13[] = {0, 1, 2, 3, 4, 5, 8, 10, 6, 7, 9, 11, 12};
     209              : 
     210              : constexpr const int *ElemNodesComsol[] = {SkipElem, Msh3,     MphQuad4, Msh4,     MphHex8,
     211              :                                           Msh6,     MphPyr5,  SkipElem, MphTri6,  MphQuad9,
     212              :                                           MphTet10, MphHex27, MphWdg18, MphPyr14, SkipElem,
     213              :                                           SkipElem, SkipElem, SkipElem, SkipElem};
     214              : constexpr const int *ElemNodesNastran[] = {SkipElem, Msh3,     Msh4,     Msh4,     Msh8,
     215              :                                            Msh6,     Msh5,     SkipElem, Msh6,     Msh9,
     216              :                                            NasTet10, SkipElem, SkipElem, SkipElem, SkipElem,
     217              :                                            Msh8,     NasHex20, NasWdg15, NasPyr13};
     218              : 
     219              : // Get line, strip comments, leading/trailing whitespace. Should not be called if end of
     220              : // file is expected.
     221            0 : inline std::string GetLineComsol(std::ifstream &input)
     222              : {
     223              :   std::string str;
     224            0 :   std::getline(input, str);
     225            0 :   MFEM_VERIFY(input, "Unexpected read failure parsing mesh file!");
     226              :   const auto pos = str.find_first_of('#');
     227            0 :   if (pos != std::string::npos)
     228              :   {
     229            0 :     str.erase(pos);
     230              :   }
     231              :   const auto start = str.find_first_not_of(" \t");
     232            0 :   if (start == std::string::npos)
     233              :   {
     234            0 :     return "";
     235              :   }
     236              :   const auto stop = str.find_last_not_of(" \t");
     237            0 :   return str.substr(start, stop - start + 1);
     238              : }
     239              : 
     240            0 : inline std::string GetLineNastran(std::ifstream &input)
     241              : {
     242              :   std::string str;
     243            0 :   std::getline(input, str);
     244            0 :   MFEM_VERIFY(input.good(), "Unexpected read failure parsing mesh file!");
     245            0 :   str.erase(std::remove(str.begin(), str.end(), '\r'), str.end());
     246            0 :   return str[0] == '$' ? "" : str;
     247              : }
     248              : 
     249              : // COMSOL strings are parsed as an integer length followed by array of integers for the
     250              : // string characters.
     251            0 : inline std::string ReadStringComsol(std::istream &input)
     252              : {
     253              :   int n;
     254              :   std::string str;
     255            0 :   input >> n >> str;
     256            0 :   return str;
     257              : }
     258              : 
     259            0 : inline std::string ReadStringComsolBinary(std::istream &input)
     260              : {
     261              :   int n;
     262            0 :   input.read(reinterpret_cast<char *>(&n), sizeof(int));
     263            0 :   std::vector<int> vstr(n);
     264            0 :   input.read(reinterpret_cast<char *>(vstr.data()), (std::streamsize)(n * sizeof(int)));
     265            0 :   return std::string(vstr.begin(), vstr.end());
     266              : }
     267              : 
     268              : // Nastran has a special floating point format: "-7.-1" instead of "-7.E-01" or "2.3+2"
     269              : // instead of "2.3E+02".
     270            0 : inline double ConvertDoubleNastran(const std::string &str)
     271              : {
     272              :   double d;
     273              :   try
     274              :   {
     275              :     d = std::stod(str);
     276              :   }
     277            0 :   catch (const std::invalid_argument &ia)
     278              :   {
     279            0 :     const std::size_t start = str.find_first_not_of(' ');
     280            0 :     MFEM_VERIFY(start != std::string::npos,
     281              :                 "Invalid number conversion parsing Nastran mesh!")
     282            0 :     std::string fstr = str.substr(start);
     283            0 :     std::size_t pos = fstr.find('+', 1);  // Skip leading +/- sign
     284            0 :     if (pos != std::string::npos)
     285              :     {
     286            0 :       fstr.replace(pos, 1, "E+");
     287              :     }
     288            0 :     else if ((pos = fstr.find('-', 1)) != std::string::npos)
     289              :     {
     290            0 :       fstr.replace(pos, 1, "E-");
     291              :     }
     292              :     d = std::stod(fstr);
     293            0 :   }
     294            0 :   return d;
     295              : }
     296              : 
     297              : inline void WriteNode(std::ostream &buffer, const int tag, const double *coord)
     298              : {
     299              : #if defined(GMSH_BIN)
     300            0 :   buffer.write(reinterpret_cast<const char *>(&tag), sizeof(int));
     301            0 :   buffer.write(reinterpret_cast<const char *>(coord), 3 * sizeof(double));
     302              :   // No newline for binary data.
     303              : #else
     304              :   // Always 3D coordinates (user sets floating point format/precision on buffer).
     305              :   buffer << tag << ' ' << coord[0] << ' ' << coord[1] << ' ' << coord[2] << '\n';
     306              : #endif
     307              : }
     308              : 
     309            0 : inline void WriteElement(std::ostream &buffer, const int tag, const int type,
     310              :                          const int geom, const int nodes[])
     311              : {
     312              : #if defined(GMSH_BIN)
     313            0 :   const int data[3] = {tag, geom, geom};
     314            0 :   buffer.write(reinterpret_cast<const char *>(data), 3 * sizeof(int));
     315            0 :   buffer.write(reinterpret_cast<const char *>(nodes),
     316            0 :                (std::streamsize)(ElemNumNodes[type - 1] * sizeof(int)));
     317              :   // No newline for binary data.
     318              : #else
     319              :   buffer << tag << ' ' << type << " 2 " << geom << ' ' << geom;
     320              :   for (int i = 0; i < ElemNumNodes[type - 1]; i++)
     321              :   {
     322              :     buffer << ' ' << nodes[i];
     323              :   }
     324              :   buffer << '\n';
     325              : #endif
     326            0 : }
     327              : 
     328            0 : void WriteGmsh(std::ostream &buffer, const std::vector<double> &node_coords,
     329              :                const std::vector<int> &node_tags,
     330              :                const std::unordered_map<int, std::vector<int>> &elem_nodes,
     331              :                const bool use_lo_type)
     332              : {
     333              :   // Write the Gmsh file header (version 2.2).
     334              :   buffer << "$MeshFormat\n2.2 "
     335              :          <<
     336              : #if defined(GMSH_BIN)
     337              :       "1 " <<
     338              : #else
     339              :       "0 " <<
     340              : #endif
     341            0 :       sizeof(double) << '\n';
     342              : #if defined(GMSH_BIN)
     343            0 :   const int one = 1;
     344            0 :   buffer.write(reinterpret_cast<const char *>(&one), sizeof(int));
     345            0 :   buffer << '\n';
     346              : #endif
     347            0 :   buffer << "$EndMeshFormat\n";
     348              : 
     349              :   // Write mesh nodes.
     350            0 :   const int num_nodes = (int)node_coords.size() / 3;
     351            0 :   MFEM_VERIFY(num_nodes > 0 && node_coords.size() % 3 == 0,
     352              :               "Gmsh nodes should always be in 3D space!");
     353            0 :   buffer << "$Nodes\n" << num_nodes << '\n';
     354              :   {
     355            0 :     if (!node_tags.empty())
     356              :     {
     357              :       // Use input node tags which should be positive but don't need to be contiguous.
     358            0 :       MFEM_VERIFY(node_tags.size() == (std::size_t)num_nodes,
     359              :                   "Invalid size for node tags!");
     360            0 :       for (int i = 0; i < num_nodes; i++)
     361              :       {
     362            0 :         WriteNode(buffer, node_tags[i], &node_coords[3 * i]);
     363              :       }
     364              :     }
     365              :     else
     366              :     {
     367              :       // Label nodes as contiguous starting at 1.
     368            0 :       for (int i = 0; i < num_nodes; i++)
     369              :       {
     370            0 :         WriteNode(buffer, i + 1, &node_coords[3 * i]);
     371              :       }
     372              :     }
     373              :   }
     374              : #if defined(GMSH_BIN)
     375            0 :   buffer << '\n';
     376              : #endif
     377            0 :   buffer << "$EndNodes\n";
     378              : 
     379              :   // Write mesh elements.
     380              :   int tot_num_elem = 0;
     381            0 :   for (const auto &[elem_type, nodes] : elem_nodes)
     382              :   {
     383            0 :     MFEM_VERIFY(elem_type > 0, "Invalid element type writing Gmsh elements!");
     384            0 :     const int &num_elem_nodes = ElemNumNodes[elem_type - 1];
     385            0 :     tot_num_elem += ((int)nodes.size()) / (num_elem_nodes + 1);
     386            0 :     MFEM_VERIFY(nodes.size() % (num_elem_nodes + 1) == 0,
     387              :                 "Unexpected data size when writing elements!");
     388              :   }
     389            0 :   MFEM_VERIFY(tot_num_elem > 0, "No mesh elements parsed from COMSOL mesh file!");
     390            0 :   buffer << "$Elements\n" << tot_num_elem << '\n';
     391              :   {
     392              :     int tag = 1;  // Global element tag
     393            0 :     for (const auto &[elem_type, nodes] : elem_nodes)
     394              :     {
     395            0 :       const int elem_type_w = use_lo_type ? LOElemTypeGmsh(elem_type) : elem_type;
     396            0 :       const int &num_elem_nodes = ElemNumNodes[elem_type - 1];
     397            0 :       const int num_elem = (int)nodes.size() / (num_elem_nodes + 1);
     398              : #if defined(GMSH_BIN)
     399              :       // For binary output, write the element header for each type. Always have 2 tags
     400              :       // (physical + geometry).
     401            0 :       const int header[3] = {elem_type_w, num_elem, 2};
     402            0 :       buffer.write(reinterpret_cast<const char *>(header), 3 * sizeof(int));
     403              : #endif
     404            0 :       for (int i = 0; i < num_elem; i++)
     405              :       {
     406            0 :         WriteElement(buffer, tag++, elem_type_w,
     407            0 :                      nodes[i * (num_elem_nodes + 1)],        // Geometry tag
     408            0 :                      &nodes[i * (num_elem_nodes + 1) + 1]);  // Element nodes
     409              :       }
     410              :     }
     411              :   }
     412              : #if defined(GMSH_BIN)
     413            0 :   buffer << '\n';
     414              : #endif
     415            0 :   buffer << "$EndElements\n";
     416            0 : }
     417              : 
     418              : }  // namespace
     419              : 
     420              : namespace mesh
     421              : {
     422              : 
     423            0 : void ConvertMeshComsol(const std::string &filename, std::ostream &buffer,
     424              :                        bool remove_curvature)
     425              : {
     426              :   // Read a COMSOL format mesh.
     427            0 :   const int comsol_bin = !filename.compare(filename.length() - 7, 7, ".mphbin") ||
     428            0 :                          !filename.compare(filename.length() - 7, 7, ".MPHBIN");
     429            0 :   MFEM_VERIFY(!filename.compare(filename.length() - 7, 7, ".mphtxt") ||
     430              :                   !filename.compare(filename.length() - 7, 7, ".MPHTXT") || comsol_bin,
     431              :               "Invalid file extension for COMSOL mesh format conversion!");
     432            0 :   std::ifstream input(filename);
     433            0 :   if (!input.is_open())
     434              :   {
     435            0 :     MFEM_ABORT("Unable to open mesh file \"" << filename << "\"!");
     436              :   }
     437              : 
     438              :   // Parse COMSOL header. COMSOL encodes strings as integer-string pairs where the integer
     439              :   // is the string length. It also allows for blank lines and other whitespace wherever in
     440              :   // the file.
     441              :   {
     442            0 :     int version[2] = {-1, -1};
     443            0 :     int num_tags = -1;
     444            0 :     int num_types = -1;
     445            0 :     if (!comsol_bin)
     446              :     {
     447            0 :       while (num_types < 0)
     448              :       {
     449            0 :         auto line = GetLineComsol(input);
     450            0 :         if (!line.empty())
     451              :         {
     452            0 :           std::istringstream sline(line);
     453            0 :           if (version[0] < 0)
     454              :           {
     455            0 :             sline >> version[0] >> version[1];
     456              :           }
     457            0 :           else if (num_tags < 0)
     458              :           {
     459            0 :             sline >> num_tags;
     460              :             int i = 0;
     461            0 :             while (i < num_tags)
     462              :             {
     463            0 :               if (!GetLineComsol(input).empty())
     464              :               {
     465            0 :                 i++;
     466              :               }
     467              :             }
     468              :           }
     469            0 :           else if (num_types < 0)
     470              :           {
     471            0 :             sline >> num_types;
     472              :             int i = 0;
     473            0 :             while (i < num_types)
     474              :             {
     475            0 :               if (!GetLineComsol(input).empty())
     476              :               {
     477            0 :                 i++;
     478              :               }
     479              :             }
     480              :           }
     481            0 :         }
     482              :       }
     483              :     }
     484              :     else
     485              :     {
     486            0 :       input.read(reinterpret_cast<char *>(version), 2 * sizeof(int));
     487            0 :       input.read(reinterpret_cast<char *>(&num_tags), sizeof(int));
     488              :       {
     489              :         int i = 0;
     490            0 :         while (i < num_tags)
     491              :         {
     492            0 :           ReadStringComsolBinary(input);
     493            0 :           i++;
     494              :         }
     495              :       }
     496            0 :       input.read(reinterpret_cast<char *>(&num_types), sizeof(int));
     497              :       {
     498              :         int i = 0;
     499            0 :         while (i < num_types)
     500              :         {
     501            0 :           ReadStringComsolBinary(input);
     502            0 :           i++;
     503              :         }
     504              :       }
     505              :     }
     506            0 :     MFEM_VERIFY(version[0] == 0 && version[1] == 1, "Invalid COMSOL file version!");
     507              :   }
     508              : 
     509              :   // Parse mesh objects until we get to the mesh. Currently only supports a single mesh
     510              :   // object in the file, and selections are ignored.
     511              :   while (true)
     512              :   {
     513            0 :     int object[3] = {-1, -1, -1};
     514              :     std::string object_class;
     515            0 :     if (!comsol_bin)
     516              :     {
     517            0 :       while (object_class.empty())
     518              :       {
     519            0 :         auto line = GetLineComsol(input);
     520            0 :         if (!line.empty())
     521              :         {
     522            0 :           std::istringstream sline(line);
     523            0 :           if (object[0] < 0)
     524              :           {
     525            0 :             sline >> object[0] >> object[1] >> object[2];
     526              :           }
     527            0 :           else if (object_class.empty())
     528              :           {
     529            0 :             object_class = ReadStringComsol(sline);
     530              :           }
     531            0 :         }
     532              :       }
     533              :     }
     534              :     else
     535              :     {
     536            0 :       input.read(reinterpret_cast<char *>(object), 3 * sizeof(int));
     537            0 :       object_class = ReadStringComsolBinary(input);
     538              :     }
     539            0 :     MFEM_VERIFY(object[0] == 0 && object[1] == 0 && object[2] == 1,
     540              :                 "Invalid COMSOL object version!");
     541              : 
     542              :     // If yes, then ready to parse the mesh.
     543            0 :     if (!object_class.compare(0, 4, "Mesh"))
     544              :     {
     545              :       break;
     546              :     }
     547              : 
     548              :     // Otherwise, parse over the selection to the next object.
     549            0 :     MFEM_VERIFY(!object_class.compare(0, 9, "Selection"),
     550              :                 "COMSOL mesh file only supports Mesh and Selection objects!");
     551            0 :     int version = -1;
     552              :     std::string label_str;
     553              :     std::string tag_str;
     554            0 :     int sdim = -1;
     555            0 :     int num_ent = -1;
     556            0 :     if (!comsol_bin)
     557              :     {
     558            0 :       while (num_ent < 0)
     559              :       {
     560            0 :         auto line = GetLineComsol(input);
     561            0 :         if (!line.empty())
     562              :         {
     563            0 :           std::istringstream sline(line);
     564            0 :           if (version < 0)
     565              :           {
     566            0 :             sline >> version;
     567              :           }
     568            0 :           else if (label_str.empty())
     569              :           {
     570            0 :             label_str = ReadStringComsol(sline);
     571              :           }
     572            0 :           else if (tag_str.empty())
     573              :           {
     574            0 :             tag_str = ReadStringComsol(sline);
     575              :           }
     576            0 :           else if (sdim < 0)
     577              :           {
     578            0 :             sline >> sdim;
     579              :           }
     580            0 :           else if (num_ent < 0)
     581              :           {
     582            0 :             sline >> num_ent;
     583              :           }
     584            0 :         }
     585              :       }
     586              :     }
     587              :     else
     588              :     {
     589            0 :       input.read(reinterpret_cast<char *>(&version), sizeof(int));
     590            0 :       label_str = ReadStringComsolBinary(input);
     591            0 :       tag_str = ReadStringComsolBinary(input);
     592            0 :       input.read(reinterpret_cast<char *>(&sdim), sizeof(int));
     593            0 :       input.read(reinterpret_cast<char *>(&num_ent), sizeof(int));
     594              :     }
     595              : 
     596              :     // Parse over the entities in the selection.
     597              :     int i = 0;
     598            0 :     if (!comsol_bin)
     599              :     {
     600            0 :       while (i < num_ent)
     601              :       {
     602            0 :         if (!GetLineComsol(input).empty())
     603              :         {
     604            0 :           i++;
     605              :         }
     606              :       }
     607              :     }
     608              :     else
     609              :     {
     610            0 :       while (i < num_ent)
     611              :       {
     612              :         int dummy;
     613            0 :         input.read(reinterpret_cast<char *>(&dummy), sizeof(int));
     614            0 :         i++;
     615              :       }
     616              :     }
     617              :   }  // Repeat until Mesh is found
     618              : 
     619              :   // Parse the mesh object header.
     620            0 :   int sdim = -1;
     621            0 :   int num_nodes = -1;
     622            0 :   int nodes_start = -1;
     623              :   {
     624            0 :     int version = -1;
     625            0 :     if (!comsol_bin)
     626              :     {
     627            0 :       while (nodes_start < 0)
     628              :       {
     629            0 :         auto line = GetLineComsol(input);
     630            0 :         if (!line.empty())
     631              :         {
     632            0 :           std::istringstream sline(line);
     633            0 :           if (version < 0)
     634              :           {
     635            0 :             sline >> version;
     636              :           }
     637            0 :           else if (sdim < 0)
     638              :           {
     639            0 :             sline >> sdim;
     640              :           }
     641            0 :           else if (num_nodes < 0)
     642              :           {
     643            0 :             sline >> num_nodes;
     644              :           }
     645            0 :           else if (nodes_start < 0)
     646              :           {
     647            0 :             sline >> nodes_start;
     648              :           }
     649            0 :         }
     650              :       }
     651              :     }
     652              :     else
     653              :     {
     654            0 :       input.read(reinterpret_cast<char *>(&version), sizeof(int));
     655            0 :       input.read(reinterpret_cast<char *>(&sdim), sizeof(int));
     656            0 :       input.read(reinterpret_cast<char *>(&num_nodes), sizeof(int));
     657            0 :       input.read(reinterpret_cast<char *>(&nodes_start), sizeof(int));
     658              :     }
     659            0 :     MFEM_VERIFY(version == 4, "Only COMSOL files with Mesh version 4 are supported!");
     660            0 :     MFEM_VERIFY(sdim == 2 || sdim == 3,
     661              :                 "COMSOL mesh nodes are required to be in 2D or 3D space!");
     662            0 :     MFEM_VERIFY(num_nodes > 0, "COMSOL mesh file contains no nodes!");
     663            0 :     MFEM_VERIFY(nodes_start >= 0, "COMSOL mesh nodes have a negative starting tag!");
     664              :   }
     665              : 
     666              :   // Parse mesh nodes.
     667              :   std::vector<double> node_coords;
     668              :   {
     669              :     // Gmsh nodes are always 3D, so initialize to 0.0 in case z-coordinate isn't set.
     670            0 :     node_coords.resize(3 * num_nodes, 0.0);
     671              :     int i = 0;
     672            0 :     if (!comsol_bin)
     673              :     {
     674            0 :       while (i < num_nodes)
     675              :       {
     676            0 :         auto line = GetLineComsol(input);
     677            0 :         if (!line.empty())
     678              :         {
     679            0 :           std::istringstream sline(line);
     680            0 :           for (int j = 0; j < sdim; j++)
     681              :           {
     682            0 :             sline >> node_coords[3 * i + j];
     683              :           }
     684            0 :           i++;
     685            0 :         }
     686              :       }
     687              :     }
     688              :     else
     689              :     {
     690              :       // Don't read as a single block in case sdim < 3.
     691            0 :       while (i < num_nodes)
     692              :       {
     693            0 :         input.read(reinterpret_cast<char *>(node_coords.data() + 3 * i),
     694            0 :                    (std::streamsize)(sdim * sizeof(double)));
     695            0 :         i++;
     696              :       }
     697              :     }
     698              :   }
     699              : 
     700              :   // Parse mesh elements. Store for each element of each type: [geometry tag, [node tags]].
     701              :   std::unordered_map<int, std::vector<int>> elem_nodes;
     702              :   {
     703            0 :     int num_elem_types = -1;
     704            0 :     if (!comsol_bin)
     705              :     {
     706            0 :       while (num_elem_types < 0)
     707              :       {
     708            0 :         auto line = GetLineComsol(input);
     709            0 :         if (!line.empty())
     710              :         {
     711            0 :           std::istringstream sline(line);
     712            0 :           if (num_elem_types < 0)
     713              :           {
     714            0 :             sline >> num_elem_types;
     715              :           }
     716            0 :         }
     717              :       }
     718              :     }
     719              :     else
     720              :     {
     721            0 :       input.read(reinterpret_cast<char *>(&num_elem_types), sizeof(int));
     722              :     }
     723            0 :     MFEM_VERIFY(num_elem_types > 0, "COMSOL mesh file contains no elements!");
     724              : 
     725              :     int parsed_types = 0;  // COMSOL groups elements by type in file
     726            0 :     int elem_type = -1;
     727            0 :     int num_elem_nodes = -1;
     728            0 :     int num_elem = -1;
     729            0 :     int num_elem_geom = -1;
     730              :     bool skip_type = false;
     731            0 :     while (parsed_types < num_elem_types)
     732              :     {
     733            0 :       if (!comsol_bin)
     734              :       {
     735            0 :         auto line = GetLineComsol(input);
     736            0 :         if (!line.empty())
     737              :         {
     738            0 :           std::istringstream sline(line);
     739            0 :           if (elem_type < 0)
     740              :           {
     741            0 :             auto elem_str = ReadStringComsol(sline);
     742            0 :             MFEM_VERIFY(!elem_str.empty(),
     743              :                         "Unexpected empty element type found in COMSOL mesh file!");
     744            0 :             elem_type = ElemTypeComsol(elem_str);
     745            0 :             skip_type = (elem_type == 0);
     746            0 :             MFEM_VERIFY(skip_type || elem_nodes.find(elem_type) == elem_nodes.end(),
     747              :                         "Duplicate element types found in COMSOL mesh file!");
     748              :           }
     749            0 :           else if (num_elem_nodes < 0)
     750              :           {
     751            0 :             sline >> num_elem_nodes;
     752            0 :             MFEM_VERIFY(num_elem_nodes > 0,
     753              :                         "COMSOL element type " << elem_type << " has no nodes!");
     754            0 :             MFEM_VERIFY(skip_type || num_elem_nodes == ElemNumNodes[elem_type - 1],
     755              :                         "Mismatch between COMSOL and Gmsh element types!");
     756              :           }
     757            0 :           else if (num_elem < 0)
     758              :           {
     759            0 :             sline >> num_elem;
     760            0 :             MFEM_VERIFY(num_elem > 0,
     761              :                         "COMSOL mesh file has no elements of type " << elem_type << "!");
     762              :             std::vector<int> *data = nullptr;
     763            0 :             if (!skip_type)
     764              :             {
     765              :               data = &elem_nodes[elem_type];
     766            0 :               data->resize(num_elem * (num_elem_nodes + 1));  // Node tags + geometry tag
     767              :             }
     768              : 
     769              :             // Parse all element nodes.
     770              :             int i = 0;
     771            0 :             while (i < num_elem)
     772              :             {
     773            0 :               line = GetLineComsol(input);
     774            0 :               if (!line.empty())
     775              :               {
     776            0 :                 if (!skip_type)
     777              :                 {
     778            0 :                   std::istringstream isline(line);
     779            0 :                   for (int j = 0; j < num_elem_nodes; j++)
     780              :                   {
     781              :                     // Permute and reset to 1-based node tags.
     782            0 :                     const int &p = ElemNodesComsol[elem_type - 1][j];
     783            0 :                     isline >> (*data)[i * (num_elem_nodes + 1) + 1 + p];
     784            0 :                     (*data)[i * (num_elem_nodes + 1) + 1 + p] += (1 - nodes_start);
     785              :                   }
     786            0 :                 }
     787            0 :                 i++;
     788              :               }
     789              :             }
     790              :           }
     791            0 :           else if (num_elem_geom < 0)
     792              :           {
     793            0 :             sline >> num_elem_geom;
     794            0 :             MFEM_VERIFY(num_elem_geom == num_elem,
     795              :                         "COMSOL mesh file should have geometry tags for all elements!");
     796              :             std::vector<int> *data = nullptr;
     797            0 :             if (!skip_type)
     798              :             {
     799            0 :               MFEM_VERIFY(elem_nodes.find(elem_type) != elem_nodes.end(),
     800              :                           "Can't find expected element type!");
     801              :               data = &elem_nodes[elem_type];
     802            0 :               MFEM_VERIFY(data->size() == (std::size_t)num_elem * (num_elem_nodes + 1),
     803              :                           "Unexpected element data size!");
     804              :             }
     805              : 
     806              :             // Parse all element geometry tags (stored at beginning of element nodes). For
     807              :             // geometric entites in < 3D, the exported COMSOL tags are 0-based and need
     808              :             // correcting to 1-based for Gmsh.
     809              :             int i = 0;
     810              :             const int geom_start =
     811            0 :                 (elem_type < 4 || (elem_type > 7 && elem_type < 11)) ? 1 : 0;
     812            0 :             while (i < num_elem)
     813              :             {
     814            0 :               line = GetLineComsol(input);
     815            0 :               if (!line.empty())
     816              :               {
     817            0 :                 if (!skip_type)
     818              :                 {
     819            0 :                   std::istringstream ssline(line);
     820            0 :                   ssline >> (*data)[i * (num_elem_nodes + 1)];
     821            0 :                   (*data)[i * (num_elem_nodes + 1)] += geom_start;
     822            0 :                 }
     823            0 :                 i++;
     824              :               }
     825              :             }
     826              : 
     827              :             // Debug
     828              :             // std::cout << "Finished parsing " << num_elem
     829              :             //           << " elements with type " << elem_type
     830              :             //           << " (parsed types " << parsed_types + 1 << ")\n";
     831              : 
     832              :             // Finished with this element type, on to the next.
     833            0 :             parsed_types++;
     834            0 :             elem_type = num_elem_nodes = num_elem = num_elem_geom = -1;
     835              :             skip_type = false;
     836              :           }
     837            0 :         }
     838              :       }
     839              :       else
     840              :       {
     841            0 :         auto elem_str = ReadStringComsolBinary(input);
     842            0 :         MFEM_VERIFY(!elem_str.empty(),
     843              :                     "Unexpected empty element type found in COMSOL mesh file!");
     844            0 :         elem_type = ElemTypeComsol(elem_str);
     845              :         skip_type = (elem_type == 0);
     846            0 :         MFEM_VERIFY(skip_type || elem_nodes.find(elem_type) == elem_nodes.end(),
     847              :                     "Duplicate element types found in COMSOL mesh file!");
     848            0 :         input.read(reinterpret_cast<char *>(&num_elem_nodes), sizeof(int));
     849            0 :         MFEM_VERIFY(num_elem_nodes > 0,
     850              :                     "COMSOL element type " << elem_type << " has no nodes!");
     851            0 :         MFEM_VERIFY(skip_type || num_elem_nodes == ElemNumNodes[elem_type - 1],
     852              :                     "Mismatch between COMSOL and Gmsh element types!");
     853              : 
     854              :         // Parse all element nodes.
     855            0 :         input.read(reinterpret_cast<char *>(&num_elem), sizeof(int));
     856            0 :         MFEM_VERIFY(num_elem > 0,
     857              :                     "COMSOL mesh file has no elements of type " << elem_type << "!");
     858              :         std::vector<int> *data = nullptr;
     859            0 :         if (!skip_type)
     860              :         {
     861              :           data = &elem_nodes[elem_type];
     862            0 :           data->resize(num_elem * (num_elem_nodes + 1));  // Node tags + geometry tag
     863              :         }
     864              :         int i = 0;
     865            0 :         std::vector<int> nodes(num_elem_nodes);
     866            0 :         while (i < num_elem)
     867              :         {
     868            0 :           input.read(reinterpret_cast<char *>(nodes.data()),
     869            0 :                      (std::streamsize)(num_elem_nodes * sizeof(int)));
     870            0 :           if (!skip_type)
     871              :           {
     872            0 :             for (int j = 0; j < num_elem_nodes; j++)
     873              :             {
     874              :               // Permute and reset to 1-based node tags.
     875            0 :               const int &p = ElemNodesComsol[elem_type - 1][j];
     876            0 :               (*data)[i * (num_elem_nodes + 1) + 1 + p] = nodes[j] + (1 - nodes_start);
     877              :             }
     878              :           }
     879            0 :           i++;
     880              :         }
     881              : 
     882              :         // Parse element geometry tags.
     883            0 :         input.read(reinterpret_cast<char *>(&num_elem_geom), sizeof(int));
     884            0 :         MFEM_VERIFY(num_elem_geom == num_elem,
     885              :                     "COMSOL mesh file should have geometry tags for all elements!");
     886              : 
     887              :         i = 0;
     888            0 :         const int geom_start = (elem_type < 4 || (elem_type > 7 && elem_type < 11)) ? 1 : 0;
     889              :         int geom_tag;
     890            0 :         while (i < num_elem)
     891              :         {
     892            0 :           input.read(reinterpret_cast<char *>(&geom_tag), sizeof(int));
     893            0 :           if (!skip_type)
     894              :           {
     895            0 :             (*data)[i * (num_elem_nodes + 1)] = geom_tag + geom_start;
     896              :           }
     897            0 :           i++;
     898              :         }
     899              : 
     900              :         // Debug
     901              :         // std::cout << "Finished parsing " << num_elem
     902              :         //           << " elements with type " << elem_type
     903              :         //           << " (parsed types " << parsed_types + 1 << ")\n";
     904              : 
     905              :         // Finished with this element type, on to the next.
     906            0 :         parsed_types++;
     907            0 :         elem_type = num_elem_nodes = num_elem = num_elem_geom = -1;
     908              :         skip_type = false;
     909              :       }
     910              :     }
     911              :   }
     912              : 
     913              :   // Finalize input, write the Gmsh mesh.
     914            0 :   input.close();
     915              :   std::vector<int> dummy;
     916            0 :   WriteGmsh(buffer, node_coords, dummy, elem_nodes, remove_curvature);
     917            0 : }
     918              : 
     919            0 : void ConvertMeshNastran(const std::string &filename, std::ostream &buffer,
     920              :                         bool remove_curvature)
     921              : {
     922              :   // Read a Nastran/BDF format mesh.
     923            0 :   MFEM_VERIFY(!filename.compare(filename.length() - 4, 4, ".nas") ||
     924              :                   !filename.compare(filename.length() - 4, 4, ".NAS") ||
     925              :                   !filename.compare(filename.length() - 4, 4, ".bdf") ||
     926              :                   !filename.compare(filename.length() - 4, 4, ".BDF"),
     927              :               "Invalid file extension for Nastran mesh format conversion!");
     928            0 :   std::ifstream input(filename);
     929            0 :   if (!input.is_open())
     930              :   {
     931            0 :     MFEM_ABORT("Unable to open mesh file \"" << filename << "\"!");
     932              :   }
     933              :   const int NASTRAN_CHUNK = 8;  // NASTRAN divides row into 10 columns of 8 spaces
     934              :   const int MAX_CHUNK = 9;      // Never read the 10-th chunk
     935              : 
     936              :   // Parse until bulk data starts.
     937              :   while (true)
     938              :   {
     939            0 :     auto line = GetLineNastran(input);
     940            0 :     if (line.length() > 0)
     941              :     {
     942            0 :       if (!line.compare(0, 10, "BEGIN BULK"))
     943              :       {
     944              :         break;
     945              :       }
     946              :     }
     947              :   }
     948              : 
     949              :   // Parse mesh nodes and elements. It is expected that node tags start at 1 and are
     950              :   // contiguous. Store for each element of each type: [geometry tag, [node tags]].
     951              :   std::vector<double> node_coords;
     952              :   std::vector<int> node_tags;
     953              :   std::unordered_map<int, std::vector<int>> elem_nodes;
     954              :   int elem_type;
     955              :   while (true)
     956              :   {
     957            0 :     auto line = GetLineNastran(input);
     958            0 :     if (line.length() > 0 && !input.eof())
     959              :     {
     960            0 :       if (!line.compare(0, 7, "ENDDATA"))
     961              :       {
     962              :         break;  // Done parsing file
     963              :       }
     964            0 :       else if (!line.compare(0, 5, "GRID*"))
     965              :       {
     966              :         // Coordinates in long field format (8 + 16 * 4 + 8).
     967            0 :         auto next = GetLineNastran(input);
     968            0 :         MFEM_VERIFY(!next.empty(), "Unexpected empty line parsing Nastran!");
     969              : 
     970            0 :         node_tags.push_back(std::stoi(line.substr(1 * NASTRAN_CHUNK, 2 * NASTRAN_CHUNK)));
     971            0 :         node_coords.insert(
     972              :             node_coords.end(),
     973            0 :             {ConvertDoubleNastran(line.substr(5 * NASTRAN_CHUNK, 2 * NASTRAN_CHUNK)),
     974            0 :              ConvertDoubleNastran(line.substr(7 * NASTRAN_CHUNK, 2 * NASTRAN_CHUNK)),
     975            0 :              ConvertDoubleNastran(next.substr(1 * NASTRAN_CHUNK, 2 * NASTRAN_CHUNK))});
     976              :       }
     977            0 :       else if (!line.compare(0, 4, "GRID"))
     978              :       {
     979            0 :         if (line.find_first_of(',') != std::string::npos)
     980              :         {
     981              :           // Free field format (comma separated).
     982            0 :           std::istringstream sline(line);
     983              : 
     984              :           std::string word;
     985            0 :           std::getline(sline, word, ',');  // Discard "GRID"
     986              : 
     987            0 :           std::getline(sline, word, ',');
     988            0 :           node_tags.push_back(std::stoi(word));
     989              : 
     990            0 :           std::getline(sline, word, ',');  // Discard coordinate system
     991              : 
     992            0 :           std::getline(sline, word, ',');
     993            0 :           double x = ConvertDoubleNastran(word);
     994            0 :           std::getline(sline, word, ',');
     995            0 :           double y = ConvertDoubleNastran(word);
     996            0 :           std::getline(sline, word, ',');
     997            0 :           double z = ConvertDoubleNastran(word);
     998            0 :           node_coords.insert(node_coords.end(), {x, y, z});
     999            0 :         }
    1000              :         else
    1001              :         {
    1002              :           // Short format (10 * 8).
    1003            0 :           node_tags.push_back(std::stoi(line.substr(1 * NASTRAN_CHUNK, NASTRAN_CHUNK)));
    1004            0 :           node_coords.insert(
    1005              :               node_coords.end(),
    1006            0 :               {ConvertDoubleNastran(line.substr(3 * NASTRAN_CHUNK, NASTRAN_CHUNK)),
    1007            0 :                ConvertDoubleNastran(line.substr(4 * NASTRAN_CHUNK, NASTRAN_CHUNK)),
    1008            0 :                ConvertDoubleNastran(line.substr(5 * NASTRAN_CHUNK, NASTRAN_CHUNK))});
    1009              :         }
    1010              :       }
    1011            0 :       else if ((elem_type = ElemTypeNastran(line)))
    1012              :       {
    1013              :         // Prepare to parse the element ID and nodes.
    1014              :         const bool free = (line.find_first_of(',') != std::string::npos);
    1015              : 
    1016              :         // Get the element type, tag, and geometry attribute. Then get the element nodes on
    1017              :         // this line.
    1018              :         std::string elem_str;
    1019              :         // int elem_tag;
    1020              :         int geom_tag;
    1021              :         std::vector<int> nodes;
    1022              :         std::string word;
    1023            0 :         if (!free)
    1024              :         {
    1025            0 :           elem_str = line.substr(0 * NASTRAN_CHUNK, NASTRAN_CHUNK);
    1026            0 :           const std::size_t stop = elem_str.find_last_not_of(' ');
    1027            0 :           MFEM_VERIFY(stop != std::string::npos, "Invalid element type string!");
    1028            0 :           elem_str.resize(stop + 1);
    1029              :           // elem_tag = std::stoi(line.substr(1*NASTRAN_CHUNK, NASTRAN_CHUNK));
    1030            0 :           geom_tag = std::stoi(line.substr(2 * NASTRAN_CHUNK, NASTRAN_CHUNK));
    1031              : 
    1032              :           int i = 3;
    1033            0 :           while (i < MAX_CHUNK)
    1034              :           {
    1035            0 :             word = line.substr((i++) * NASTRAN_CHUNK, NASTRAN_CHUNK);
    1036            0 :             if (word.find_first_not_of(' ') == std::string::npos)
    1037              :             {
    1038              :               break;
    1039              :             }
    1040            0 :             nodes.push_back(std::stoi(word));
    1041              :           }
    1042              :         }
    1043              :         else
    1044              :         {
    1045            0 :           std::istringstream sline(line);
    1046            0 :           std::getline(sline, elem_str, ',');
    1047            0 :           std::getline(sline, word, ',');
    1048              :           // elem_tag = std::stoi(word);
    1049            0 :           std::getline(sline, word, ',');
    1050              :           geom_tag = std::stoi(word);
    1051              : 
    1052              :           int i = 3;
    1053            0 :           while (i < MAX_CHUNK)
    1054              :           {
    1055            0 :             std::getline(sline, word, ',');
    1056            0 :             if (word.find_first_not_of(' ') == std::string::npos)
    1057              :             {
    1058              :               break;
    1059              :             }
    1060            0 :             nodes.push_back(std::stoi(word));
    1061            0 :             i++;
    1062              :           }
    1063            0 :         }
    1064              : 
    1065              :         // Handle line continuation.
    1066            0 :         while (input.peek() == '+')
    1067              :         {
    1068            0 :           auto next = GetLineNastran(input);
    1069            0 :           MFEM_VERIFY(!next.empty(), "Unexpected empty line parsing Nastran!");
    1070              : 
    1071            0 :           if (!free)
    1072              :           {
    1073              :             int i = 1;
    1074            0 :             while (i < MAX_CHUNK)
    1075              :             {
    1076            0 :               word = next.substr((i++) * NASTRAN_CHUNK, NASTRAN_CHUNK);
    1077            0 :               if (word.find_first_not_of(' ') == std::string::npos)
    1078              :               {
    1079              :                 break;
    1080              :               }
    1081            0 :               nodes.push_back(std::stoi(word));
    1082              :             }
    1083              :           }
    1084              :           else
    1085              :           {
    1086            0 :             std::istringstream snext(next);
    1087              :             int i = 1;
    1088            0 :             while (i < MAX_CHUNK)
    1089              :             {
    1090            0 :               std::getline(snext, word, ',');
    1091            0 :               if (word.find_first_not_of(' ') == std::string::npos)
    1092              :               {
    1093              :                 break;
    1094              :               }
    1095            0 :               nodes.push_back(std::stoi(word));
    1096            0 :               i++;
    1097              :             }
    1098            0 :           }
    1099              :         }
    1100              : 
    1101              :         // Save the element and its geometry tag.
    1102            0 :         elem_type = HOElemTypeNastran(elem_type, (int)nodes.size());
    1103            0 :         const int &num_elem_nodes = ElemNumNodes[elem_type - 1];
    1104            0 :         MFEM_VERIFY((std::size_t)num_elem_nodes == nodes.size(),
    1105              :                     "Mismatch between Nastran and Gmsh element types!");
    1106              :         std::vector<int> &data = elem_nodes[elem_type];
    1107            0 :         const int i = (int)data.size();
    1108            0 :         data.resize(i + 1 + num_elem_nodes);
    1109            0 :         data[i] = geom_tag;
    1110            0 :         for (int j = 0; j < num_elem_nodes; j++)
    1111              :         {
    1112              :           // Permute back to Gmsh ordering.
    1113            0 :           const int &p = ElemNodesNastran[elem_type - 1][j];
    1114            0 :           data[i + 1 + p] = nodes[j];
    1115              :         }
    1116              :       }
    1117              :     }
    1118              :   }
    1119              : 
    1120              :   // Finalize input, write the Gmsh mesh.
    1121            0 :   input.close();
    1122            0 :   WriteGmsh(buffer, node_coords, node_tags, elem_nodes, remove_curvature);
    1123            0 : }
    1124              : 
    1125              : }  // namespace mesh
    1126              : 
    1127              : }  // namespace palace
        

Generated by: LCOV version 2.0-1