157#ifdef LIBMESH_HAVE_NETGEN
159 std::unique_ptr<UnstructuredMesh>
mesh =
160 dynamic_pointer_cast<UnstructuredMesh>(std::move(
_bdy_ptr));
165 mesh->get_boundary_info().clear_boundary_node_ids();
174 const bool second_order_output = (tet_type !=
libMesh::TET4);
189 for (
const auto hole_i : index_range(
_hole_ptrs))
191 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
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;
201 bool hole_has_midpoints =
false;
202 for (
auto elem : hole_mesh.element_ptr_range())
204 hole_elem_dims.emplace(elem->dim());
206 hole_has_midpoints =
true;
210 if (elem->dim() == 3)
211 for (
auto s : make_range(elem->n_sides()))
214 if (!elem->neighbor_ptr(s))
216 hole_elem_types.emplace(elem->side_ptr(s)->type());
217 hole_elem_external_sides.emplace_back(elem->id(), s);
222 hole_elem_types.emplace(elem->type());
224 if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
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)
233 if (!second_order_output && stitching_this_hole && hole_has_midpoints)
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.");
245 if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
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.");
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);
264 hole_mesh.prepare_for_use();
267 MeshTools::Modification::all_tri(**
_hole_ptrs[hole_i]);
274 if (second_order_output && stitching_this_hole)
276 if (!hole_mesh.is_prepared())
277 hole_mesh.prepare_for_use();
280 for (
const auto elem : hole_mesh.element_ptr_range())
282 cur_type = elem->type();
286 hole_mesh.all_second_order();
288 hole_mesh.all_complete_order();
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.");
301 std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
302 std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
306 for (std::unique_ptr<MeshBase> * hole_ptr :
_hole_ptrs)
310 const UnstructuredMesh & hole =
dynamic_cast<UnstructuredMesh &
>(**hole_ptr);
311 ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
313#ifdef LIBMESH_ENABLE_UNIQUE_ID
320 UnstructuredMesh & hole_copy = *ngholes->back();
321 hole_copy.set_next_unique_id(hole_copy.parallel_max_unique_id());
332 auto output_subdomain_name = getParam<SubdomainName>(
"output_subdomain_name");
345 if (possible_sbdid != Elem::invalid_subdomain_id)
359 for (
auto elem :
mesh->element_ptr_range())
361 mooseAssert(elem->type() == tet_type,
"Unexpected element type found in triangulation");
370 if (elem->is_flipped())
373 mooseError(
"Inverted element found in triangulation.\n"
374 "Laplacian smoothing can create these at reentrant corners; disable it?");
376 mooseError(
"Unexplained inverted element found in triangulation.\n");
380 const bool use_binary_search = (
_algorithm ==
"BINARY");
397 const boundary_id_type end_bcid =
_hole_ptrs.size() + 1;
421 boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
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();
431 const std::vector<BoundaryID> output_boundary_id =
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>();
439 std::vector<BoundaryID> hole_boundary_ids;
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);
449 if (hole_boundary_ids.size())
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));
455 bool doing_stitching =
false;
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();
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);
468 doing_stitching =
true;
471 const boundary_id_type inner_bcid = new_hole_bcid + 1;
486 std::unordered_map<std::tuple<Point, Point, Point>,
487 std::pair<std::pair<Elem *, unsigned int>,
bool>>
490 auto sorted_point_tuple = [](Elem & elem,
unsigned int side)
495 const std::vector<unsigned int> nodes_on_side = elem.nodes_on_side(side);
496 std::vector<Point> p;
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);
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]);
509 return std::make_tuple(p[2], p[0], p[1]);
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]);
518 return std::make_tuple(p[2], p[1], p[0]);
523 for (
auto elem :
mesh->element_ptr_range())
524 for (
auto s : make_range(elem->n_sides()))
525 if (!elem->neighbor_ptr(s))
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));
532 auto & mesh_boundary_info =
mesh->get_boundary_info();
535 auto & main_subdomain_map =
mesh->set_subdomain_name_map();
538 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
539 auto & hole_boundary_info = hole_mesh.get_boundary_info();
549 for (
auto elem : hole_mesh.element_ptr_range())
550 for (
auto s : make_range(elem->n_sides()))
551 if (!elem->neighbor_ptr(s))
553 auto points = sorted_point_tuple(*elem, s);
554 auto it = mesh_faces.find(points);
558 if (it != mesh_faces.end())
560 auto [main_elem, main_side] = it->second.first;
563 hole_boundary_info.add_side(elem, s, new_hole_bcid);
564 mesh_boundary_info.add_side(main_elem, main_side, inner_bcid);
568 mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
569 it->second.second =
true;
576 for (
auto & [points, elem_side] : mesh_faces)
577 if (!elem_side.second)
579 auto [main_elem, main_side] = elem_side.first;
580 mesh_boundary_info.add_side(main_elem, main_side, 0);
596 *
mesh, 0, temp_bcid_shift + free_boundary_id);
600 *
mesh, hole_i + 1, hole_i + 1 + temp_bcid_shift + free_boundary_id);
605 if (output_boundary_id.size())
607 *
mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
610 *
mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
612 if (hole_boundary_ids.size())
615 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
619 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
623 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
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());
642 std::size_t n_nodes_stitched =
mesh->stitch_meshes(hole_mesh,
650 if (!n_nodes_stitched)
651 mooseError(
"Failed to stitch hole mesh ", hole_i,
" to new tetrahedralization.");
657 std::size_t n_nodes_stitched =
mesh->stitch_surfaces(inner_bcid,
663 if (!n_nodes_stitched)
664 mooseError(
"Failed to stitch combined hole meshes to new tetrahedralization.");
668 if (hole_boundary_ids.size())
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;
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.");
686 mesh->unset_is_prepared();
689 mooseError(
"Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
690 return std::unique_ptr<MeshBase>();