16 #include "libmesh/elem.h" 17 #include "libmesh/int_range.h" 18 #include "libmesh/mesh_modification.h" 19 #include "libmesh/mesh_netgen_interface.h" 20 #include "libmesh/mesh_serializer.h" 21 #include "libmesh/parsed_function.h" 22 #include "libmesh/replicated_mesh.h" 29 struct hash<
std::tuple<libMesh::Point, libMesh::Point, libMesh::Point>>
31 std::size_t
operator()(
const std::tuple<libMesh::Point, libMesh::Point, libMesh::Point> & p)
const 48 MooseEnum algorithm(
"BINARY EXHAUSTIVE",
"BINARY");
52 "The input MeshGenerator defining the output outer boundary. The input mesh (the output mesh " 53 "of the input mesh generator) can either " 54 "include 3D volume elements or 2D surface elements.");
56 params.
addParam<SubdomainName>(
"output_subdomain_name",
57 "Subdomain name to set on new triangles.");
61 "Boundary name to set on new outer boundary. Default ID: 0 if no hole meshes are stitched; " 62 "or maximum boundary ID of all the stitched hole meshes + 1.");
63 params.
addParam<std::vector<BoundaryName>>(
65 "Boundary names to set on holes. Default IDs are numbered up from 1 if no hole meshes are " 66 "stitched; or from maximum boundary ID of all the stitched hole meshes + 2.");
68 params.
addParam<
bool>(
"smooth_triangulation",
70 "Whether to do Laplacian mesh smoothing on the generated triangles.");
71 params.
addParam<std::vector<MeshGeneratorName>>(
74 "The MeshGenerators that create meshes defining the holes. A hole mesh must contain either " 76 "elements where the external surface of the mesh works as the closed manifold that defines " 77 "the hole, or 2D surface elements that form the closed manifold that defines the hole.");
79 "stitch_holes", std::vector<bool>(),
"Whether to stitch to the mesh defining each hole.");
80 params.
addParam<
bool>(
"convert_holes_for_stitching",
82 "Whether to convert the 3D hole meshes with non-TRI3 surface sides into " 83 "all-TET4 meshes to allow stitching.");
89 "Desired (maximum) tetrahedral volume, or 0 to skip uniform refinement");
94 "Control the use of binary search for the nodes of the stitched surfaces.");
96 "verbose_stitching",
false,
"Whether mesh hole stitching should have verbose output.");
99 "Creates tetrahedral 3D meshes within boundaries defined by input meshes.");
106 _bdy_ptr(getMesh(
"boundary")),
107 _desired_volume(getParam<
Real>(
"desired_volume")),
108 _output_subdomain_id(0),
109 _smooth_tri(getParam<bool>(
"smooth_triangulation")),
110 _hole_ptrs(getMeshes(
"holes")),
111 _stitch_holes(getParam<
std::vector<bool>>(
"stitch_holes")),
112 _convert_holes_for_stitching(getParam<bool>(
"convert_holes_for_stitching")),
114 _verbose_stitching(parameters.
get<bool>(
"verbose_stitching"))
117 paramError(
"stitch_holes",
"Need one stitch_holes entry per hole, if specified.");
121 auto & hole_boundaries = getParam<std::vector<BoundaryName>>(
"hole_boundaries");
122 if (hole_boundaries.size() !=
_hole_ptrs.size())
123 paramError(
"hole_boundaries",
"Need one hole_boundaries entry per hole, if specified.");
127 std::unique_ptr<MeshBase>
130 #ifdef LIBMESH_HAVE_NETGEN 132 std::unique_ptr<UnstructuredMesh>
mesh =
138 mesh->get_boundary_info().clear_boundary_node_ids();
153 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
157 std::set<ElemType> hole_elem_types;
158 std::set<unsigned short> hole_elem_dims;
159 for (
auto elem : hole_mesh.element_ptr_range())
161 hole_elem_dims.emplace(elem->dim());
165 if (elem->dim() == 3)
169 if (!elem->neighbor_ptr(s))
170 hole_elem_types.emplace(elem->side_ptr(s)->type());
174 hole_elem_types.emplace(elem->type());
176 if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
179 "All elements in a hole mesh must have the same dimension that is either 2D or 3D.");
180 else if (*hole_elem_dims.begin() == 3)
186 if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
190 "3D hole meshes with non-TRI3 surface elements cannot be stitched without " 191 "converting them to TET4. Consider setting convert_holes_for_stitching=true.");
193 MeshTools::Modification::all_tri(**
_hole_ptrs[hole_i]);
201 "the hole mesh with index " + std::to_string(hole_i) +
202 " is a 2D mesh, for which stitching onto a 3D mesh does not make sense.");
206 std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
207 std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
211 for (std::unique_ptr<MeshBase> * hole_ptr :
_hole_ptrs)
215 const UnstructuredMesh & hole =
dynamic_cast<UnstructuredMesh &
>(**hole_ptr);
216 ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
226 auto output_subdomain_name = getParam<SubdomainName>(
"output_subdomain_name");
239 if (possible_sbdid != Elem::invalid_subdomain_id)
253 for (
auto elem :
mesh->element_ptr_range())
262 if (elem->is_flipped())
265 mooseError(
"Inverted element found in triangulation.\n" 266 "Laplacian smoothing can create these at reentrant corners; disable it?");
268 mooseError(
"Unexplained inverted element found in triangulation.\n");
272 const bool use_binary_search = (
_algorithm ==
"BINARY");
316 ? getParam<std::vector<BoundaryName>>(
"hole_boundaries")
317 : std::vector<BoundaryName>();
318 const BoundaryName output_boundary =
319 isParamValid(
"output_boundary") ? getParam<BoundaryName>(
"output_boundary") : BoundaryName();
323 const std::vector<BoundaryID> output_boundary_id =
326 ? std::vector<BoundaryID>(
327 1, MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(output_boundary))
328 : std::vector<BoundaryID>(1, free_boundary_id))
329 : std::vector<BoundaryID>();
331 std::vector<BoundaryID> hole_boundary_ids;
334 hole_boundary_ids.push_back(
336 ? MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(hole_boundaries[h])
337 : h + 1 + free_boundary_id);
341 if (hole_boundary_ids.size())
344 if (output_boundary_id.size())
347 bool doing_stitching =
false;
351 const MeshBase & hole_mesh = **
_hole_ptrs[hole_i];
352 auto & hole_boundary_info = hole_mesh.get_boundary_info();
353 const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
355 if (!local_hole_bcids.empty())
357 hole_mesh.comm().max(new_hole_bcid);
360 doing_stitching =
true;
378 std::unordered_map<std::tuple<Point, Point, Point>,
379 std::pair<std::pair<Elem *, unsigned int>,
bool>>
382 auto sorted_point_tuple = [](Elem & elem,
unsigned int side)
384 std::vector<unsigned int> nodes_on_side = elem.nodes_on_side(side);
385 libmesh_assert_equal_to(nodes_on_side.size(), 3);
386 std::vector<Point> p(3);
388 p[i] = elem.point(nodes_on_side[i]);
392 return std::make_tuple(p[0], p[1], p[2]);
393 else if (p[0] < p[2])
394 return std::make_tuple(p[0], p[2], p[1]);
396 return std::make_tuple(p[2], p[0], p[1]);
401 return std::make_tuple(p[1], p[0], p[2]);
402 else if (p[1] < p[2])
403 return std::make_tuple(p[1], p[2], p[0]);
405 return std::make_tuple(p[2], p[1], p[0]);
410 for (
auto elem :
mesh->element_ptr_range())
412 if (!elem->neighbor_ptr(s))
414 auto points = sorted_point_tuple(*elem, s);
416 mesh_faces.emplace(points, std::make_pair(std::make_pair(elem, s),
false));
419 auto & mesh_boundary_info =
mesh->get_boundary_info();
422 auto & main_subdomain_map =
mesh->set_subdomain_name_map();
425 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
426 auto & hole_boundary_info = hole_mesh.get_boundary_info();
436 for (
auto elem : hole_mesh.element_ptr_range())
438 if (!elem->neighbor_ptr(s))
440 auto points = sorted_point_tuple(*elem, s);
441 auto it = mesh_faces.find(points);
445 if (it != mesh_faces.end())
447 auto [main_elem, main_side] = it->second.first;
450 hole_boundary_info.add_side(elem, s, new_hole_bcid);
451 mesh_boundary_info.add_side(main_elem, main_side, inner_bcid);
455 mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
456 it->second.second =
true;
463 for (
auto & [points, elem_side] : mesh_faces)
464 if (!elem_side.second)
466 auto [main_elem, main_side] = elem_side.first;
467 mesh_boundary_info.add_side(main_elem, main_side, 0);
483 *
mesh, 0, temp_bcid_shift + free_boundary_id);
487 *
mesh, hole_i + 1, hole_i + 1 + temp_bcid_shift + free_boundary_id);
492 if (output_boundary_id.size())
494 *
mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
497 *
mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
499 if (hole_boundary_ids.size())
502 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
506 *
mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
510 UnstructuredMesh & hole_mesh =
dynamic_cast<UnstructuredMesh &
>(**
_hole_ptrs[hole_i]);
516 const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
517 main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
519 std::size_t n_nodes_stitched =
mesh->stitch_meshes(hole_mesh,
527 if (!n_nodes_stitched)
528 mooseError(
"Failed to stitch hole mesh ", hole_i,
" to new tetrahedralization.");
532 if (hole_boundary_ids.size())
534 mesh->get_boundary_info().sideset_name(hole_boundary_ids[h]) = hole_boundaries[h];
535 if (output_boundary_id.size())
536 mesh->get_boundary_info().sideset_name(output_boundary_id[0]) = output_boundary;
539 std::set<SubdomainName> main_subdomain_map_name_list;
540 for (
auto const & id_name_pair : main_subdomain_map)
541 main_subdomain_map_name_list.emplace(id_name_pair.second);
542 if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
543 paramError(
"holes",
"The hole meshes contain subdomain name maps with conflicts.");
550 mesh->set_isnt_prepared();
553 mooseError(
"Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
554 return std::unique_ptr<MeshBase>();
const Real _desired_volume
Desired volume of output tetrahedra.
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 all-TET4 meshes.
std::size_t operator()(const std::tuple< libMesh::Point, libMesh::Point, libMesh::Point > &p) const
T * get(const std::unique_ptr< T > &u)
The MooseUtils::get() specializations are used to support making forwards-compatible code changes fro...
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.
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()
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...
bool isDigits(const std::string &str)
Courtesy https://stackoverflow.com/a/8889045 and https://en.cppreference.com/w/cpp/string/byte/isdigi...
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...
MeshGenerators are objects that can modify or add to an existing mesh.
auto index_range(const T &sizable)
SubdomainID _output_subdomain_id
What subdomain_id to set on the generated tetrahedra.