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" 30 struct hash<
std::tuple<libMesh::Point, libMesh::Point, libMesh::Point>>
32 std::size_t
operator()(
const std::tuple<libMesh::Point, libMesh::Point, libMesh::Point> & p)
const 49 MooseEnum algorithm(
"BINARY EXHAUSTIVE",
"BINARY");
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.");
57 params.
addParam<SubdomainName>(
"output_subdomain_name",
58 "Subdomain name to set on new triangles.");
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>>(
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.");
69 params.
addParam<
bool>(
"smooth_triangulation",
71 "Whether to do Laplacian mesh smoothing on the generated triangles.");
72 params.
addParam<std::vector<MeshGeneratorName>>(
75 "The MeshGenerators that create meshes defining the holes. A hole mesh must contain either " 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.");
80 "stitch_holes", std::vector<bool>(),
"Whether to stitch to the mesh defining each hole.");
81 params.
addParam<
bool>(
"convert_holes_for_stitching",
83 "Whether to convert the 3D hole meshes with non-TRI3 surface sides into " 84 "a compatible form.");
86 MooseEnum conversion_method(
"ALL SURFACE",
"ALL");
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.");
97 "Whether to stitch all holes in one combined stitching step. This is efficient if a great " 98 "number of holes are to be stitched. But it is hard to debug if problems arise.");
104 "Desired (maximum) tetrahedral volume, or 0 to skip uniform refinement");
109 "Control the use of binary search for the nodes of the stitched surfaces.");
111 "verbose_stitching",
false,
"Whether mesh hole stitching should have verbose output.");
114 "Creates tetrahedral 3D meshes within boundaries defined by input meshes.");
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")),
131 _verbose_stitching(parameters.
get<bool>(
"verbose_stitching"))
134 paramError(
"stitch_holes",
"Need one stitch_holes entry per hole, if specified.");
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.");
146 "This parameter is only applicable when convert_holes_for_stitching is set to true.");
149 std::unique_ptr<MeshBase>
152 #ifdef LIBMESH_HAVE_NETGEN 154 std::unique_ptr<UnstructuredMesh>
mesh =
160 mesh->get_boundary_info().clear_boundary_node_ids();
175 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
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())
184 hole_elem_dims.emplace(elem->dim());
188 if (elem->dim() == 3)
192 if (!elem->neighbor_ptr(s))
194 hole_elem_types.emplace(elem->side_ptr(s)->type());
195 hole_elem_external_sides.emplace_back(elem->id(), s);
200 hole_elem_types.emplace(elem->type());
202 if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
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)
212 if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
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.");
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);
231 hole_mesh.prepare_for_use();
234 MeshTools::Modification::all_tri(**
_hole_ptrs[hole_i]);
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.");
247 std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
248 std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
252 for (std::unique_ptr<MeshBase> * hole_ptr :
_hole_ptrs)
256 const UnstructuredMesh & hole =
dynamic_cast<UnstructuredMesh &
>(**hole_ptr);
257 ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
267 auto output_subdomain_name = getParam<SubdomainName>(
"output_subdomain_name");
280 if (possible_sbdid != Elem::invalid_subdomain_id)
294 for (
auto elem :
mesh->element_ptr_range())
303 if (elem->is_flipped())
306 mooseError(
"Inverted element found in triangulation.\n" 307 "Laplacian smoothing can create these at reentrant corners; disable it?");
309 mooseError(
"Unexplained inverted element found in triangulation.\n");
313 const bool use_binary_search = (
_algorithm ==
"BINARY");
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();
364 const std::vector<BoundaryID> output_boundary_id =
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>();
372 std::vector<BoundaryID> hole_boundary_ids;
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);
382 if (hole_boundary_ids.size())
385 if (output_boundary_id.size())
388 bool doing_stitching =
false;
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();
396 if (!local_hole_bcids.empty())
398 hole_mesh.comm().max(new_hole_bcid);
401 doing_stitching =
true;
419 std::unordered_map<std::tuple<Point, Point, Point>,
420 std::pair<std::pair<Elem *, unsigned int>,
bool>>
423 auto sorted_point_tuple = [](Elem & elem,
unsigned int side)
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);
429 p[i] = elem.point(nodes_on_side[i]);
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]);
437 return std::make_tuple(p[2], p[0], p[1]);
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]);
446 return std::make_tuple(p[2], p[1], p[0]);
451 for (
auto elem :
mesh->element_ptr_range())
453 if (!elem->neighbor_ptr(s))
455 auto points = sorted_point_tuple(*elem, s);
457 mesh_faces.emplace(points, std::make_pair(std::make_pair(elem, s),
false));
460 auto & mesh_boundary_info =
mesh->get_boundary_info();
463 auto & main_subdomain_map =
mesh->set_subdomain_name_map();
466 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
467 auto & hole_boundary_info = hole_mesh.get_boundary_info();
477 for (
auto elem : hole_mesh.element_ptr_range())
479 if (!elem->neighbor_ptr(s))
481 auto points = sorted_point_tuple(*elem, s);
482 auto it = mesh_faces.find(points);
486 if (it != mesh_faces.end())
488 auto [main_elem, main_side] = it->second.first;
491 hole_boundary_info.add_side(elem, s, new_hole_bcid);
492 mesh_boundary_info.add_side(main_elem, main_side, inner_bcid);
496 mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
497 it->second.second =
true;
504 for (
auto & [points, elem_side] : mesh_faces)
505 if (!elem_side.second)
507 auto [main_elem, main_side] = elem_side.first;
508 mesh_boundary_info.add_side(main_elem, main_side, 0);
524 *
mesh, 0, temp_bcid_shift + free_boundary_id);
528 *
mesh, hole_i + 1, hole_i + 1 + temp_bcid_shift + free_boundary_id);
533 if (output_boundary_id.size())
535 *
mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
538 *
mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
540 if (hole_boundary_ids.size())
543 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
547 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
551 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
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());
570 std::size_t n_nodes_stitched =
mesh->stitch_meshes(hole_mesh,
578 if (!n_nodes_stitched)
579 mooseError(
"Failed to stitch hole mesh ", hole_i,
" to new tetrahedralization.");
585 std::size_t n_nodes_stitched =
mesh->stitch_surfaces(inner_bcid,
591 if (!n_nodes_stitched)
592 mooseError(
"Failed to stitch combined hole meshes to new tetrahedralization.");
596 if (hole_boundary_ids.size())
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;
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.");
614 mesh->set_isnt_prepared();
617 mooseError(
"Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
618 return std::unique_ptr<MeshBase>();
const Real _desired_volume
Desired volume of output tetrahedra.
const bool _combined_stitching
Whether to stitch all holes in one combined stitching step.
void paramError(const std::string ¶m, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
void hash_combine(std::size_t &seed, const T &value)
const bool _convert_holes_for_stitching
Whether to convert 3D hole meshes with non-TRI3 surface elements into a compatible form...
std::size_t operator()(const std::tuple< libMesh::Point, libMesh::Point, libMesh::Point > &p) const
XYZDelaunayGenerator(const InputParameters ¶meters)
const Parallel::Communicator & comm() const
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
virtual void triangulate() override
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
const Parallel::Communicator & _communicator
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
auto max(const L &left, const R &right)
const bool _verbose_stitching
Whether mesh stitching should have verbose output.
boundary_id_type BoundaryID
const MooseEnum _algorithm
Type of algorithm used to find matching nodes (binary or exhaustive)
registerMooseObject("MooseApp", XYZDelaunayGenerator)
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
static InputParameters validParams()
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.
const bool _smooth_tri
Whether to do Laplacian mesh smoothing on the generated triangles.
static InputParameters validParams()
const std::vector< std::unique_ptr< MeshBase > * > _hole_ptrs
Holds pointers to the pointers to input meshes defining holes.
const std::vector< bool > _stitch_holes
Whether to stitch to the mesh defining each hole.
void attach_hole_list(std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh >>> holes)
Generates a tetrahedral mesh, based on an input mesh defining the outer boundary and an optional set ...
std::unique_ptr< MeshBase > & _bdy_ptr
Input mesh defining the boundary to triangulate within.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void max(const T &r, T &o, Request &req) const
IntRange< T > make_range(T beg, T end)
bool & smooth_after_generating()
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...
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
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...
const MooseEnum _conversion_method
Method to convert 3D hole meshes into compatible meshes.
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.
MeshGenerators are objects that can modify or add to an existing 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.
auto index_range(const T &sizable)
const Elem & get(const ElemType type_in)
SubdomainID _output_subdomain_id
What subdomain_id to set on the generated tetrahedra.