Line data Source code
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"
15 : #include "MooseMeshElementConversionUtils.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 :
25 : registerMooseObject("MooseApp", XYZDelaunayGenerator);
26 :
27 : namespace std
28 : {
29 : template <>
30 : struct hash<std::tuple<libMesh::Point, libMesh::Point, libMesh::Point>>
31 : {
32 15960 : std::size_t operator()(const std::tuple<libMesh::Point, libMesh::Point, libMesh::Point> & p) const
33 : {
34 15960 : std::size_t seed = 0;
35 15960 : libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<0>(p)));
36 15960 : libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<1>(p)));
37 15960 : libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<2>(p)));
38 15960 : return seed;
39 : }
40 : };
41 :
42 : } // namespace std
43 :
44 : InputParameters
45 3501 : XYZDelaunayGenerator::validParams()
46 : {
47 3501 : InputParameters params = MeshGenerator::validParams();
48 :
49 14004 : MooseEnum algorithm("BINARY EXHAUSTIVE", "BINARY");
50 :
51 14004 : 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 14004 : params.addParam<SubdomainName>("output_subdomain_name",
58 : "Subdomain name to set on new triangles.");
59 :
60 14004 : 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 14004 : 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 10503 : params.addParam<bool>("smooth_triangulation",
70 7002 : false,
71 : "Whether to do Laplacian mesh smoothing on the generated triangles.");
72 14004 : 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 10503 : params.addParam<std::vector<bool>>(
80 7002 : "stitch_holes", std::vector<bool>(), "Whether to stitch to the mesh defining each hole.");
81 10503 : params.addParam<bool>("convert_holes_for_stitching",
82 7002 : false,
83 : "Whether to convert the 3D hole meshes with non-TRI3 surface sides into "
84 : "a compatible form.");
85 :
86 14004 : MooseEnum conversion_method("ALL SURFACE", "ALL");
87 14004 : 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 21006 : 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 10503 : params.addParam<bool>(
101 : "combined_stitching",
102 7002 : 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 14004 : params.addParam<MooseEnum>(
106 : "algorithm",
107 : algorithm,
108 : "Control the use of binary search for the nodes of the stitched surfaces.");
109 21006 : params.renameParam("algorithm", "stitching_algorithm", "12/10/2026");
110 10503 : params.addParam<bool>(
111 7002 : "verbose_stitching", false, "Whether mesh hole stitching should have verbose output.");
112 :
113 3501 : params.addClassDescription(
114 : "Creates tetrahedral 3D meshes within boundaries defined by input meshes.");
115 :
116 7002 : return params;
117 3501 : }
118 :
119 223 : XYZDelaunayGenerator::XYZDelaunayGenerator(const InputParameters & parameters)
120 : : MeshGenerator(parameters),
121 223 : _bdy_ptr(getMesh("boundary")),
122 446 : _desired_volume(getParam<Real>("desired_volume")),
123 223 : _output_subdomain_id(0),
124 446 : _smooth_tri(getParam<bool>("smooth_triangulation")),
125 446 : _hole_ptrs(getMeshes("holes")),
126 446 : _stitch_holes(getParam<std::vector<bool>>("stitch_holes")),
127 446 : _convert_holes_for_stitching(getParam<bool>("convert_holes_for_stitching")),
128 223 : _conversion_method(parameters.get<MooseEnum>("conversion_method")),
129 223 : _combined_stitching(parameters.get<bool>("combined_stitching")),
130 223 : _algorithm(parameters.get<MooseEnum>("algorithm")),
131 446 : _verbose_stitching(parameters.get<bool>("verbose_stitching"))
132 : {
133 223 : if (!_stitch_holes.empty() && _stitch_holes.size() != _hole_ptrs.size())
134 6 : paramError("stitch_holes", "Need one stitch_holes entry per hole, if specified.");
135 :
136 660 : if (isParamValid("hole_boundaries"))
137 : {
138 66 : auto & hole_boundaries = getParam<std::vector<BoundaryName>>("hole_boundaries");
139 33 : if (hole_boundaries.size() != _hole_ptrs.size())
140 6 : paramError("hole_boundaries", "Need one hole_boundaries entry per hole, if specified.");
141 : }
142 :
143 651 : if (isParamSetByUser("conversion_method") && !_convert_holes_for_stitching)
144 0 : paramError(
145 : "conversion_method",
146 : "This parameter is only applicable when convert_holes_for_stitching is set to true.");
147 217 : }
148 :
149 : std::unique_ptr<MeshBase>
150 201 : XYZDelaunayGenerator::generate()
151 : {
152 : #ifdef LIBMESH_HAVE_NETGEN
153 : // Put the boundary mesh in a local pointer
154 : std::unique_ptr<UnstructuredMesh> mesh =
155 201 : 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 201 : mesh->get_boundary_info().clear_boundary_node_ids();
161 :
162 : // Get ready to triangulate its boundary
163 201 : libMesh::NetGenMeshInterface ngint(*mesh);
164 :
165 201 : ngint.smooth_after_generating() = _smooth_tri;
166 :
167 201 : ngint.desired_volume() = _desired_volume;
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 447 : for (const auto hole_i : index_range(_hole_ptrs))
174 : {
175 258 : UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
176 258 : 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 258 : std::set<ElemType> hole_elem_types;
180 258 : std::set<unsigned short> hole_elem_dims;
181 258 : std::vector<std::pair<dof_id_type, unsigned int>> hole_elem_external_sides;
182 8206 : for (auto elem : hole_mesh.element_ptr_range())
183 : {
184 7948 : 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 7948 : if (elem->dim() == 3)
189 39976 : 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 32214 : if (!elem->neighbor_ptr(s))
193 : {
194 4854 : hole_elem_types.emplace(elem->side_ptr(s)->type());
195 4854 : 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 186 : hole_elem_types.emplace(elem->type());
201 258 : }
202 258 : if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
203 12 : paramError(
204 : "holes",
205 : "All elements in a hole mesh must have the same dimension that is either 2D or 3D.");
206 252 : 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 229 : if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
213 : {
214 63 : if (_stitch_holes.size() && _stitch_holes[hole_i] && !_convert_holes_for_stitching)
215 6 : 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 60 : 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 10 : BoundaryID temp_ext_bid = MooseMeshUtils::getNextFreeBoundaryID(hole_mesh);
222 550 : for (const auto & hees : hole_elem_external_sides)
223 540 : hole_mesh.get_boundary_info().add_side(hees.first, hees.second, temp_ext_bid);
224 10 : MooseMeshElementConversionUtils::transitionLayerGenerator(
225 20 : hole_mesh, std::vector<BoundaryName>({std::to_string(temp_ext_bid)}), 1, false);
226 10 : 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 10 : hole_mesh.prepare_for_use();
232 : }
233 : else
234 50 : 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 23 : if (_stitch_holes.size() && _stitch_holes[hole_i])
241 3 : paramError("holes",
242 3 : "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 : }
246 :
247 : std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
248 189 : 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 435 : for (std::unique_ptr<MeshBase> * hole_ptr : _hole_ptrs)
253 : {
254 : // How did we never add a ReplicatedMesh(MeshBase&) constructor in
255 : // libMesh?
256 246 : const UnstructuredMesh & hole = dynamic_cast<UnstructuredMesh &>(**hole_ptr);
257 246 : ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
258 : }
259 :
260 189 : if (!_hole_ptrs.empty())
261 153 : ngint.attach_hole_list(std::move(ngholes));
262 :
263 189 : ngint.triangulate();
264 :
265 567 : if (isParamValid("output_subdomain_name"))
266 : {
267 102 : auto output_subdomain_name = getParam<SubdomainName>("output_subdomain_name");
268 51 : _output_subdomain_id = MooseMeshUtils::getSubdomainID(output_subdomain_name, *mesh);
269 :
270 51 : if (_output_subdomain_id == Elem::invalid_subdomain_id)
271 : {
272 : // We'll probably need to make a new ID, then
273 51 : _output_subdomain_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
274 :
275 : // But check the hole meshes for our output subdomain name too
276 137 : for (auto & hole_ptr : _hole_ptrs)
277 : {
278 86 : auto possible_sbdid = MooseMeshUtils::getSubdomainID(output_subdomain_name, **hole_ptr);
279 : // Huh, it was in one of them
280 86 : if (possible_sbdid != Elem::invalid_subdomain_id)
281 : {
282 0 : _output_subdomain_id = possible_sbdid;
283 0 : break;
284 : }
285 86 : _output_subdomain_id =
286 86 : std::max(_output_subdomain_id, MooseMeshUtils::getNextFreeSubdomainID(**hole_ptr));
287 : }
288 :
289 51 : mesh->subdomain_name(_output_subdomain_id) = output_subdomain_name;
290 : }
291 51 : }
292 :
293 189 : if (_smooth_tri || _output_subdomain_id)
294 27756 : for (auto elem : mesh->element_ptr_range())
295 : {
296 27675 : 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 27675 : if (elem->is_flipped())
304 : {
305 0 : if (_smooth_tri)
306 0 : mooseError("Inverted element found in triangulation.\n"
307 : "Laplacian smoothing can create these at reentrant corners; disable it?");
308 : else
309 0 : mooseError("Unexplained inverted element found in triangulation.\n");
310 : }
311 81 : }
312 :
313 189 : 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 189 : 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 189 : 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 189 : if (_stitch_holes.size())
339 : {
340 279 : for (auto hole_i : index_range(_hole_ptrs))
341 : {
342 166 : if (_stitch_holes[hole_i])
343 : {
344 136 : free_boundary_id =
345 136 : std::max(free_boundary_id, MooseMeshUtils::getNextFreeBoundaryID(**_hole_ptrs[hole_i]));
346 136 : (*_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 189 : boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
355 :
356 378 : auto hole_boundaries = isParamValid("hole_boundaries")
357 189 : ? getParam<std::vector<BoundaryName>>("hole_boundaries")
358 249 : : std::vector<BoundaryName>();
359 : const BoundaryName output_boundary =
360 438 : 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 378 : isParamValid("output_boundary")
366 30 : ? (MooseUtils::isDigits(output_boundary)
367 30 : ? std::vector<BoundaryID>(
368 0 : 1, MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(output_boundary))
369 : : std::vector<BoundaryID>(1, free_boundary_id))
370 249 : : std::vector<BoundaryID>();
371 : // Similarly, set the hole boundary ids one by one
372 189 : std::vector<BoundaryID> hole_boundary_ids;
373 567 : if (isParamValid("hole_boundaries"))
374 90 : for (auto h : index_range(hole_boundaries))
375 60 : hole_boundary_ids.push_back(
376 60 : MooseUtils::isDigits(hole_boundaries[h])
377 20 : ? MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(hole_boundaries[h])
378 40 : : 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 189 : if (hole_boundary_ids.size())
383 90 : for (auto h : index_range(_hole_ptrs))
384 60 : new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(hole_boundary_ids[h] + 1));
385 189 : if (output_boundary_id.size())
386 30 : new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(output_boundary_id[0] + 1));
387 :
388 189 : bool doing_stitching = false;
389 :
390 435 : for (auto hole_i : index_range(_hole_ptrs))
391 : {
392 246 : const MeshBase & hole_mesh = **_hole_ptrs[hole_i];
393 246 : auto & hole_boundary_info = hole_mesh.get_boundary_info();
394 246 : const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
395 :
396 246 : if (!local_hole_bcids.empty())
397 226 : new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(*local_hole_bcids.rbegin() + 1));
398 246 : hole_mesh.comm().max(new_hole_bcid);
399 :
400 246 : if (_stitch_holes.size() && _stitch_holes[hole_i])
401 136 : doing_stitching = true;
402 : }
403 : // Now we can define boundaries to assist with stitching
404 189 : 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 189 : 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 189 : mesh_faces;
422 :
423 15960 : auto sorted_point_tuple = [](Elem & elem, unsigned int side)
424 : {
425 15960 : std::vector<unsigned int> nodes_on_side = elem.nodes_on_side(side);
426 : libmesh_assert_equal_to(nodes_on_side.size(), 3);
427 15960 : std::vector<Point> p(3);
428 63840 : for (auto i : index_range(p))
429 47880 : p[i] = elem.point(nodes_on_side[i]);
430 15960 : if (p[0] < p[1])
431 : {
432 8024 : if (p[1] < p[2])
433 2312 : return std::make_tuple(p[0], p[1], p[2]);
434 5712 : else if (p[0] < p[2])
435 3095 : return std::make_tuple(p[0], p[2], p[1]);
436 : else
437 2617 : return std::make_tuple(p[2], p[0], p[1]);
438 : }
439 : else
440 : {
441 7936 : if (p[0] < p[2])
442 2524 : return std::make_tuple(p[1], p[0], p[2]);
443 5412 : else if (p[1] < p[2])
444 3051 : return std::make_tuple(p[1], p[2], p[0]);
445 : else
446 2361 : return std::make_tuple(p[2], p[1], p[0]);
447 : }
448 15960 : };
449 :
450 189 : if (!_hole_ptrs.empty())
451 34336 : for (auto elem : mesh->element_ptr_range())
452 170915 : for (auto s : make_range(elem->n_sides()))
453 136732 : if (!elem->neighbor_ptr(s))
454 : {
455 9816 : auto points = sorted_point_tuple(*elem, s);
456 : libmesh_assert(!mesh_faces.count(points));
457 9816 : mesh_faces.emplace(points, std::make_pair(std::make_pair(elem, s), false));
458 153 : }
459 :
460 189 : auto & mesh_boundary_info = mesh->get_boundary_info();
461 :
462 : // Define a reference map variable for subdomain map
463 189 : auto & main_subdomain_map = mesh->set_subdomain_name_map();
464 435 : for (auto hole_i : index_range(_hole_ptrs))
465 : {
466 246 : UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
467 246 : 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 246 : 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 11512 : for (auto elem : hole_mesh.element_ptr_range())
478 57370 : for (auto s : make_range(elem->n_sides()))
479 46104 : if (!elem->neighbor_ptr(s))
480 : {
481 6144 : auto points = sorted_point_tuple(*elem, s);
482 6144 : 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 6144 : if (it != mesh_faces.end())
487 : {
488 6144 : auto [main_elem, main_side] = it->second.first;
489 6144 : if (_stitch_holes.size() && _stitch_holes[hole_i])
490 : {
491 3504 : hole_boundary_info.add_side(elem, s, new_hole_bcid);
492 3504 : 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 6144 : mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
497 6144 : it->second.second = true;
498 : }
499 246 : }
500 246 : }
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 10005 : for (auto & [points, elem_side] : mesh_faces)
505 9816 : if (!elem_side.second)
506 : {
507 3672 : auto [main_elem, main_side] = elem_side.first;
508 3672 : 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 189 : 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 189 : if (_stitch_holes.size() || output_boundary_id.size())
523 133 : libMesh::MeshTools::Modification::change_boundary_id(
524 133 : *mesh, 0, temp_bcid_shift + free_boundary_id);
525 189 : if (_stitch_holes.size() || hole_boundary_ids.size())
526 339 : for (auto hole_i : index_range(_hole_ptrs))
527 206 : libMesh::MeshTools::Modification::change_boundary_id(
528 206 : *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 189 : if (output_boundary_id.size())
534 30 : libMesh::MeshTools::Modification::change_boundary_id(
535 30 : *mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
536 : else
537 159 : libMesh::MeshTools::Modification::change_boundary_id(
538 159 : *mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
539 :
540 189 : if (hole_boundary_ids.size())
541 90 : for (auto hole_i : index_range(_hole_ptrs))
542 60 : libMesh::MeshTools::Modification::change_boundary_id(
543 60 : *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
544 : else
545 345 : for (auto hole_i : index_range(_hole_ptrs))
546 186 : libMesh::MeshTools::Modification::change_boundary_id(
547 186 : *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
548 :
549 435 : for (auto hole_i : index_range(_hole_ptrs))
550 : {
551 246 : UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
552 :
553 246 : 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 136 : const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
558 136 : main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
559 :
560 136 : if (_combined_stitching)
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 20 : libMesh::MeshSerializer serial_hole(hole_mesh);
566 20 : MooseMeshUtils::copyIntoMesh(*this, *mesh, hole_mesh, false, false, _communicator);
567 20 : }
568 : else
569 : {
570 116 : 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,
575 116 : _verbose_stitching,
576 : use_binary_search);
577 :
578 116 : if (!n_nodes_stitched)
579 0 : mooseError("Failed to stitch hole mesh ", hole_i, " to new tetrahedralization.");
580 : }
581 : }
582 : }
583 189 : if (doing_stitching && _combined_stitching)
584 : {
585 10 : std::size_t n_nodes_stitched = mesh->stitch_surfaces(inner_bcid,
586 : new_hole_bcid,
587 : TOLERANCE,
588 : /*clear_stitched_bcids*/ true,
589 10 : _verbose_stitching,
590 : use_binary_search);
591 10 : if (!n_nodes_stitched)
592 0 : mooseError("Failed to stitch combined hole meshes to new tetrahedralization.");
593 : }
594 :
595 : // Add user-specified sideset names
596 189 : if (hole_boundary_ids.size())
597 90 : for (auto h : index_range(_hole_ptrs))
598 60 : mesh->get_boundary_info().sideset_name(hole_boundary_ids[h]) = hole_boundaries[h];
599 189 : if (output_boundary_id.size())
600 30 : 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 189 : std::set<SubdomainName> main_subdomain_map_name_list;
604 294 : for (auto const & id_name_pair : main_subdomain_map)
605 105 : main_subdomain_map_name_list.emplace(id_name_pair.second);
606 189 : if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
607 6 : 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 426 : for (auto & hole_ptr : _hole_ptrs)
612 240 : hole_ptr->reset();
613 :
614 186 : mesh->unset_is_prepared();
615 372 : return mesh;
616 : #else
617 : mooseError("Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
618 : return std::unique_ptr<MeshBase>();
619 : #endif
620 196 : }
|