LCOV - code coverage report
Current view: top level - src/mesh - mesh_netgen_interface.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4513 (c53953) with base e77e8c Lines: 205 219 93.6 %
Date: 2026-08-07 20:41:44 Functions: 13 13 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2023 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : #include "libmesh/libmesh_config.h"
      19             : #ifdef LIBMESH_HAVE_NETGEN
      20             : 
      21             : 
      22             : // C++ includes
      23             : #include <sstream>
      24             : 
      25             : // Local includes
      26             : #include "libmesh/mesh_netgen_interface.h"
      27             : 
      28             : #include "libmesh/boundary_info.h"
      29             : #include "libmesh/cell_tet4.h"
      30             : #include "libmesh/cell_tet10.h"
      31             : #include "libmesh/elem.h"
      32             : #include "libmesh/face_tri3.h"
      33             : #include "libmesh/face_tri6.h"
      34             : #include "libmesh/libmesh_logging.h"
      35             : #include "libmesh/mesh_communication.h"
      36             : #include "libmesh/threads.h"
      37             : #include "libmesh/unstructured_mesh.h"
      38             : #include "libmesh/utility.h" // libmesh_map_find
      39             : 
      40             : namespace nglib {
      41             : #include "netgen/nglib/nglib.h"
      42             : }
      43             : 
      44             : namespace {
      45             : 
      46             : // RAII for exception safety
      47             : class WrappedNgMesh
      48             : {
      49             : public:
      50         120 :   WrappedNgMesh() {
      51         120 :     _ngmesh = nglib::Ng_NewMesh();
      52          10 :   }
      53             : 
      54          30 :   ~WrappedNgMesh() {
      55         120 :     nglib::Ng_DeleteMesh(_ngmesh);
      56         110 :   }
      57             : 
      58             :   void clear() {
      59             :     nglib::Ng_DeleteMesh(_ngmesh);
      60             :     _ngmesh = nglib::Ng_NewMesh();
      61             :   }
      62             : 
      63        1396 :   operator nglib::Ng_Mesh* () {
      64       15362 :     return _ngmesh;
      65             :   }
      66             : 
      67             : private:
      68             :   nglib::Ng_Mesh * _ngmesh;
      69             : };
      70             : 
      71             : }
      72             : 
      73             : namespace libMesh
      74             : {
      75             : 
      76             : //----------------------------------------------------------------------
      77             : // NetGenMeshInterface class members
      78         781 : NetGenMeshInterface::NetGenMeshInterface (UnstructuredMesh & mesh) :
      79             :   MeshTetInterface(mesh),
      80         781 :   _serializer(mesh)
      81             : {
      82         781 : }
      83             : 
      84             : 
      85             : 
      86         781 : void NetGenMeshInterface::triangulate ()
      87             : {
      88             :   using namespace nglib;
      89             : 
      90          24 :   LOG_SCOPE("triangulate()", "NetGenMeshInterface");
      91             : 
      92         803 :   if (_elem_type != TET4 &&
      93         767 :       _elem_type != TET10 &&
      94           4 :       _elem_type != TET14)
      95           0 :     libmesh_not_implemented();
      96             : 
      97             :   // We're hoping to do volume_to_surface_mesh in parallel at least,
      98             :   // but then we'll need to serialize any hole meshes to rank 0 so it
      99             :   // can use them in serial.
     100             : 
     101             :   // If the user wants higher-order output, record midpoints from any
     102             :   // quadratic boundary elements before stripping them to TRI3 for
     103             :   // NetGen.  We key by sorted position pairs (not node IDs) so that
     104             :   // outer mesh and hole mesh node namespaces cannot conflict.
     105          24 :   std::map<std::pair<Point,Point>, Point> edge_midpoints;
     106             : 
     107             :   // TRI7 boundary faces additionally carry a face-centroid node (local index
     108             :   // 6) that TET14 output reproduces (its faces are TRI7).  Record those too,
     109             :   // keyed by the sorted triple of the face's three corner positions so the
     110             :   // key is independent of node id and rotation.
     111          24 :   std::map<std::array<Point,3>, Point> face_centroids;
     112             : 
     113             :   auto record_and_strip =
     114        3992 :     [&edge_midpoints, &face_centroids](UnstructuredMesh & m)
     115             :     {
     116           8 :       bool has_quadratic = false;
     117        4976 :       for (const auto & elem : m.element_ptr_range())
     118             :         {
     119        2272 :           if (elem->type() != TRI6 && elem->type() != TRI7) continue;
     120          32 :           has_quadratic = true;
     121             :           // TRI6/TRI7: edge e runs node[e]→node[(e+1)%3], midpoint=node[e+3]
     122        4544 :           for (auto e : make_range(3u))
     123             :             {
     124        3408 :               const Point & pa = elem->point(e);
     125        3408 :               const Point & pb = elem->point((e+1)%3);
     126        3408 :               auto key = pa < pb
     127        3408 :                 ? std::make_pair(pa,pb) : std::make_pair(pb,pa);
     128        3504 :               edge_midpoints[key] = elem->point(e+3);
     129             :             }
     130             :           // TRI7: node 6 is the face-centroid node.
     131        1136 :           if (elem->type() == TRI7)
     132             :             {
     133             :               std::array<Point,3> corners =
     134         616 :                 {elem->point(0), elem->point(1), elem->point(2)};
     135          16 :               std::sort(corners.begin(), corners.end());
     136         584 :               face_centroids[corners] = elem->point(6);
     137             :             }
     138         268 :         }
     139         284 :       if (has_quadratic)
     140         142 :         m.all_first_order();
     141         306 :     };
     142             : 
     143             :   const BoundingBox mesh_bb =
     144         781 :     MeshTetInterface::volume_to_surface_mesh(this->_mesh);
     145             : 
     146         710 :   if (_elem_type != TET4)
     147         284 :     record_and_strip(this->_mesh);
     148             : 
     149          30 :   std::vector<MeshSerializer> hole_serializers;
     150         690 :   if (_holes)
     151         284 :     for (std::unique_ptr<UnstructuredMesh> & hole : *_holes)
     152             :       {
     153             :         const BoundingBox hole_bb =
     154         142 :           MeshTetInterface::volume_to_surface_mesh(*hole);
     155             : 
     156         142 :         if (_elem_type != TET4)
     157           0 :           record_and_strip(*hole);
     158             : 
     159         142 :         libmesh_error_msg_if
     160             :           (!mesh_bb.contains(hole_bb),
     161             :            "Found hole with bounding box " << hole_bb <<
     162             :            "\nextending outside of mesh bounding box " << mesh_bb);
     163             : 
     164             :         hole_serializers.emplace_back
     165         276 :           (*hole, /* need_serial */ true,
     166         146 :            /* serial_only_needed_on_proc_0 */ true);
     167             :       }
     168             : 
     169             :   // Increasing the element order (all_second_order()/all_complete_order()
     170             :   // via increase_tet_order()) performs collective MPI communication, so it
     171             :   // must run on every rank in lockstep -- it cannot run on rank 0 alone
     172             :   // while the other ranks wait in broadcast().  We therefore broadcast
     173             :   // NetGen's TET4 result first, then increase the order and restore the
     174             :   // curved-boundary midpoints identically on all ranks.  This mirrors what
     175             :   // the 2D interfaces do (see TriangulatorInterface::increase_triangle_order,
     176             :   // which is likewise called on all ranks).  edge_midpoints was built while
     177             :   // the mesh was serialized on every rank, so it is identical everywhere and
     178             :   // this post-broadcast fixup is deterministic.
     179             :   auto increase_order_and_restore_midpoints =
     180        4448 :     [this, &edge_midpoints, &face_centroids]()
     181             :     {
     182         710 :       if (_elem_type == TET4)
     183          12 :         return;
     184             : 
     185             :       // find_neighbors() is needed before all_second_order() can place
     186             :       // shared edge midpoints correctly.
     187         284 :       this->_mesh.find_neighbors();
     188             : 
     189             :       // Refresh the cached element dimensions.  We just replaced the 2D
     190             :       // TRI3 surface elements with 3D TET4 volume elements, but the mesh's
     191             :       // cached dimension is still that of the original surface (2).
     192             :       // all_second_order()/all_complete_order() derive the per-element
     193             :       // unique_id reservation width from mesh_dimension(): a stale value of
     194             :       // 2 reserves only 9-4=5 slots per element, too few for the 6 new edge
     195             :       // nodes of a TET10, so adjacent elements' unique_id ranges overlap and
     196             :       // collide.  cache_elem_data() recomputes the dimension to 3 (reserving
     197             :       // 27-8=19 slots) before the order increase runs.
     198         284 :       this->_mesh.cache_elem_data();
     199             : 
     200         284 :       this->increase_tet_order();
     201             : 
     202             :       // Move auto-placed geometric midpoints to the recorded positions,
     203             :       // preserving any curvature from the original quadratic boundary.
     204         284 :       if (!edge_midpoints.empty() || !face_centroids.empty())
     205        1936 :         for (Elem * elem : _mesh.element_ptr_range())
     206        2840 :           for (auto s : elem->side_index_range())
     207             :             {
     208        2336 :               if (elem->neighbor_ptr(s)) continue;
     209             : 
     210             :               // build_side_ptr() returns a TRI6 (TET10) or TRI7 (TET14)
     211             :               // whose node pointers reference the actual mesh nodes; point()
     212             :               // assignments update mesh node coordinates in place.
     213        1168 :               auto side = elem->build_side_ptr(s);
     214        4544 :               for (auto e : make_range(3u))
     215             :                 {
     216         192 :                   const Point & pa = side->point(e);
     217        3408 :                   const Point & pb = side->point((e+1)%3);
     218        3408 :                   auto key = pa < pb
     219        3408 :                     ? std::make_pair(pa,pb) : std::make_pair(pb,pa);
     220        3408 :                   if (auto it = edge_midpoints.find(key);
     221          96 :                       it != edge_midpoints.end())
     222        3504 :                     side->point(e+3) = it->second;
     223             :                 }
     224             : 
     225             :               // TRI7 faces (TET14 output) also carry a face-centroid node
     226             :               // at local index 6; restore its recorded curved position.
     227        1136 :               if (side->type() == TRI7)
     228             :                 {
     229             :                   std::array<Point,3> corners =
     230         616 :                     {side->point(0), side->point(1), side->point(2)};
     231          16 :                   std::sort(corners.begin(), corners.end());
     232         568 :                   if (auto it = face_centroids.find(corners);
     233          16 :                       it != face_centroids.end())
     234         584 :                     side->point(6) = it->second;
     235             :                 }
     236        1206 :             }
     237         710 :     };
     238             : 
     239             :   // This should probably only be done on rank 0, but the API is
     240             :   // designed with the hope that we'll parallelize it eventually
     241         710 :   auto integrity = this->improve_hull_integrity();
     242         710 :   this->process_hull_integrity_result(integrity);
     243             : 
     244             :   // If we're not rank 0, we're just going to wait for rank 0 to call
     245             :   // Netgen, then receive its data afterward, we're not going to hope
     246             :   // that Netgen does the exact same thing on every processor.
     247         730 :   if (this->_mesh.processor_id() != 0)
     248             :     {
     249             :       // We don't need our holes anymore.  Delete their serializers
     250             :       // first to avoid dereferencing dangling pointers.
     251          10 :       hole_serializers.clear();
     252         590 :       if (_holes)
     253           2 :         _holes->clear();
     254             : 
     255             :       // Receive the TET4 mesh data rank 0 will send later.
     256         590 :       MeshCommunication().broadcast(this->_mesh);
     257             : 
     258             :       // If we got an empty mesh here then our tetrahedralization
     259             :       // failed.
     260         590 :       libmesh_error_msg_if (!this->_mesh.n_elem(),
     261             :                             "NetGen failed to generate any tetrahedra");
     262             : 
     263             :       // Increase the element order collectively, in lockstep with rank 0.
     264         590 :       increase_order_and_restore_midpoints();
     265             : 
     266         590 :       this->_mesh.prepare_for_use();
     267          10 :       return;
     268             :     }
     269             : 
     270         120 :   Ng_Meshing_Parameters params;
     271             : 
     272         120 :   Ng_SetNumThreads(cast_int<int>(libMesh::n_threads()));
     273             : 
     274             :   // Override any default parameters we might need to, to avoid
     275             :   // inserting nodes we don't want.
     276         120 :   params.uselocalh = false;
     277         120 :   params.minh = 0;
     278         120 :   params.elementsperedge = 1;
     279         120 :   params.elementspercurve = 1;
     280         120 :   params.closeedgeenable = false;
     281         120 :   params.closeedgefact = 0;
     282         120 :   params.minedgelenenable = false;
     283         120 :   params.minedgelen = 0;
     284             : 
     285             :   // Try to get a no-extra-nodes mesh if we're asked to, or try to
     286             :   // translate our desired volume into NetGen terms otherwise.
     287             :   //
     288             :   // Spoiler alert: all we can do is try; NetGen uses a marching front
     289             :   // algorithm that can insert extra nodes despite all my best
     290             :   // efforts.
     291         120 :   if (_desired_volume == 0) // shorthand for "no refinement"
     292             :     {
     293         108 :       params.maxh = std::numeric_limits<double>::max();
     294         108 :       params.fineness = 0; // "coarse" in the docs
     295         108 :       params.grading = 1;  // "aggressive local grading" to avoid smoothing??
     296             : 
     297             :       // Turning off optimization steps avoids another opportunity for
     298             :       // Netgen to try to add more nodes.
     299         108 :       params.optsteps_3d = 0;
     300             :     }
     301             :   else
     302          12 :     params.maxh = double(std::pow(_desired_volume, 1./3.));
     303             : 
     304             :   // Keep track of how NetGen copies of nodes map back to our original
     305             :   // nodes, so we can connect new elements to nodes correctly.
     306          20 :   std::unordered_map<int, dof_id_type> ng_to_libmesh_id;
     307             : 
     308         120 :   auto handle_ng_result = [](Ng_Result result) {
     309             :     static const std::vector<std::string> result_types =
     310             :       {"Netgen error", "Netgen success", "Netgen surface input error",
     311             :        "Netgen volume failure", "Netgen STL input error",
     312         214 :        "Netgen surface failure", "Netgen file not found"};
     313             : 
     314         130 :     if (result+1 >= 0 &&
     315         120 :         std::size_t(result+1) < result_types.size())
     316         120 :       libmesh_error_msg_if
     317             :         (result, "Ng_GenerateVolumeMesh failed: " <<
     318             :          result_types[result+1]);
     319             :     else
     320           0 :       libmesh_error_msg
     321             :         ("Ng_GenerateVolumeMesh failed with an unknown error code");
     322         140 :   };
     323             : 
     324             :   // Keep track of what boundary ids we want to assign to each new
     325             :   // triangle.  We'll give the outer boundary BC 0, and give holes ids
     326             :   // starting from 1.
     327             :   // We key on sorted tuples of node ids to identify a side.
     328             :   std::unordered_map<std::array<dof_id_type,3>,
     329          20 :                      boundary_id_type, libMesh::hash> side_boundary_id;
     330             : 
     331       90900 :   auto insert_id = []
     332             :     (std::array<dof_id_type,3> & array,
     333        9084 :      dof_id_type n_id)
     334             :   {
     335        9084 :     libmesh_assert_less(n_id, DofObject::invalid_id);
     336        9084 :     unsigned int i=0;
     337      163762 :     while (array[i] < n_id)
     338       54694 :       ++i;
     339      381578 :     while (i < 3)
     340      272510 :       std::swap(array[i++], n_id);
     341       99984 :   };
     342             : 
     343          20 :   WrappedNgMesh ngmesh;
     344             : 
     345             :   // Create surface mesh in the WrappedNgMesh
     346             :   {
     347             :     // NetGen appears to use ONE-BASED numbering for its nodes, and
     348             :     // since it doesn't return an id when adding nodes we'll have to
     349             :     // track the numbering ourselves.
     350         120 :     int ng_id = 1;
     351             : 
     352             :     auto create_surface_component =
     353         120 :       [this, &ng_id, &ng_to_libmesh_id, &ngmesh, &side_boundary_id, &insert_id]
     354             :       (UnstructuredMesh & srcmesh, bool hole_mesh,
     355        9114 :        boundary_id_type bcid)
     356             :     {
     357          24 :       LOG_SCOPE("create_surface_component()", "NetGenMeshInterface");
     358             : 
     359             :       // Keep track of what nodes we've already added to the Netgen
     360             :       // mesh vs what nodes we need to add.  We'll keep track by id,
     361             :       // not by point location.  I don't know if Netgen can handle
     362             :       // multiple nodes with the same point location, but if they can
     363             :       // it's not going to be *us* who breaks that feature.
     364          24 :       std::unordered_map<dof_id_type, int> libmesh_to_ng_id;
     365             : 
     366             :       // Keep track of what nodes we've already added to the main
     367             :       // mesh from a hole mesh.
     368          24 :       std::unordered_map<dof_id_type, dof_id_type> hole_to_main_mesh_id;
     369             : 
     370             :       // Use a separate array for passing points to NetGen, just in case
     371             :       // we're not using double-precision ourselves.
     372             :       std::array<double, 3> point_val;
     373             : 
     374             :       // And an array for element vertices
     375             :       std::array<int, 3> elem_nodes;
     376             : 
     377       10572 :       for (const auto * elem : srcmesh.element_ptr_range())
     378             :         {
     379             :           // If someone has triangles we can't triangulate, we have a
     380             :           // problem
     381       11232 :           if (elem->type() == TRI6 ||
     382        5616 :               elem->type() == TRI7)
     383           0 :             libmesh_not_implemented_msg
     384             :               ("Netgen tetrahedralization currently only supports TRI3 boundaries");
     385             : 
     386             :           // If someone has non-triangles, let's just ignore them.
     387        5616 :           if (elem->type() != TRI3)
     388           0 :             continue;
     389             : 
     390        5616 :           std::array<dof_id_type,3> sorted_ids =
     391             :             {DofObject::invalid_id, DofObject::invalid_id,
     392             :              DofObject::invalid_id};
     393             : 
     394       22464 :           for (int ni : make_range(3))
     395             :             {
     396             :               // Just using the "invert_trigs" option in NetGen params
     397             :               // doesn't work for me, so we'll have to have properly
     398             :               // oriented the tris earlier.
     399       16848 :               auto & elem_node = hole_mesh ? elem_nodes[2-ni] : elem_nodes[ni];
     400             : 
     401        2808 :               const Node & n = elem->node_ref(ni);
     402       16848 :               auto n_id = n.id();
     403       16848 :               if (hole_mesh)
     404             :                 {
     405        7200 :                   if (auto it = hole_to_main_mesh_id.find(n_id);
     406         600 :                       it != hole_to_main_mesh_id.end())
     407             :                     {
     408        5952 :                       n_id = it->second;
     409             :                     }
     410             :                   else
     411             :                     {
     412        1248 :                       Node * n_new = this->_mesh.add_point(n);
     413        1248 :                       const dof_id_type n_new_id = n_new->id();
     414         104 :                       hole_to_main_mesh_id.emplace(n_id, n_new_id);
     415        1248 :                       n_id = n_new_id;
     416             :                     }
     417             :                 }
     418             : 
     419       16848 :               if (auto it = libmesh_to_ng_id.find(n_id);
     420        1404 :                   it != libmesh_to_ng_id.end())
     421             :                 {
     422       13752 :                   const int existing_ng_id = it->second;
     423       13752 :                   elem_node = existing_ng_id;
     424             :                 }
     425             :               else
     426             :                 {
     427       12384 :                   for (auto i : make_range(3))
     428        9288 :                     point_val[i] = double(n(i));
     429             : 
     430        3096 :                   Ng_AddPoint(ngmesh, point_val.data());
     431             : 
     432        3096 :                   ng_to_libmesh_id[ng_id] = n_id;
     433        3096 :                   libmesh_to_ng_id[n_id] = ng_id;
     434        3096 :                   elem_node = ng_id;
     435        3096 :                   ++ng_id;
     436             :                 }
     437             : 
     438       16848 :               insert_id(sorted_ids, n_id);
     439             :             }
     440             : 
     441        5616 :           side_boundary_id[sorted_ids] = bcid;
     442             : 
     443        5616 :           Ng_AddSurfaceElement(ngmesh, NG_TRIG, elem_nodes.data());
     444         120 :         }
     445         144 :     };
     446             : 
     447             :     // Number the outer boundary 0, and the holes starting from 1
     448          10 :     boundary_id_type bcid = 0;
     449             : 
     450         120 :     create_surface_component(this->_mesh, false, bcid);
     451             : 
     452         120 :     if (_holes)
     453          48 :       for (const std::unique_ptr<UnstructuredMesh> & h : *_holes)
     454          26 :         create_surface_component(*h, true, ++bcid);
     455             :   }
     456             : 
     457             :   {
     458          20 :     LOG_SCOPE("Ng_GenerateVolumeMesh()", "NetGenMeshInterface");
     459             : 
     460         120 :     auto result = Ng_GenerateVolumeMesh(ngmesh, &params);
     461         120 :     handle_ng_result(result);
     462             :   }
     463             : 
     464         120 :   const int n_elem = Ng_GetNE(ngmesh);
     465             : 
     466             :   // If Netgen fails us, we're likely to get n_elem <= 0.  This is a
     467             :   // common enough failure from bad setups that I want to make sure
     468             :   // it's thrown in parallel so as to not desynchronize any unit tests
     469             :   // that trigger it.  So we'll broadcast the empty mesh to indicate
     470             :   // the problem and enable throwing exceptions in parallel.
     471         120 :   if (n_elem <= 0)
     472             :     {
     473           0 :       this->_mesh.clear();
     474           0 :       MeshCommunication().broadcast(this->_mesh);
     475           0 :       libmesh_error_msg ("NetGen failed to generate any tetrahedra");
     476             :     }
     477             : 
     478         120 :   const dof_id_type n_points = Ng_GetNP(ngmesh);
     479         120 :   const dof_id_type old_nodes = this->_mesh.n_nodes();
     480             : 
     481             :   // Netgen may have generated new interior nodes
     482         120 :   if (n_points != old_nodes)
     483             :     {
     484             :       std::array<double, 3> point_val;
     485             : 
     486             :       // We should only be getting new nodes if we asked for them
     487           1 :       if (!_desired_volume)
     488             :         {
     489             :           std::cout <<
     490           0 :             "NetGen output " << n_points <<
     491           0 :             " points when we gave it " <<
     492           0 :             old_nodes << " and disabled refinement\n" <<
     493             :             "If new interior points are acceptable in your mesh, please set\n" <<
     494             :             "a non-zero desired_volume to indicate that.  If new interior\n" <<
     495             :             "points are not acceptable in your mesh, you may need a different\n" <<
     496           0 :             "(non-advancing-front?) mesh generator." << std::endl;
     497           0 :           libmesh_error();
     498             :         }
     499             :       else
     500           2 :         for (auto i : make_range(old_nodes, n_points))
     501             :           {
     502             :             // i+1 since ng uses ONE-BASED numbering
     503           1 :             Ng_GetPoint (ngmesh, i+1, point_val.data());
     504           1 :             const Point p(point_val[0], point_val[1], point_val[2]);
     505           1 :             Node * n_new = this->_mesh.add_point(p);
     506           0 :             const dof_id_type n_new_id = n_new->id();
     507           1 :             ng_to_libmesh_id[i+1] = n_new_id;
     508             :           }
     509             :     }
     510             : 
     511        6126 :   for (auto * elem : this->_mesh.element_ptr_range())
     512        3316 :     this->_mesh.delete_elem(elem);
     513             : 
     514         120 :   BoundaryInfo * bi = & this->_mesh.get_boundary_info();
     515             : 
     516        7805 :   for (auto i : make_range(n_elem))
     517             :   {
     518             :     // Enough data to return even a Tet10 without a segfault if nglib
     519             :     // went nuts
     520             :     int ngnodes[11];
     521             : 
     522             :     // i+1 since we must be 1-based with these ids too...
     523             :     Ng_Volume_Element_Type ngtype =
     524        7685 :       Ng_GetVolumeElement(ngmesh, i+1, ngnodes);
     525             : 
     526             :     // But really nglib shouldn't go nuts
     527         640 :     libmesh_assert(ngtype == NG_TET);
     528         640 :     libmesh_ignore(ngtype);
     529             : 
     530        8325 :     auto elem = this->_mesh.add_elem(Elem::build_with_id(TET4, i));
     531       38425 :     for (auto n : make_range(4))
     532             :       {
     533             :         const dof_id_type node_id =
     534       30740 :           libmesh_map_find(ng_to_libmesh_id, ngnodes[n]);
     535       30740 :         elem->set_node(n, this->_mesh.node_ptr(node_id));
     536             :       }
     537             : 
     538             :     // NetGen and we disagree about node numbering orientation
     539        7045 :     elem->orient(bi);
     540             : 
     541       38425 :     for (auto s : make_range(4))
     542             :       {
     543       30740 :         std::array<dof_id_type,3> sorted_ids =
     544             :           {DofObject::invalid_id, DofObject::invalid_id,
     545             :            DofObject::invalid_id};
     546             : 
     547       33300 :         std::vector<unsigned int> nos = elem->nodes_on_side(s);
     548      122960 :         for (auto n : nos)
     549       92220 :           insert_id(sorted_ids, elem->node_id(n));
     550             : 
     551       30740 :         if (auto it = side_boundary_id.find(sorted_ids);
     552        2560 :             it != side_boundary_id.end())
     553        5616 :           bi->add_side(elem, s, it->second);
     554             :       }
     555             :   }
     556             : 
     557             :   // We don't need our holes anymore.  Delete their serializers
     558             :   // first to avoid dereferencing dangling pointers.
     559          10 :   hole_serializers.clear();
     560         120 :   if (_holes)
     561           2 :     _holes->clear();
     562             : 
     563             :   // Send NetGen's TET4 result to the other ranks, then increase the element
     564             :   // order collectively on all ranks together.  increase_tet_order() performs
     565             :   // collective communication, so it must not run on rank 0 alone -- doing so
     566             :   // would deadlock against the other ranks waiting in broadcast() above.
     567         120 :   MeshCommunication().broadcast(this->_mesh);
     568         120 :   increase_order_and_restore_midpoints();
     569         120 :   this->_mesh.prepare_for_use();
     570         670 : }
     571             : 
     572             : 
     573             : 
     574             : } // namespace libMesh
     575             : 
     576             : 
     577             : #endif // #ifdef LIBMESH_HAVE_NETGEN

Generated by: LCOV version 1.14