https://mooseframework.inl.gov
XYZDelaunayGenerator.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #include "XYZDelaunayGenerator.h"
11 
12 #include "CastUniquePointer.h"
13 #include "MooseMeshUtils.h"
14 #include "MooseUtils.h"
16 
17 #include "libmesh/elem.h"
18 #include "libmesh/int_range.h"
19 #include "libmesh/mesh_modification.h"
20 #include "libmesh/mesh_netgen_interface.h"
21 #include "libmesh/mesh_serializer.h"
22 #include "libmesh/parsed_function.h"
23 #include "libmesh/replicated_mesh.h"
24 
26 
27 namespace std
28 {
29 template <>
30 struct hash<std::tuple<libMesh::Point, libMesh::Point, libMesh::Point>>
31 {
32  std::size_t operator()(const std::tuple<libMesh::Point, libMesh::Point, libMesh::Point> & p) const
33  {
34  std::size_t seed = 0;
38  return seed;
39  }
40 };
41 
42 } // namespace std
43 
46 {
48 
49  MooseEnum algorithm("BINARY EXHAUSTIVE", "BINARY");
50 
51  params.addRequiredParam<MeshGeneratorName>(
52  "boundary",
53  "The input MeshGenerator defining the output outer boundary. The input mesh (the output mesh "
54  "of the input mesh generator) can either "
55  "include 3D volume elements or 2D surface elements.");
56 
57  params.addParam<SubdomainName>("output_subdomain_name",
58  "Subdomain name to set on new triangles.");
59 
60  params.addParam<BoundaryName>(
61  "output_boundary",
62  "Boundary name to set on new outer boundary. Default ID: 0 if no hole meshes are stitched; "
63  "or maximum boundary ID of all the stitched hole meshes + 1.");
64  params.addParam<std::vector<BoundaryName>>(
65  "hole_boundaries",
66  "Boundary names to set on holes. Default IDs are numbered up from 1 if no hole meshes are "
67  "stitched; or from maximum boundary ID of all the stitched hole meshes + 2.");
68 
69  params.addParam<bool>("smooth_triangulation",
70  false,
71  "Whether to do Laplacian mesh smoothing on the generated triangles.");
72  params.addParam<std::vector<MeshGeneratorName>>(
73  "holes",
74  {},
75  "The MeshGenerators that create meshes defining the holes. A hole mesh must contain either "
76  "3D volume "
77  "elements where the external surface of the mesh works as the closed manifold that defines "
78  "the hole, or 2D surface elements that form the closed manifold that defines the hole.");
79  params.addParam<std::vector<bool>>(
80  "stitch_holes", std::vector<bool>(), "Whether to stitch to the mesh defining each hole.");
81  params.addParam<bool>("convert_holes_for_stitching",
82  false,
83  "Whether to convert the 3D hole meshes with non-TRI3 surface sides into "
84  "a compatible form.");
85 
86  MooseEnum conversion_method("ALL SURFACE", "ALL");
87  params.addParam<MooseEnum>(
88  "conversion_method",
89  conversion_method,
90  "The method to convert 3D hole meshes into compatible meshes. Options are "
91  "ALL: convert all elements into TET4; SURFACE: convert only the surface elements "
92  "that are stitched to the generated Delaunay mesh.");
93 
94  params.addParam<bool>(
95  "combined_stitching",
96  false,
97  "Whether to stitch all holes in one combined stitching step. This is efficient if a great "
98  "number of holes are to be stitched. But it is hard to debug if problems arise.");
99 
100  params.addRangeCheckedParam<Real>(
101  "desired_volume",
102  0,
103  "desired_volume>=0",
104  "Desired (maximum) tetrahedral volume, or 0 to skip uniform refinement");
105 
106  params.addParam<MooseEnum>(
107  "algorithm",
108  algorithm,
109  "Control the use of binary search for the nodes of the stitched surfaces.");
110  params.addParam<bool>(
111  "verbose_stitching", false, "Whether mesh hole stitching should have verbose output.");
112 
113  params.addClassDescription(
114  "Creates tetrahedral 3D meshes within boundaries defined by input meshes.");
115 
116  return params;
117 }
118 
120  : MeshGenerator(parameters),
121  _bdy_ptr(getMesh("boundary")),
122  _desired_volume(getParam<Real>("desired_volume")),
123  _output_subdomain_id(0),
124  _smooth_tri(getParam<bool>("smooth_triangulation")),
125  _hole_ptrs(getMeshes("holes")),
126  _stitch_holes(getParam<std::vector<bool>>("stitch_holes")),
127  _convert_holes_for_stitching(getParam<bool>("convert_holes_for_stitching")),
128  _conversion_method(parameters.get<MooseEnum>("conversion_method")),
129  _combined_stitching(parameters.get<bool>("combined_stitching")),
130  _algorithm(parameters.get<MooseEnum>("algorithm")),
131  _verbose_stitching(parameters.get<bool>("verbose_stitching"))
132 {
133  if (!_stitch_holes.empty() && _stitch_holes.size() != _hole_ptrs.size())
134  paramError("stitch_holes", "Need one stitch_holes entry per hole, if specified.");
135 
136  if (isParamValid("hole_boundaries"))
137  {
138  auto & hole_boundaries = getParam<std::vector<BoundaryName>>("hole_boundaries");
139  if (hole_boundaries.size() != _hole_ptrs.size())
140  paramError("hole_boundaries", "Need one hole_boundaries entry per hole, if specified.");
141  }
142 
143  if (isParamSetByUser("conversion_method") && !_convert_holes_for_stitching)
144  paramError(
145  "conversion_method",
146  "This parameter is only applicable when convert_holes_for_stitching is set to true.");
147 }
148 
149 std::unique_ptr<MeshBase>
151 {
152 #ifdef LIBMESH_HAVE_NETGEN
153  // Put the boundary mesh in a local pointer
154  std::unique_ptr<UnstructuredMesh> mesh =
155  dynamic_pointer_cast<UnstructuredMesh>(std::move(_bdy_ptr));
156 
157  // The libMesh Netgen removes all the sideset info
158  // But it keeps some nodeset info for the retained nodes
159  // We need to clear these nodeset info as they could overlap with upcoming boundary info
160  mesh->get_boundary_info().clear_boundary_node_ids();
161 
162  // Get ready to triangulate its boundary
164 
166 
168 
169  // The hole meshes will be used for hole boundary identification and optionally for stitching.
170  // if a hole mesh contains 3D volume elements but has non-TRI3 surface side elements, it cannot be
171  // used directly for stitching. But it can be converted into an all-TET4 mesh to support hole
172  // boundary identification
173  for (const auto hole_i : index_range(_hole_ptrs))
174  {
175  UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
176  libMesh::MeshSerializer serial_hole(hole_mesh);
177  // Check the dimension of the hole mesh
178  // We do not need to worry about element order here as libMesh checks it
179  std::set<ElemType> hole_elem_types;
180  std::set<unsigned short> hole_elem_dims;
181  std::vector<std::pair<dof_id_type, unsigned int>> hole_elem_external_sides;
182  for (auto elem : hole_mesh.element_ptr_range())
183  {
184  hole_elem_dims.emplace(elem->dim());
185 
186  // For a 3D element, we need to check the surface side element type instead of the element
187  // type. It is also a good opportunity to define the external boundary.
188  if (elem->dim() == 3)
189  for (auto s : make_range(elem->n_sides()))
190  {
191  // Note that the entire external boundary is used for defining the hole at this time
192  if (!elem->neighbor_ptr(s))
193  {
194  hole_elem_types.emplace(elem->side_ptr(s)->type());
195  hole_elem_external_sides.emplace_back(elem->id(), s);
196  }
197  }
198  // For a non-3D element, we just need to record the element type
199  else
200  hole_elem_types.emplace(elem->type());
201  }
202  if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
203  paramError(
204  "holes",
205  "All elements in a hole mesh must have the same dimension that is either 2D or 3D.");
206  else if (*hole_elem_dims.begin() == 3)
207  {
208  // For 3D meshes, if there are non-TRI3 surface side elements
209  // (1) if no stitching is needed, we can just convert the whole mesh into TET to facilitate
210  // boundary identification (2) if stitching is needed, we can still convert and stitch, but
211  // that would modify the input hole mesh
212  if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
213  {
214  if (_stitch_holes.size() && _stitch_holes[hole_i] && !_convert_holes_for_stitching)
215  paramError("holes",
216  "3D hole meshes with non-TRI3 surface elements cannot be stitched without "
217  "converting them to TET4. Consider setting convert_holes_for_stitching=true.");
218  else if (_stitch_holes.size() && _stitch_holes[hole_i] && _conversion_method == "SURFACE")
219  {
220  // Create a transition layer with triangle sides on the external boundary of the hole
221  BoundaryID temp_ext_bid = MooseMeshUtils::getNextFreeBoundaryID(hole_mesh);
222  for (const auto & hees : hole_elem_external_sides)
223  hole_mesh.get_boundary_info().add_side(hees.first, hees.second, temp_ext_bid);
225  hole_mesh, std::vector<BoundaryName>({std::to_string(temp_ext_bid)}), 1, false);
226  hole_mesh.get_boundary_info().remove_id(temp_ext_bid);
227  // MeshSerializer's destructor calls delete_remote_elements()
228  // For replicated meshes, we need to refresh the neighbor information
229  // Also, not doing prepare_for_use() causes an error in DEBUG mode
230  // in MeshTools::libmesh_assert_topology_consistent_procids()
231  hole_mesh.prepare_for_use();
232  }
233  else
234  MeshTools::Modification::all_tri(**_hole_ptrs[hole_i]);
235  }
236  }
237  else // if (*hole_elem_dims.begin() == 2)
238  {
239  // There is no point to stitch a 2D hole mesh, so we throw an error if this is attempted
240  if (_stitch_holes.size() && _stitch_holes[hole_i])
241  paramError("holes",
242  "the hole mesh with index " + std::to_string(hole_i) +
243  " is a 2D mesh, for which stitching onto a 3D mesh does not make sense.");
244  }
245  }
246 
247  std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
248  std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
249 
250  // The libMesh Netgen interface will modify hole meshes in-place, so
251  // we make copies to pass in.
252  for (std::unique_ptr<MeshBase> * hole_ptr : _hole_ptrs)
253  {
254  // How did we never add a ReplicatedMesh(MeshBase&) constructor in
255  // libMesh?
256  const UnstructuredMesh & hole = dynamic_cast<UnstructuredMesh &>(**hole_ptr);
257  ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
258  }
259 
260  if (!_hole_ptrs.empty())
261  ngint.attach_hole_list(std::move(ngholes));
262 
263  ngint.triangulate();
264 
265  if (isParamValid("output_subdomain_name"))
266  {
267  auto output_subdomain_name = getParam<SubdomainName>("output_subdomain_name");
268  _output_subdomain_id = MooseMeshUtils::getSubdomainID(output_subdomain_name, *mesh);
269 
270  if (_output_subdomain_id == Elem::invalid_subdomain_id)
271  {
272  // We'll probably need to make a new ID, then
274 
275  // But check the hole meshes for our output subdomain name too
276  for (auto & hole_ptr : _hole_ptrs)
277  {
278  auto possible_sbdid = MooseMeshUtils::getSubdomainID(output_subdomain_name, **hole_ptr);
279  // Huh, it was in one of them
280  if (possible_sbdid != Elem::invalid_subdomain_id)
281  {
282  _output_subdomain_id = possible_sbdid;
283  break;
284  }
287  }
288 
289  mesh->subdomain_name(_output_subdomain_id) = output_subdomain_name;
290  }
291  }
292 
294  for (auto elem : mesh->element_ptr_range())
295  {
296  elem->subdomain_id() = _output_subdomain_id;
297 
298  // I do not trust Laplacian mesh smoothing not to invert
299  // elements near reentrant corners. Eventually we'll add better
300  // smoothing options, but even those might have failure cases.
301  // Better to always do extra tests here than to ever let users
302  // try to run on a degenerate mesh.
303  if (elem->is_flipped())
304  {
305  if (_smooth_tri)
306  mooseError("Inverted element found in triangulation.\n"
307  "Laplacian smoothing can create these at reentrant corners; disable it?");
308  else
309  mooseError("Unexplained inverted element found in triangulation.\n");
310  }
311  }
312 
313  const bool use_binary_search = (_algorithm == "BINARY");
314 
315  // The hole meshes are specified by the user, so they could have any
316  // BCID or no BCID or any combination of BCIDs on their outer
317  // boundary, so we'll have to set our own BCID to use for stitching
318  // there. We'll need to check all the holes for used BCIDs, if we
319  // want to pick a new ID on hole N that doesn't conflict with any
320  // IDs on hole M < N (or with the IDs on the new triangulation)
321 
322  // The Netgen generated mesh does not have hole and output boundary ids,
323  // which is different from its 2D counterpart.
324  // So, we need to assign boundary ids to the hole boundaries and output boundary
325  // By default, we assign the hole boundary ids to be 1,2,..., N
326  // and the output boundary id to be 0
327  // We will need a different set of boundary ids if we are stitching holes
328 
329  // end_bcid is one more than the maximum of default output boundary id and hole boundary ids
330  const boundary_id_type end_bcid = _hole_ptrs.size() + 1;
331 
332  // For the hole meshes that need to be stitched, we would like to make sure the hole boundary ids
333  // and output boundary id are not conflicting with the existing boundary ids of the hole meshes to
334  // be stitched.
335  BoundaryID free_boundary_id = 0;
336  // We need to get a free boundary id that is larger than the existing boundary ids in any hole
337  // meshes
338  if (_stitch_holes.size())
339  {
340  for (auto hole_i : index_range(_hole_ptrs))
341  {
342  if (_stitch_holes[hole_i])
343  {
344  free_boundary_id =
345  std::max(free_boundary_id, MooseMeshUtils::getNextFreeBoundaryID(**_hole_ptrs[hole_i]));
346  (*_hole_ptrs[hole_i])->comm().max(free_boundary_id);
347  }
348  }
349  }
350  // new_hole_bcid is used to ensure the boundary ids used for stitching have no conflicts
351  // If we shift the default hole boundary ids and output boundary id by free_boundary_id, we need
352  // to have new_hole_bcid that is larger than any existing boundary ids in the hole meshes and the
353  // Netgen generated mesh
354  boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
355 
356  auto hole_boundaries = isParamValid("hole_boundaries")
357  ? getParam<std::vector<BoundaryName>>("hole_boundaries")
358  : std::vector<BoundaryName>();
359  const BoundaryName output_boundary =
360  isParamValid("output_boundary") ? getParam<BoundaryName>("output_boundary") : BoundaryName();
361 
362  // if outer boundary id is not specified by a numeric output_boundary, we just assign
363  // free_boundary_id to the output boundary
364  const std::vector<BoundaryID> output_boundary_id =
365  isParamValid("output_boundary")
366  ? (MooseUtils::isDigits(output_boundary)
367  ? std::vector<BoundaryID>(
368  1, MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(output_boundary))
369  : std::vector<BoundaryID>(1, free_boundary_id))
370  : std::vector<BoundaryID>();
371  // Similarly, set the hole boundary ids one by one
372  std::vector<BoundaryID> hole_boundary_ids;
373  if (isParamValid("hole_boundaries"))
374  for (auto h : index_range(hole_boundaries))
375  hole_boundary_ids.push_back(
376  MooseUtils::isDigits(hole_boundaries[h])
377  ? MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(hole_boundaries[h])
378  : h + 1 + free_boundary_id);
379 
380  // if large ids are used in the hole boundaries or the output boundary,
381  // we need to make sure the new_hole_bcid is larger than them
382  if (hole_boundary_ids.size())
383  for (auto h : index_range(_hole_ptrs))
384  new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(hole_boundary_ids[h] + 1));
385  if (output_boundary_id.size())
386  new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(output_boundary_id[0] + 1));
387 
388  bool doing_stitching = false;
389 
390  for (auto hole_i : index_range(_hole_ptrs))
391  {
392  const MeshBase & hole_mesh = **_hole_ptrs[hole_i];
393  auto & hole_boundary_info = hole_mesh.get_boundary_info();
394  const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
395 
396  if (!local_hole_bcids.empty())
397  new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(*local_hole_bcids.rbegin() + 1));
398  hole_mesh.comm().max(new_hole_bcid);
399 
400  if (_stitch_holes.size() && _stitch_holes[hole_i])
401  doing_stitching = true;
402  }
403  // Now we can define boundaries to assist with stitching
404  const boundary_id_type inner_bcid = new_hole_bcid + 1;
405 
406  // libMesh mesh stitching still requires a serialized mesh, and it's
407  // cheaper to do that once than to do it once-per-hole
408  libMesh::MeshSerializer serial(*mesh, doing_stitching);
409 
410  // We'll be looking for any sides that match between hole meshes and
411  // the newly triangulated mesh, to apply bcids accordingly. We
412  // can't key on Elem::key() here, because that depends on node ids
413  // that differ from mesh to mesh. We can't use centroids here,
414  // because that depends on rounding error in order of operations
415  // that can differ from mesh to mesh. The node locations themselves
416  // should always match up exactly, though, so let's use (sorted!)
417  // tuples of those to map from nodes to elements and side numbers.
418  // Also added a Boolean to check if the face is already used
419  std::unordered_map<std::tuple<Point, Point, Point>,
420  std::pair<std::pair<Elem *, unsigned int>, bool>>
421  mesh_faces;
422 
423  auto sorted_point_tuple = [](Elem & elem, unsigned int side)
424  {
425  std::vector<unsigned int> nodes_on_side = elem.nodes_on_side(side);
426  libmesh_assert_equal_to(nodes_on_side.size(), 3);
427  std::vector<Point> p(3);
428  for (auto i : index_range(p))
429  p[i] = elem.point(nodes_on_side[i]);
430  if (p[0] < p[1])
431  {
432  if (p[1] < p[2])
433  return std::make_tuple(p[0], p[1], p[2]);
434  else if (p[0] < p[2])
435  return std::make_tuple(p[0], p[2], p[1]);
436  else
437  return std::make_tuple(p[2], p[0], p[1]);
438  }
439  else
440  {
441  if (p[0] < p[2])
442  return std::make_tuple(p[1], p[0], p[2]);
443  else if (p[1] < p[2])
444  return std::make_tuple(p[1], p[2], p[0]);
445  else
446  return std::make_tuple(p[2], p[1], p[0]);
447  }
448  };
449 
450  if (!_hole_ptrs.empty())
451  for (auto elem : mesh->element_ptr_range())
452  for (auto s : make_range(elem->n_sides()))
453  if (!elem->neighbor_ptr(s))
454  {
455  auto points = sorted_point_tuple(*elem, s);
456  libmesh_assert(!mesh_faces.count(points));
457  mesh_faces.emplace(points, std::make_pair(std::make_pair(elem, s), false));
458  }
459 
460  auto & mesh_boundary_info = mesh->get_boundary_info();
461 
462  // Define a reference map variable for subdomain map
463  auto & main_subdomain_map = mesh->set_subdomain_name_map();
464  for (auto hole_i : index_range(_hole_ptrs))
465  {
466  UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
467  auto & hole_boundary_info = hole_mesh.get_boundary_info();
468 
469  // Our algorithm here requires a serialized Mesh. To avoid
470  // redundant serialization and deserialization (libMesh
471  // MeshedHole and stitch_meshes still also require
472  // serialization) we'll do the serialization up front.
473  libMesh::MeshSerializer serial_hole(hole_mesh);
474 
475  // We'll look for any sides that match between the hole mesh and
476  // the newly triangulated mesh, and apply bcids accordingly.
477  for (auto elem : hole_mesh.element_ptr_range())
478  for (auto s : make_range(elem->n_sides()))
479  if (!elem->neighbor_ptr(s))
480  {
481  auto points = sorted_point_tuple(*elem, s);
482  auto it = mesh_faces.find(points);
483 
484  // I'd love to assert that we don't have any missing
485  // matches, but our holes might themselves have holes
486  if (it != mesh_faces.end())
487  {
488  auto [main_elem, main_side] = it->second.first;
489  if (_stitch_holes.size() && _stitch_holes[hole_i])
490  {
491  hole_boundary_info.add_side(elem, s, new_hole_bcid);
492  mesh_boundary_info.add_side(main_elem, main_side, inner_bcid);
493  }
494  // We would like to take this opportunity to identify the hole boundary
495  // We set the boundary id to hole_i + 1 at this stage (the default)
496  mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
497  it->second.second = true;
498  }
499  }
500  }
501 
502  // Any sides that do not match the hole surfaces belong to the external boundary
503  // We set the boundary id to 0 at this stage (the default)
504  for (auto & [points, elem_side] : mesh_faces)
505  if (!elem_side.second)
506  {
507  auto [main_elem, main_side] = elem_side.first;
508  mesh_boundary_info.add_side(main_elem, main_side, 0);
509  }
510 
511  // Now hole boundary ids are 1,2,..., N
512  // Now external boundary id is 0
513  // We will find the upper bound of the boundary ids of the mesh so that we would not overwrite
514  // things during renumbering (because of the bcids used for stitching, this is different from
515  // end_bcid)
516  const auto temp_bcid_shift = MooseMeshUtils::getNextFreeBoundaryID(*mesh);
517  // If we stitch holes, we want to make sure the netgen mesh has no conflicting boundary ids
518  // with the hole meshes.
519  // OR, if we assign custom boundary ids, we want to make sure no overwriting occurs.
520  // So we shift all these boundary ids by temp_bcid_shift + free_boundary_id
521  // before we assign the new boundary ids
522  if (_stitch_holes.size() || output_boundary_id.size())
524  *mesh, 0, temp_bcid_shift + free_boundary_id);
525  if (_stitch_holes.size() || hole_boundary_ids.size())
526  for (auto hole_i : index_range(_hole_ptrs))
528  *mesh, hole_i + 1, hole_i + 1 + temp_bcid_shift + free_boundary_id);
529 
530  // Now we can reassign the boundary ids
531  // If these ids are specified, we will use them
532  // Otherwise, we will use the default ones
533  if (output_boundary_id.size())
535  *mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
536  else
538  *mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
539 
540  if (hole_boundary_ids.size())
541  for (auto hole_i : index_range(_hole_ptrs))
543  *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
544  else
545  for (auto hole_i : index_range(_hole_ptrs))
547  *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
548 
549  for (auto hole_i : index_range(_hole_ptrs))
550  {
551  UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
552 
553  if (_stitch_holes.size() && _stitch_holes[hole_i])
554  {
555  // Retrieve subdomain name map from the mesh to be stitched and insert it into the main
556  // subdomain map
557  const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
558  main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
559 
561  {
562  // The main mesh has been serialized early and will last through the end of this MG
563  // If the hole mesh to be stitched is not serialized, it will cause parallelization
564  // issues after combining when the number of processors is large
565  libMesh::MeshSerializer serial_hole(hole_mesh);
566  MooseMeshUtils::copyIntoMesh(*this, *mesh, hole_mesh, false, false, _communicator);
567  }
568  else
569  {
570  std::size_t n_nodes_stitched = mesh->stitch_meshes(hole_mesh,
571  inner_bcid,
572  new_hole_bcid,
573  TOLERANCE,
574  /*clear_stitched_bcids*/ true,
576  use_binary_search);
577 
578  if (!n_nodes_stitched)
579  mooseError("Failed to stitch hole mesh ", hole_i, " to new tetrahedralization.");
580  }
581  }
582  }
583  if (doing_stitching && _combined_stitching)
584  {
585  std::size_t n_nodes_stitched = mesh->stitch_surfaces(inner_bcid,
586  new_hole_bcid,
587  TOLERANCE,
588  /*clear_stitched_bcids*/ true,
590  use_binary_search);
591  if (!n_nodes_stitched)
592  mooseError("Failed to stitch combined hole meshes to new tetrahedralization.");
593  }
594 
595  // Add user-specified sideset names
596  if (hole_boundary_ids.size())
597  for (auto h : index_range(_hole_ptrs))
598  mesh->get_boundary_info().sideset_name(hole_boundary_ids[h]) = hole_boundaries[h];
599  if (output_boundary_id.size())
600  mesh->get_boundary_info().sideset_name(output_boundary_id[0]) = output_boundary;
601 
602  // Check if one SubdomainName is shared by more than one subdomain ids
603  std::set<SubdomainName> main_subdomain_map_name_list;
604  for (auto const & id_name_pair : main_subdomain_map)
605  main_subdomain_map_name_list.emplace(id_name_pair.second);
606  if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
607  paramError("holes", "The hole meshes contain subdomain name maps with conflicts.");
608 
609  // We're done with the hole meshes now, and MeshGenerator doesn't
610  // want them anymore either.
611  for (auto & hole_ptr : _hole_ptrs)
612  hole_ptr->reset();
613 
614  mesh->set_isnt_prepared();
615  return mesh;
616 #else
617  mooseError("Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
618  return std::unique_ptr<MeshBase>();
619 #endif
620 }
const Real _desired_volume
Desired volume of output tetrahedra.
const bool _combined_stitching
Whether to stitch all holes in one combined stitching step.
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:439
void hash_combine(std::size_t &seed, const T &value)
const bool _convert_holes_for_stitching
Whether to convert 3D hole meshes with non-TRI3 surface elements into a compatible form...
std::size_t operator()(const std::tuple< libMesh::Point, libMesh::Point, libMesh::Point > &p) const
XYZDelaunayGenerator(const InputParameters &parameters)
MeshBase & mesh
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
const Parallel::Communicator & comm() const
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
virtual void triangulate() override
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
const Parallel::Communicator & _communicator
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
auto max(const L &left, const R &right)
int8_t boundary_id_type
const bool _verbose_stitching
Whether mesh stitching should have verbose output.
boundary_id_type BoundaryID
const MooseEnum _algorithm
Type of algorithm used to find matching nodes (binary or exhaustive)
void change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
registerMooseObject("MooseApp", XYZDelaunayGenerator)
libmesh_assert(ctx)
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:33
static InputParameters validParams()
void copyIntoMesh(MeshGenerator &mg, UnstructuredMesh &destination, const UnstructuredMesh &source, const bool avoid_merging_subdomains, const bool avoid_merging_boundaries, const Parallel::Communicator &communicator)
Helper function for copying one mesh into another.
const bool _smooth_tri
Whether to do Laplacian mesh smoothing on the generated triangles.
static InputParameters validParams()
Definition: MeshGenerator.C:23
const std::vector< std::unique_ptr< MeshBase > * > _hole_ptrs
Holds pointers to the pointers to input meshes defining holes.
const std::vector< bool > _stitch_holes
Whether to stitch to the mesh defining each hole.
void attach_hole_list(std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh >>> holes)
Generates a tetrahedral mesh, based on an input mesh defining the outer boundary and an optional set ...
std::unique_ptr< MeshBase > & _bdy_ptr
Input mesh defining the boundary to triangulate within.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void max(const T &r, T &o, Request &req) const
IntRange< T > make_range(T beg, T end)
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:271
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:199
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
BoundaryID getNextFreeBoundaryID(MeshBase &input_mesh)
Checks input mesh and returns the largest boundary ID in the mesh plus one, which is a boundary ID in...
const MooseEnum _conversion_method
Method to convert 3D hole meshes into compatible meshes.
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition: MooseBase.h:205
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:33
void transitionLayerGenerator(MeshBase &mesh, const std::vector< BoundaryName > &boundary_names, const unsigned int conversion_element_layer_number, const bool external_boundaries_checking)
Generate a transition layer of elements with TRI3 surfaces on the given boundaries.
auto index_range(const T &sizable)
const Elem & get(const ElemType type_in)
SubdomainID _output_subdomain_id
What subdomain_id to set on the generated tetrahedra.