166#ifdef LIBMESH_HAVE_NETGEN
168 std::unique_ptr<UnstructuredMesh>
mesh =
169 dynamic_pointer_cast<UnstructuredMesh>(std::move(
_bdy_ptr));
174 mesh->get_boundary_info().clear_boundary_node_ids();
183 const bool second_order_output = (tet_type !=
libMesh::TET4);
198 for (
const auto hole_i : index_range(
_hole_ptrs))
200 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
204 std::set<ElemType> hole_elem_types;
205 std::set<unsigned short> hole_elem_dims;
206 std::vector<std::pair<dof_id_type, unsigned int>> hole_elem_external_sides;
210 bool hole_has_midpoints =
false;
211 for (
auto elem : hole_mesh.element_ptr_range())
213 hole_elem_dims.emplace(elem->dim());
215 hole_has_midpoints =
true;
219 if (elem->dim() == 3)
220 for (
auto s : make_range(elem->n_sides()))
223 if (!elem->neighbor_ptr(s))
225 hole_elem_types.emplace(elem->side_ptr(s)->type());
226 hole_elem_external_sides.emplace_back(elem->id(), s);
231 hole_elem_types.emplace(elem->type());
233 if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
236 "All elements in a hole mesh must have the same dimension that is either 2D or 3D.");
237 else if (*hole_elem_dims.begin() == 3)
242 if (!second_order_output && stitching_this_hole && hole_has_midpoints)
245 "Cannot use first order elements with stitched quadratic element holes. Please "
246 "specify a higher-order tet_element_type or reduce the order of the hole inputs.");
254 if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
258 "3D hole meshes with non-TRI3 surface elements cannot be stitched without "
259 "converting them to TET4. Consider setting convert_holes_for_stitching=true.");
263 std::set<subdomain_id_type> hole_mesh_original_subdomain_ids;
264 for (
auto elem_it = hole_mesh.active_elements_begin();
265 elem_it != hole_mesh.active_elements_end();
267 hole_mesh_original_subdomain_ids.emplace((*elem_it)->subdomain_id());
271 for (
const auto & hees : hole_elem_external_sides)
272 hole_mesh.get_boundary_info().add_side(hees.first, hees.second, temp_ext_bid);
274 hole_mesh, std::vector<BoundaryName>({std::to_string(temp_ext_bid)}), 1,
false);
275 hole_mesh.get_boundary_info().remove_id(temp_ext_bid);
282 hole_mesh_original_subdomain_ids,
283 *hole_mesh_original_subdomain_ids.rbegin() + 1,
284 getParam<SubdomainName>(
"converted_tet_element_subdomain_name_suffix"),
285 getParam<SubdomainName>(
"converted_pyramid_element_subdomain_name_suffix"));
287 catch (
const std::exception & e)
289 if (((std::string)e.what()).compare(26, 4,
"TET4") == 0)
290 paramError(
"converted_tet_element_subdomain_name_suffix", e.what());
292 paramError(
"converted_pyramid_element_subdomain_name_suffix", e.what());
299 hole_mesh.prepare_for_use();
302 MeshTools::Modification::all_tri(**
_hole_ptrs[hole_i]);
309 if (second_order_output && stitching_this_hole)
311 if (!hole_mesh.is_prepared())
312 hole_mesh.prepare_for_use();
315 for (
const auto elem : hole_mesh.element_ptr_range())
317 cur_type = elem->type();
321 hole_mesh.all_second_order();
323 hole_mesh.all_complete_order();
331 "the hole mesh with index " + std::to_string(hole_i) +
332 " is a 2D mesh, for which stitching onto a 3D mesh does not make sense.");
336 std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
337 std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
341 for (std::unique_ptr<MeshBase> * hole_ptr :
_hole_ptrs)
345 const UnstructuredMesh & hole =
dynamic_cast<UnstructuredMesh &
>(**hole_ptr);
346 ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
348#ifdef LIBMESH_ENABLE_UNIQUE_ID
355 UnstructuredMesh & hole_copy = *ngholes->back();
356 hole_copy.set_next_unique_id(hole_copy.parallel_max_unique_id());
367 auto output_subdomain_name = getParam<SubdomainName>(
"output_subdomain_name");
380 if (possible_sbdid != Elem::invalid_subdomain_id)
394 for (
auto elem :
mesh->element_ptr_range())
396 mooseAssert(elem->type() == tet_type,
"Unexpected element type found in triangulation");
405 if (elem->is_flipped())
408 mooseError(
"Inverted element found in triangulation.\n"
409 "Laplacian smoothing can create these at reentrant corners; disable it?");
411 mooseError(
"Unexplained inverted element found in triangulation.\n");
416 mesh->unset_has_cached_elem_data();
418 const bool use_binary_search = (
_algorithm ==
"BINARY");
435 const boundary_id_type end_bcid =
_hole_ptrs.size() + 1;
459 boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
462 ? getParam<std::vector<BoundaryName>>(
"hole_boundaries")
463 : std::vector<BoundaryName>();
464 const BoundaryName output_boundary =
465 isParamValid(
"output_boundary") ? getParam<BoundaryName>(
"output_boundary") : BoundaryName();
469 const std::vector<BoundaryID> output_boundary_id =
471 ? (MooseUtils::isDigits(output_boundary)
472 ? std::vector<BoundaryID>(
473 1, MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(output_boundary))
474 : std::vector<BoundaryID>(1, free_boundary_id))
475 : std::vector<BoundaryID>();
477 std::vector<BoundaryID> hole_boundary_ids;
479 for (
auto h : index_range(hole_boundaries))
480 hole_boundary_ids.push_back(
481 MooseUtils::isDigits(hole_boundaries[h])
482 ? MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(hole_boundaries[h])
483 : h + 1 + free_boundary_id);
487 if (hole_boundary_ids.size())
489 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(hole_boundary_ids[h] + 1));
490 if (output_boundary_id.size())
491 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(output_boundary_id[0] + 1));
493 bool doing_stitching =
false;
497 const MeshBase & hole_mesh = **
_hole_ptrs[hole_i];
498 auto & hole_boundary_info = hole_mesh.get_boundary_info();
499 const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
501 if (!local_hole_bcids.empty())
502 new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(*local_hole_bcids.rbegin() + 1));
503 hole_mesh.comm().max(new_hole_bcid);
506 doing_stitching =
true;
509 const boundary_id_type inner_bcid = new_hole_bcid + 1;
524 std::unordered_map<std::tuple<Point, Point, Point>,
525 std::pair<std::pair<Elem *, unsigned int>,
bool>>
528 auto sorted_point_tuple = [](Elem & elem,
unsigned int side)
533 const std::vector<unsigned int> nodes_on_side = elem.nodes_on_side(side);
534 std::vector<Point> p;
536 for (
const auto n : nodes_on_side)
537 if (elem.is_vertex(n))
538 p.push_back(elem.point(n));
539 libmesh_assert_equal_to(p.size(), 3);
543 return std::make_tuple(p[0], p[1], p[2]);
544 else if (p[0] < p[2])
545 return std::make_tuple(p[0], p[2], p[1]);
547 return std::make_tuple(p[2], p[0], p[1]);
552 return std::make_tuple(p[1], p[0], p[2]);
553 else if (p[1] < p[2])
554 return std::make_tuple(p[1], p[2], p[0]);
556 return std::make_tuple(p[2], p[1], p[0]);
561 for (
auto elem :
mesh->element_ptr_range())
562 for (
auto s : make_range(elem->n_sides()))
563 if (!elem->neighbor_ptr(s))
565 auto points = sorted_point_tuple(*elem, s);
566 libmesh_assert(!mesh_faces.count(points));
567 mesh_faces.emplace(points, std::make_pair(std::make_pair(elem, s),
false));
570 auto & mesh_boundary_info =
mesh->get_boundary_info();
573 auto & main_subdomain_map =
mesh->set_subdomain_name_map();
576 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
577 auto & hole_boundary_info = hole_mesh.get_boundary_info();
587 for (
auto elem : hole_mesh.element_ptr_range())
588 for (
auto s : make_range(elem->n_sides()))
589 if (!elem->neighbor_ptr(s))
591 auto points = sorted_point_tuple(*elem, s);
592 auto it = mesh_faces.find(points);
596 if (it != mesh_faces.end())
598 auto [main_elem, main_side] = it->second.first;
601 hole_boundary_info.add_side(elem, s, new_hole_bcid);
602 mesh_boundary_info.add_side(main_elem, main_side, inner_bcid);
606 mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
607 it->second.second =
true;
614 for (
auto & [points, elem_side] : mesh_faces)
615 if (!elem_side.second)
617 auto [main_elem, main_side] = elem_side.first;
618 mesh_boundary_info.add_side(main_elem, main_side, 0);
634 *
mesh, 0, temp_bcid_shift + free_boundary_id);
638 *
mesh, hole_i + 1, hole_i + 1 + temp_bcid_shift + free_boundary_id);
643 if (output_boundary_id.size())
645 *
mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
648 *
mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
650 if (hole_boundary_ids.size())
653 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
657 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
661 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
667 const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
668 main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
680 std::size_t n_nodes_stitched =
mesh->stitch_meshes(hole_mesh,
688 if (!n_nodes_stitched)
689 mooseError(
"Failed to stitch hole mesh ", hole_i,
" to new tetrahedralization.");
695 std::size_t n_nodes_stitched =
mesh->stitch_surfaces(inner_bcid,
701 if (!n_nodes_stitched)
702 mooseError(
"Failed to stitch combined hole meshes to new tetrahedralization.");
706 if (hole_boundary_ids.size())
708 mesh->get_boundary_info().sideset_name(hole_boundary_ids[h]) = hole_boundaries[h];
709 if (output_boundary_id.size())
710 mesh->get_boundary_info().sideset_name(output_boundary_id[0]) = output_boundary;
713 std::set<SubdomainName> main_subdomain_map_name_list;
714 for (
auto const & id_name_pair : main_subdomain_map)
715 main_subdomain_map_name_list.emplace(id_name_pair.second);
716 if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
717 paramError(
"holes",
"The hole meshes contain subdomain name maps with conflicts.");
724 mesh->unset_is_prepared();
727 mooseError(
"Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
728 return std::unique_ptr<MeshBase>();