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 MooseEnum tet_elem_type("TET4 TET10 TET14 DEFAULT", "DEFAULT");
114 params.addParam<MooseEnum>(
115 "tet_element_type", tet_elem_type, "Type of the tetrahedral elements to be generated.");
116
117 params.addClassDescription(
118 "Creates tetrahedral 3D meshes within boundaries defined by input meshes.");
119
120 return params;
121}
122
124 : MeshGenerator(parameters),
125 _bdy_ptr(getMesh("boundary")),
126 _desired_volume(getParam<Real>("desired_volume")),
127 _output_subdomain_id(0),
128 _smooth_tri(getParam<bool>("smooth_triangulation")),
129 _hole_ptrs(getMeshes("holes")),
130 _stitch_holes(getParam<std::vector<bool>>("stitch_holes")),
131 _convert_holes_for_stitching(getParam<bool>("convert_holes_for_stitching")),
132 _conversion_method(parameters.get<MooseEnum>("conversion_method")),
133 _combined_stitching(parameters.get<bool>("combined_stitching")),
134 _algorithm(parameters.get<MooseEnum>("algorithm")),
135 _verbose_stitching(parameters.get<bool>("verbose_stitching")),
136 _tet_elem_type(parameters.get<MooseEnum>("tet_element_type"))
137{
138 if (!_stitch_holes.empty() && _stitch_holes.size() != _hole_ptrs.size())
139 paramError("stitch_holes", "Need one stitch_holes entry per hole, if specified.");
140
141 if (isParamValid("hole_boundaries"))
142 {
143 auto & hole_boundaries = getParam<std::vector<BoundaryName>>("hole_boundaries");
144 if (hole_boundaries.size() != _hole_ptrs.size())
145 paramError("hole_boundaries", "Need one hole_boundaries entry per hole, if specified.");
146 }
147
148 if (isParamSetByUser("conversion_method") && !_convert_holes_for_stitching)
150 "conversion_method",
151 "This parameter is only applicable when convert_holes_for_stitching is set to true.");
152}
153
154std::unique_ptr<MeshBase>
156{
157#ifdef LIBMESH_HAVE_NETGEN
158 // Put the boundary mesh in a local pointer
159 std::unique_ptr<UnstructuredMesh> mesh =
160 dynamic_pointer_cast<UnstructuredMesh>(std::move(_bdy_ptr));
161
162 // The libMesh Netgen removes all the sideset info
163 // But it keeps some nodeset info for the retained nodes
164 // We need to clear these nodeset info as they could overlap with upcoming boundary info
165 mesh->get_boundary_info().clear_boundary_node_ids();
166
167 // Map the requested element type to a libMesh tet type. "DEFAULT" (and "TET4")
168 // keep the libMesh default of first-order TET4. "TET10"/"TET14" request
169 // second-/complete-order output; libMesh's NetGen interface then inherits any
170 // curved mid-edge nodes from a second-order (TRI6/TRI7) input surface.
171 const libMesh::ElemType tet_type = (_tet_elem_type == "TET10") ? libMesh::TET10
172 : (_tet_elem_type == "TET14") ? libMesh::TET14
174 const bool second_order_output = (tet_type != libMesh::TET4);
175
176 // Get ready to triangulate its boundary
178
180
182
183 ngint.elem_type() = tet_type;
184
185 // The hole meshes will be used for hole boundary identification and optionally for stitching.
186 // if a hole mesh contains 3D volume elements but has non-TRI3 surface side elements, it cannot be
187 // used directly for stitching. But it can be converted into an all-TET4 mesh to support hole
188 // boundary identification
189 for (const auto hole_i : index_range(_hole_ptrs))
190 {
191 UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
192 libMesh::MeshSerializer serial_hole(hole_mesh);
193 // Check the dimension of the hole mesh
194 // We do not need to worry about element order here as libMesh checks it
195 std::set<ElemType> hole_elem_types;
196 std::set<unsigned short> hole_elem_dims;
197 std::vector<std::pair<dof_id_type, unsigned int>> hole_elem_external_sides;
198 // Whether the hole mesh already carries mid-edge nodes (second order or higher). We
199 // check the element order directly: side_ptr() returns a first-order side even for a
200 // second-order element, so surface side types cannot be used to detect order.
201 bool hole_has_midpoints = false;
202 for (auto elem : hole_mesh.element_ptr_range())
203 {
204 hole_elem_dims.emplace(elem->dim());
205 if (elem->default_order() != libMesh::FIRST)
206 hole_has_midpoints = true;
207
208 // For a 3D element, we need to check the surface side element type instead of the element
209 // type. It is also a good opportunity to define the external boundary.
210 if (elem->dim() == 3)
211 for (auto s : make_range(elem->n_sides()))
212 {
213 // Note that the entire external boundary is used for defining the hole at this time
214 if (!elem->neighbor_ptr(s))
215 {
216 hole_elem_types.emplace(elem->side_ptr(s)->type());
217 hole_elem_external_sides.emplace_back(elem->id(), s);
218 }
219 }
220 // For a non-3D element, we just need to record the element type
221 else
222 hole_elem_types.emplace(elem->type());
223 }
224 if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
226 "holes",
227 "All elements in a hole mesh must have the same dimension that is either 2D or 3D.");
228 else if (*hole_elem_dims.begin() == 3)
229 {
230 const bool stitching_this_hole = _stitch_holes.size() && _stitch_holes[hole_i];
231
232 // A stitched quadratic hole cannot conform to a first-order tetrahedralization.
233 if (!second_order_output && stitching_this_hole && hole_has_midpoints)
235 "tet_element_type",
236 "Cannot use first order elements with stitched quadratic element holes. Please "
237 "specify a higher-order tet_element_type or reduce the order of the hole inputs.");
238
239 // For 3D meshes, if there are non-TRI3 surface side elements
240 // (1) if no stitching is needed, we can just convert the whole mesh into TET to facilitate
241 // boundary identification (2) if stitching is needed, we can still convert and stitch, but
242 // that would modify the input hole mesh.
243 // Note a TET10/TET14 hole reports first-order (TRI3) surface sides here, so it is not
244 // converted; NetGen strips and restores its curved mid-edge nodes internally.
245 if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
246 {
247 if (stitching_this_hole && !_convert_holes_for_stitching)
248 paramError("holes",
249 "3D hole meshes with non-TRI3 surface elements cannot be stitched without "
250 "converting them to TET4. Consider setting convert_holes_for_stitching=true.");
251 else if (stitching_this_hole && _conversion_method == "SURFACE")
252 {
253 // Create a transition layer with triangle sides on the external boundary of the hole
254 BoundaryID temp_ext_bid = MooseMeshUtils::getNextFreeBoundaryID(hole_mesh);
255 for (const auto & hees : hole_elem_external_sides)
256 hole_mesh.get_boundary_info().add_side(hees.first, hees.second, temp_ext_bid);
258 hole_mesh, std::vector<BoundaryName>({std::to_string(temp_ext_bid)}), 1, false);
259 hole_mesh.get_boundary_info().remove_id(temp_ext_bid);
260 // MeshSerializer's destructor calls delete_remote_elements()
261 // For replicated meshes, we need to refresh the neighbor information
262 // Also, not doing prepare_for_use() causes an error in DEBUG mode
263 // in MeshTools::libmesh_assert_topology_consistent_procids()
264 hole_mesh.prepare_for_use();
265 }
266 else
267 MeshTools::Modification::all_tri(**_hole_ptrs[hole_i]);
268 }
269
270 // For second-order output, raise a stitched hole's order so its seam faces have
271 // matching mid-edge (and, for TET14, face-center) nodes. A hole that already carries
272 // the needed nodes keeps its inherited curvature. For TET14 output a TET10 hole must
273 // still be raised, because the added face-center node lies on the stitched face.
274 if (second_order_output && stitching_this_hole)
275 {
276 if (!hole_mesh.is_prepared())
277 hole_mesh.prepare_for_use();
278 // Re-read the current order; the hole may have been converted to TET4 above.
279 ElemType cur_type = libMesh::INVALID_ELEM;
280 for (const auto elem : hole_mesh.element_ptr_range())
281 {
282 cur_type = elem->type();
283 break;
284 }
285 if (tet_type == libMesh::TET10 && cur_type != libMesh::TET10 && cur_type != libMesh::TET14)
286 hole_mesh.all_second_order();
287 else if (tet_type == libMesh::TET14 && cur_type != libMesh::TET14)
288 hole_mesh.all_complete_order();
289 }
290 }
291 else // if (*hole_elem_dims.begin() == 2)
292 {
293 // There is no point to stitch a 2D hole mesh, so we throw an error if this is attempted
294 if (_stitch_holes.size() && _stitch_holes[hole_i])
295 paramError("holes",
296 "the hole mesh with index " + std::to_string(hole_i) +
297 " is a 2D mesh, for which stitching onto a 3D mesh does not make sense.");
298 }
299 }
300
301 std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
302 std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
303
304 // The libMesh Netgen interface will modify hole meshes in-place, so
305 // we make copies to pass in.
306 for (std::unique_ptr<MeshBase> * hole_ptr : _hole_ptrs)
307 {
308 // How did we never add a ReplicatedMesh(MeshBase&) constructor in
309 // libMesh?
310 const UnstructuredMesh & hole = dynamic_cast<UnstructuredMesh &>(**hole_ptr);
311 ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
312
313#ifdef LIBMESH_ENABLE_UNIQUE_ID
314 // That constructor inherits the source mesh's next-unique-id counter, and a
315 // DistributedMesh deliberately keeps a different counter on every rank. A
316 // ReplicatedMesh has to agree on it (see MeshBase::set_next_unique_id), or the
317 // elements libMesh's Netgen interface adds to this copy when it converts the hole
318 // to a surface mesh get different unique ids on different ranks -- which makes the
319 // Hilbert sort keys used by the partitioner's global-index lookup disagree.
320 UnstructuredMesh & hole_copy = *ngholes->back();
321 hole_copy.set_next_unique_id(hole_copy.parallel_max_unique_id());
322#endif
323 }
324
325 if (!_hole_ptrs.empty())
326 ngint.attach_hole_list(std::move(ngholes));
327
328 ngint.triangulate();
329
330 if (isParamValid("output_subdomain_name"))
331 {
332 auto output_subdomain_name = getParam<SubdomainName>("output_subdomain_name");
334
335 if (_output_subdomain_id == Elem::invalid_subdomain_id)
336 {
337 // We'll probably need to make a new ID, then
339
340 // But check the hole meshes for our output subdomain name too
341 for (auto & hole_ptr : _hole_ptrs)
342 {
343 auto possible_sbdid = MooseMeshUtils::getSubdomainID(output_subdomain_name, **hole_ptr);
344 // Huh, it was in one of them
345 if (possible_sbdid != Elem::invalid_subdomain_id)
346 {
347 _output_subdomain_id = possible_sbdid;
348 break;
349 }
352 }
353
354 mesh->set_subdomain_name(_output_subdomain_id, output_subdomain_name);
355 }
356 }
357
359 for (auto elem : mesh->element_ptr_range())
360 {
361 mooseAssert(elem->type() == tet_type, "Unexpected element type found in triangulation");
362
363 elem->subdomain_id() = _output_subdomain_id;
364
365 // I do not trust Laplacian mesh smoothing not to invert
366 // elements near reentrant corners. Eventually we'll add better
367 // smoothing options, but even those might have failure cases.
368 // Better to always do extra tests here than to ever let users
369 // try to run on a degenerate mesh.
370 if (elem->is_flipped())
371 {
372 if (_smooth_tri)
373 mooseError("Inverted element found in triangulation.\n"
374 "Laplacian smoothing can create these at reentrant corners; disable it?");
375 else
376 mooseError("Unexplained inverted element found in triangulation.\n");
377 }
378 }
379
380 const bool use_binary_search = (_algorithm == "BINARY");
381
382 // The hole meshes are specified by the user, so they could have any
383 // BCID or no BCID or any combination of BCIDs on their outer
384 // boundary, so we'll have to set our own BCID to use for stitching
385 // there. We'll need to check all the holes for used BCIDs, if we
386 // want to pick a new ID on hole N that doesn't conflict with any
387 // IDs on hole M < N (or with the IDs on the new triangulation)
388
389 // The Netgen generated mesh does not have hole and output boundary ids,
390 // which is different from its 2D counterpart.
391 // So, we need to assign boundary ids to the hole boundaries and output boundary
392 // By default, we assign the hole boundary ids to be 1,2,..., N
393 // and the output boundary id to be 0
394 // We will need a different set of boundary ids if we are stitching holes
395
396 // end_bcid is one more than the maximum of default output boundary id and hole boundary ids
397 const boundary_id_type end_bcid = _hole_ptrs.size() + 1;
398
399 // For the hole meshes that need to be stitched, we would like to make sure the hole boundary ids
400 // and output boundary id are not conflicting with the existing boundary ids of the hole meshes to
401 // be stitched.
402 BoundaryID free_boundary_id = 0;
403 // We need to get a free boundary id that is larger than the existing boundary ids in any hole
404 // meshes
405 if (_stitch_holes.size())
406 {
407 for (auto hole_i : index_range(_hole_ptrs))
408 {
409 if (_stitch_holes[hole_i])
410 {
411 free_boundary_id =
412 std::max(free_boundary_id, MooseMeshUtils::getNextFreeBoundaryID(**_hole_ptrs[hole_i]));
413 (*_hole_ptrs[hole_i])->comm().max(free_boundary_id);
414 }
415 }
416 }
417 // new_hole_bcid is used to ensure the boundary ids used for stitching have no conflicts
418 // If we shift the default hole boundary ids and output boundary id by free_boundary_id, we need
419 // to have new_hole_bcid that is larger than any existing boundary ids in the hole meshes and the
420 // Netgen generated mesh
421 boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
422
423 auto hole_boundaries = isParamValid("hole_boundaries")
424 ? getParam<std::vector<BoundaryName>>("hole_boundaries")
425 : std::vector<BoundaryName>();
426 const BoundaryName output_boundary =
427 isParamValid("output_boundary") ? getParam<BoundaryName>("output_boundary") : BoundaryName();
428
429 // if outer boundary id is not specified by a numeric output_boundary, we just assign
430 // free_boundary_id to the output boundary
431 const std::vector<BoundaryID> output_boundary_id =
432 isParamValid("output_boundary")
433 ? (MooseUtils::isDigits(output_boundary)
434 ? std::vector<BoundaryID>(
435 1, MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(output_boundary))
436 : std::vector<BoundaryID>(1, free_boundary_id))
437 : std::vector<BoundaryID>();
438 // Similarly, set the hole boundary ids one by one
439 std::vector<BoundaryID> hole_boundary_ids;
440 if (isParamValid("hole_boundaries"))
441 for (auto h : index_range(hole_boundaries))
442 hole_boundary_ids.push_back(
443 MooseUtils::isDigits(hole_boundaries[h])
444 ? MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(hole_boundaries[h])
445 : h + 1 + free_boundary_id);
446
447 // if large ids are used in the hole boundaries or the output boundary,
448 // we need to make sure the new_hole_bcid is larger than them
449 if (hole_boundary_ids.size())
450 for (auto h : index_range(_hole_ptrs))
451 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(hole_boundary_ids[h] + 1));
452 if (output_boundary_id.size())
453 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(output_boundary_id[0] + 1));
454
455 bool doing_stitching = false;
456
457 for (auto hole_i : index_range(_hole_ptrs))
458 {
459 const MeshBase & hole_mesh = **_hole_ptrs[hole_i];
460 auto & hole_boundary_info = hole_mesh.get_boundary_info();
461 const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
462
463 if (!local_hole_bcids.empty())
464 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(*local_hole_bcids.rbegin() + 1));
465 hole_mesh.comm().max(new_hole_bcid);
466
467 if (_stitch_holes.size() && _stitch_holes[hole_i])
468 doing_stitching = true;
469 }
470 // Now we can define boundaries to assist with stitching
471 const boundary_id_type inner_bcid = new_hole_bcid + 1;
472
473 // libMesh mesh stitching still requires a serialized mesh, and it's
474 // cheaper to do that once than to do it once-per-hole
475 libMesh::MeshSerializer serial(*mesh, doing_stitching);
476
477 // We'll be looking for any sides that match between hole meshes and
478 // the newly triangulated mesh, to apply bcids accordingly. We
479 // can't key on Elem::key() here, because that depends on node ids
480 // that differ from mesh to mesh. We can't use centroids here,
481 // because that depends on rounding error in order of operations
482 // that can differ from mesh to mesh. The node locations themselves
483 // should always match up exactly, though, so let's use (sorted!)
484 // tuples of those to map from nodes to elements and side numbers.
485 // Also added a Boolean to check if the face is already used
486 std::unordered_map<std::tuple<Point, Point, Point>,
487 std::pair<std::pair<Elem *, unsigned int>, bool>>
488 mesh_faces;
489
490 auto sorted_point_tuple = [](Elem & elem, unsigned int side)
491 {
492 // Key faces on their vertex corners only. For second-order tets (TET10/TET14)
493 // nodes_on_side() also returns mid-edge/face nodes, so we filter to vertices to
494 // keep a 3-corner key. For TET4 this filter is a no-op.
495 const std::vector<unsigned int> nodes_on_side = elem.nodes_on_side(side);
496 std::vector<Point> p;
497 p.reserve(3);
498 for (const auto n : nodes_on_side)
499 if (elem.is_vertex(n))
500 p.push_back(elem.point(n));
501 libmesh_assert_equal_to(p.size(), 3);
502 if (p[0] < p[1])
503 {
504 if (p[1] < p[2])
505 return std::make_tuple(p[0], p[1], p[2]);
506 else if (p[0] < p[2])
507 return std::make_tuple(p[0], p[2], p[1]);
508 else
509 return std::make_tuple(p[2], p[0], p[1]);
510 }
511 else
512 {
513 if (p[0] < p[2])
514 return std::make_tuple(p[1], p[0], p[2]);
515 else if (p[1] < p[2])
516 return std::make_tuple(p[1], p[2], p[0]);
517 else
518 return std::make_tuple(p[2], p[1], p[0]);
519 }
520 };
521
522 if (!_hole_ptrs.empty())
523 for (auto elem : mesh->element_ptr_range())
524 for (auto s : make_range(elem->n_sides()))
525 if (!elem->neighbor_ptr(s))
526 {
527 auto points = sorted_point_tuple(*elem, s);
528 libmesh_assert(!mesh_faces.count(points));
529 mesh_faces.emplace(points, std::make_pair(std::make_pair(elem, s), false));
530 }
531
532 auto & mesh_boundary_info = mesh->get_boundary_info();
533
534 // Define a reference map variable for subdomain map
535 auto & main_subdomain_map = mesh->set_subdomain_name_map();
536 for (auto hole_i : index_range(_hole_ptrs))
537 {
538 UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
539 auto & hole_boundary_info = hole_mesh.get_boundary_info();
540
541 // Our algorithm here requires a serialized Mesh. To avoid
542 // redundant serialization and deserialization (libMesh
543 // MeshedHole and stitch_meshes still also require
544 // serialization) we'll do the serialization up front.
545 libMesh::MeshSerializer serial_hole(hole_mesh);
546
547 // We'll look for any sides that match between the hole mesh and
548 // the newly triangulated mesh, and apply bcids accordingly.
549 for (auto elem : hole_mesh.element_ptr_range())
550 for (auto s : make_range(elem->n_sides()))
551 if (!elem->neighbor_ptr(s))
552 {
553 auto points = sorted_point_tuple(*elem, s);
554 auto it = mesh_faces.find(points);
555
556 // I'd love to assert that we don't have any missing
557 // matches, but our holes might themselves have holes
558 if (it != mesh_faces.end())
559 {
560 auto [main_elem, main_side] = it->second.first;
561 if (_stitch_holes.size() && _stitch_holes[hole_i])
562 {
563 hole_boundary_info.add_side(elem, s, new_hole_bcid);
564 mesh_boundary_info.add_side(main_elem, main_side, inner_bcid);
565 }
566 // We would like to take this opportunity to identify the hole boundary
567 // We set the boundary id to hole_i + 1 at this stage (the default)
568 mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
569 it->second.second = true;
570 }
571 }
572 }
573
574 // Any sides that do not match the hole surfaces belong to the external boundary
575 // We set the boundary id to 0 at this stage (the default)
576 for (auto & [points, elem_side] : mesh_faces)
577 if (!elem_side.second)
578 {
579 auto [main_elem, main_side] = elem_side.first;
580 mesh_boundary_info.add_side(main_elem, main_side, 0);
581 }
582
583 // Now hole boundary ids are 1,2,..., N
584 // Now external boundary id is 0
585 // We will find the upper bound of the boundary ids of the mesh so that we would not overwrite
586 // things during renumbering (because of the bcids used for stitching, this is different from
587 // end_bcid)
588 const auto temp_bcid_shift = MooseMeshUtils::getNextFreeBoundaryID(*mesh);
589 // If we stitch holes, we want to make sure the netgen mesh has no conflicting boundary ids
590 // with the hole meshes.
591 // OR, if we assign custom boundary ids, we want to make sure no overwriting occurs.
592 // So we shift all these boundary ids by temp_bcid_shift + free_boundary_id
593 // before we assign the new boundary ids
594 if (_stitch_holes.size() || output_boundary_id.size())
596 *mesh, 0, temp_bcid_shift + free_boundary_id);
597 if (_stitch_holes.size() || hole_boundary_ids.size())
598 for (auto hole_i : index_range(_hole_ptrs))
600 *mesh, hole_i + 1, hole_i + 1 + temp_bcid_shift + free_boundary_id);
601
602 // Now we can reassign the boundary ids
603 // If these ids are specified, we will use them
604 // Otherwise, we will use the default ones
605 if (output_boundary_id.size())
607 *mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
608 else
610 *mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
611
612 if (hole_boundary_ids.size())
613 for (auto hole_i : index_range(_hole_ptrs))
615 *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
616 else
617 for (auto hole_i : index_range(_hole_ptrs))
619 *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
620
621 for (auto hole_i : index_range(_hole_ptrs))
622 {
623 UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
624
625 if (_stitch_holes.size() && _stitch_holes[hole_i])
626 {
627 // Retrieve subdomain name map from the mesh to be stitched and insert it into the main
628 // subdomain map
629 const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
630 main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
631
633 {
634 // The main mesh has been serialized early and will last through the end of this MG
635 // If the hole mesh to be stitched is not serialized, it will cause parallelization
636 // issues after combining when the number of processors is large
637 libMesh::MeshSerializer serial_hole(hole_mesh);
638 MooseMeshUtils::copyIntoMesh(*this, *mesh, hole_mesh, false, false, _communicator);
639 }
640 else
641 {
642 std::size_t n_nodes_stitched = mesh->stitch_meshes(hole_mesh,
643 inner_bcid,
644 new_hole_bcid,
645 TOLERANCE,
646 /*clear_stitched_bcids*/ true,
648 use_binary_search);
649
650 if (!n_nodes_stitched)
651 mooseError("Failed to stitch hole mesh ", hole_i, " to new tetrahedralization.");
652 }
653 }
654 }
655 if (doing_stitching && _combined_stitching)
656 {
657 std::size_t n_nodes_stitched = mesh->stitch_surfaces(inner_bcid,
658 new_hole_bcid,
659 TOLERANCE,
660 /*clear_stitched_bcids*/ true,
662 use_binary_search);
663 if (!n_nodes_stitched)
664 mooseError("Failed to stitch combined hole meshes to new tetrahedralization.");
665 }
666
667 // Add user-specified sideset names
668 if (hole_boundary_ids.size())
669 for (auto h : index_range(_hole_ptrs))
670 mesh->get_boundary_info().sideset_name(hole_boundary_ids[h]) = hole_boundaries[h];
671 if (output_boundary_id.size())
672 mesh->get_boundary_info().sideset_name(output_boundary_id[0]) = output_boundary;
673
674 // Check if one SubdomainName is shared by more than one subdomain ids
675 std::set<SubdomainName> main_subdomain_map_name_list;
676 for (auto const & id_name_pair : main_subdomain_map)
677 main_subdomain_map_name_list.emplace(id_name_pair.second);
678 if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
679 paramError("holes", "The hole meshes contain subdomain name maps with conflicts.");
680
681 // We're done with the hole meshes now, and MeshGenerator doesn't
682 // want them anymore either.
683 for (auto & hole_ptr : _hole_ptrs)
684 hole_ptr->reset();
685
686 mesh->unset_is_prepared();
687 return mesh;
688#else
689 mooseError("Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
690 return std::unique_ptr<MeshBase>();
691#endif
692}
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.
const MooseEnum _tet_elem_type
Type of the tetrahedral elements to be generated (TET4, TET10, TET14, or DEFAULT)
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