https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
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
27namespace std
28{
29template <>
30struct 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;
35 libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<0>(p)));
36 libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<1>(p)));
37 libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<2>(p)));
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.addRangeCheckedParam<Real>(
95 "desired_volume",
96 0,
97 "desired_volume>=0",
98 "Desired (maximum) tetrahedral volume, or 0 to skip uniform refinement");
99
100 params.addParam<bool>(
101 "combined_stitching",
102 false,
103 "Whether to stitch all holes in one combined stitching step. This is efficient if a great "
104 "number of holes are to be stitched. But it is hard to debug if problems arise.");
105 params.addParam<MooseEnum>(
106 "algorithm",
107 algorithm,
108 "Control the use of binary search for the nodes of the stitched surfaces.");
109 params.renameParam("algorithm", "stitching_algorithm", "12/10/2026");
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)
145 "conversion_method",
146 "This parameter is only applicable when convert_holes_for_stitching is set to true.");
147}
148
149std::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)
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 {
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");
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->set_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->unset_is_prepared();
615 return mesh;
616#else
617 mooseError("Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
618 return std::unique_ptr<MeshBase>();
619#endif
620}
boundary_id_type BoundaryID
registerMooseObject("MooseApp", XYZDelaunayGenerator)
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
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 renameParam(const std::string &old_name, const std::string &new_name, const std::string &new_docstring)
Rename a parameter and provide a new documentation string.
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...
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 addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
MeshGenerators are objects that can modify or add to an existing mesh.
static InputParameters validParams()
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:457
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
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
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition MooseBase.h:199
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition MooseEnum.h:55
void max(const T &r, T &o, Request &req) const
Generates a tetrahedral mesh, based on an input mesh defining the outer boundary and an optional set ...
const bool _verbose_stitching
Whether mesh stitching should have verbose output.
const bool _smooth_tri
Whether to do Laplacian mesh smoothing on the generated triangles.
const std::vector< bool > _stitch_holes
Whether to stitch to the mesh defining each hole.
const MooseEnum _algorithm
Type of algorithm used to find matching nodes (binary or exhaustive)
SubdomainID _output_subdomain_id
What subdomain_id to set on the generated tetrahedra.
const bool _combined_stitching
Whether to stitch all holes in one combined stitching step.
const Real _desired_volume
Desired volume of output tetrahedra.
const MooseEnum _conversion_method
Method to convert 3D hole meshes into compatible meshes.
const std::vector< std::unique_ptr< MeshBase > * > _hole_ptrs
Holds pointers to the pointers to input meshes defining holes.
std::unique_ptr< MeshBase > & _bdy_ptr
Input mesh defining the boundary to triangulate within.
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
static InputParameters validParams()
XYZDelaunayGenerator(const InputParameters &parameters)
const bool _convert_holes_for_stitching
Whether to convert 3D hole meshes with non-TRI3 surface elements into a compatible form.
void attach_hole_list(std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > > holes)
virtual void triangulate() override
const Parallel::Communicator & _communicator
const Parallel::Communicator & comm() const
MeshBase & mesh
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.
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.
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...
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
void change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
void hash_combine(std::size_t &seed, const T &value)
std::size_t operator()(const std::tuple< libMesh::Point, libMesh::Point, libMesh::Point > &p) const