14 #include "libmesh/mesh_tools.h" 15 #include "libmesh/mesh_modification.h" 16 #include "libmesh/face_c0polygon.h" 26 "Mesh generator to perform various improvement / fixing operations on an input mesh");
28 "Name of the mesh generator providing the mesh");
30 params.
addParam<
bool>(
"fix_node_overlap",
false,
"Whether to merge overlapping nodes");
32 "node_overlap_tol", 1e-8,
"Absolute tolerance for merging overlapping nodes");
35 "fix_elements_orientation",
false,
"Whether to flip elements with negative volumes");
37 params.
addParam<
bool>(
"separate_blocks_by_element_types",
39 "Create new blocks if multiple element types are present in a block");
41 params.
addParam<
bool>(
"merge_boundary_ids_with_same_name",
43 "Merge boundaries if they have the same name but different boundary IDs");
46 "renumber_contiguously",
48 "Whether to renumber the elements of the mesh so the numbering is contiguous");
51 "split_nonconvex_polygons",
false,
"Split non-convex polygons to form convex ones");
58 _input(getMesh(
"input")),
59 _fix_overlapping_nodes(getParam<bool>(
"fix_node_overlap")),
60 _node_overlap_tol(getParam<
Real>(
"node_overlap_tol")),
61 _fix_element_orientation(getParam<bool>(
"fix_elements_orientation")),
62 _elem_type_separation(getParam<bool>(
"separate_blocks_by_element_types")),
63 _boundary_id_merge(getParam<bool>(
"merge_boundary_ids_with_same_name")),
64 _split_nonconvex_polygons(getParam<bool>(
"split_nonconvex_polygons"))
68 mooseError(
"No specific item to fix. Are any of the parameters misspelled?");
71 std::unique_ptr<MeshBase>
74 std::unique_ptr<MeshBase>
mesh = std::move(
_input);
79 mesh->prepare_for_use();
82 if (!
mesh->is_serial())
83 mooseError(
"MeshRepairGenerator requires a serial mesh. The mesh should not be distributed.");
90 MeshTools::Modification::orient_elements(*
mesh);
101 if (getParam<bool>(
"renumber_contiguously"))
103 const auto prev_status =
mesh->allow_renumbering();
104 mesh->allow_renumbering(
true);
105 mesh->renumber_nodes_and_elements();
106 mesh->allow_renumbering(prev_status);
113 mesh->unset_is_prepared();
120 unsigned int num_fixed_nodes = 0;
121 auto pl =
mesh->sub_point_locator();
124 std::unordered_set<dof_id_type> nodes_removed;
126 for (
auto & node :
mesh->node_ptr_range())
129 if (nodes_removed.count(node->id()))
133 std::set<const Elem *> elements;
134 (*pl)(*node, elements);
136 for (
auto & elem : elements)
139 for (
auto & elem_node : elem->node_ref_range())
141 if (node->id() == elem_node.id())
149 for (
auto & elem_node : elem->node_ref_range())
151 if (elem_node.id() == node->id())
155 const auto x_node = (*node)(0);
156 const auto x_elem_node = elem_node(0);
157 const auto y_node = (*node)(1);
158 const auto y_elem_node = elem_node(1);
159 const auto z_node = (*node)(2);
160 const auto z_elem_node = elem_node(2);
162 if (MooseUtils::absoluteFuzzyEqual(x_node, x_elem_node, tol) &&
163 MooseUtils::absoluteFuzzyEqual(y_node, y_elem_node, tol) &&
164 MooseUtils::absoluteFuzzyEqual(z_node, z_elem_node, tol))
169 _console <<
"Two overlapping nodes in element " << elem->id() <<
" right by " 170 << elem->vertex_average() <<
".\n They will not be stitched" << std::endl;
176 const_cast<Elem *
>(elem)->set_node(elem->get_node_index(&elem_node), node);
177 nodes_removed.insert(elem_node.id());
180 if (num_fixed_nodes < 10)
181 _console <<
"Stitching nodes " << *node <<
" and " << elem_node
183 else if (num_fixed_nodes == 10)
184 _console <<
"Node stitching will now proceed silently." << std::endl;
190 _console <<
"Number of overlapping nodes which got merged: " << num_fixed_nodes << std::endl;
191 if (
mesh->allow_renumbering())
192 mesh->renumber_nodes_and_elements();
195 mesh->remove_orphaned_nodes();
196 mesh->update_parallel_id_counts();
203 std::set<subdomain_id_type> ids;
204 mesh->subdomain_ids(ids);
206 for (
const auto id : ids)
210 std::set<ElemType> types;
212 for (
auto & elem :
mesh->active_subdomain_elements_ptr_range(
id))
213 types.insert(elem->type());
218 if (types.size() > 1)
221 for (
const auto type : types)
223 auto new_id = next_block_id + i++;
228 for (
auto elem :
mesh->active_subdomain_elements_ptr_range(
id))
229 if (elem->type() ==
type)
230 elem->subdomain_id() = new_id;
240 unsigned int num_polygons = 0;
241 unsigned int num_nonconvex = 0;
242 unsigned int num_triangulated = 0;
243 std::vector<Elem *> elems_to_delete;
244 std::vector<std::unique_ptr<libMesh::C0Polygon>> elements_to_add_to_mesh;
247 if (!
mesh->is_serial())
248 mooseError(
"MeshRepairGenerator requires a serial mesh for this operation. " 249 "The mesh should not be distributed.");
251 for (
auto elem :
mesh->element_ptr_range())
259 auto is_convex = [](
const Elem * elem,
260 std::vector<std::pair<int, Real>> & obtuse_angle_nodes) ->
bool 262 mooseAssert(elem,
"Should have an elem");
263 mooseAssert(obtuse_angle_nodes.empty(),
"Should not be empty");
266 const auto elem_centroid = elem->vertex_average();
267 const auto n_nodes = elem->n_nodes();
272 const auto v1 = elem_centroid - elem->point(i);
273 const auto v2 = elem_centroid - elem->point((i + 1) %
n_nodes);
274 if (!MooseUtils::absoluteFuzzyEqual((v1.cross(v2)).
norm_sq(), 0))
276 plane_normal = v1.cross(v2).unit();
285 const auto n1 = elem->point(i);
286 const auto n2 = elem->point((i + 1) %
n_nodes);
287 const auto n3 = elem->point((i + 2) %
n_nodes);
288 const auto top_dir = (n2 - n1).cross(n3 - n2);
290 if (plane_normal * top_dir <= 0)
293 if (!MooseUtils::absoluteFuzzyEqual(plane_normal * top_dir, 0))
294 obtuse_angle_nodes.push_back(
295 std::make_pair<int, Real>(i, plane_normal * top_dir / top_dir.norm()));
298 obtuse_angle_nodes.push_back(std::make_pair<int, Real>(i, -1));
304 std::vector<std::pair<int, Real>> parent_obtuse_angles;
305 const auto convex = is_convex(elem, parent_obtuse_angles);
315 auto cut_polygon = [&
mesh](
const Elem * elem,
unsigned int node_i_cut1,
unsigned node_i_cut2)
316 -> std::pair<std::unique_ptr<libMesh::C0Polygon>, std::unique_ptr<libMesh::C0Polygon>>
318 const auto cut1 =
std::min(node_i_cut1, node_i_cut2);
319 const auto cut2 =
std::max(node_i_cut1, node_i_cut2);
322 const auto ns1 = cut2 - cut1 + 1;
323 const auto ns2 = elem->n_nodes() - ns1 + 2;
324 mooseAssert(ns1 >= 3,
"Should cut at least a triangle");
325 mooseAssert(ns2 >= 3,
"Should cut at least a triangle");
328 auto child1 = std::make_unique<libMesh::C0Polygon>(ns1);
329 for (
const auto i_n :
make_range(cut1, cut2 + 1))
330 child1->set_node(i_n - cut1,
mesh->node_ptr(elem->node_ptr(i_n)->id()));
332 auto child2 = std::make_unique<libMesh::C0Polygon>(ns2);
333 for (
const auto i_n :
make_range(cut2, elem->n_nodes() + cut1 + 1))
334 child2->set_node((i_n - cut2) % child2->n_nodes(),
335 mesh->node_ptr(elem->node_ptr(i_n % elem->n_nodes())->
id()));
337 return std::make_pair(std::move(child1), std::move(child2));
345 std::vector<std::unique_ptr<libMesh::C0Polygon>> elements_to_add_to_mesh_temp;
346 std::vector<std::pair<std::unique_ptr<Elem>, std::vector<std::pair<int, Real>>>> elems_to_cut;
349 std::unique_ptr<libMesh::C0Polygon> base_elem =
350 std::make_unique<libMesh::C0Polygon>(elem->n_nodes());
351 for (
const auto i_n :
make_range(elem->n_nodes()))
352 base_elem->set_node(i_n, elem->node_ptr(i_n));
353 elems_to_cut.push_back(std::make_pair(std::move(base_elem), parent_obtuse_angles));
355 while (!failed && !elems_to_cut.empty())
357 auto & [current_elem, large_angles] = elems_to_cut.back();
361 auto worst_angle_it =
362 std::min_element(large_angles.begin(),
364 [](std::pair<int, Real> lhs, std::pair<int, Real> rhs) ->
bool 365 {
return lhs.second < rhs.second; });
366 unsigned int worst_angle_i = (*worst_angle_it).first;
367 const auto n_nodes = current_elem->n_nodes();
375 auto [child1, child2] = cut_polygon(current_elem.get(), worst_angle_i, opposite);
378 std::vector<std::pair<int, Real>> child1_large_angles;
379 bool is_convex1 = is_convex(child1.get(), child1_large_angles);
380 std::vector<std::pair<int, Real>> child2_large_angles;
381 bool is_convex2 = is_convex(child2.get(), child2_large_angles);
384 if (child1->n_nodes() < 4 && !is_convex1)
386 if (child2->n_nodes() < 4 && !is_convex2)
391 if (is_convex1 && child1->volume() <= 0)
393 if (is_convex2 && child2->volume() <= 0)
399 large_angles.erase(worst_angle_it);
400 elems_to_cut.pop_back();
404 elems_to_cut.push_back(std::make_pair(std::move(child1), child1_large_angles));
406 elems_to_cut.push_back(std::make_pair(std::move(child2), child2_large_angles));
411 elements_to_add_to_mesh_temp.push_back(std::move(child1));
413 elements_to_add_to_mesh_temp.push_back(std::move(child2));
415 bool fixed_it = !failed;
419 for (
auto & elem_uptr : elements_to_add_to_mesh_temp)
421 elem_uptr->inherit_data_from(*elem);
422 elements_to_add_to_mesh.push_back(std::move(elem_uptr));
429 const auto centroid_node =
mesh->add_point(elem->true_centroid());
432 const auto n_sides =
poly->n_sides();
435 auto new_elem = std::make_unique<libMesh::C0Polygon>(3);
436 new_elem->set_node(0,
mesh->node_ptr(elem->node_ptr(i_tr)->id()));
437 new_elem->set_node(1, centroid_node);
438 new_elem->set_node(2,
mesh->node_ptr(elem->node_ptr((i_tr + 1) % n_sides)->id()));
441 if (MooseUtils::absoluteFuzzyEqual(
442 (*(Point *)centroid_node - *(Point *)elem->node_ptr(i_tr))
443 .cross(*(Point *)centroid_node -
444 *(Point *)elem->node_ptr((i_tr + 1) % n_sides))
447 mooseError(
"Manual triangulation failed as two consecutive nodes are aligned with the " 450 new_elem->inherit_data_from(*elem);
451 elements_to_add_to_mesh.push_back(std::move(new_elem));
455 elems_to_delete.push_back(elem);
459 for (
auto elem : elems_to_delete)
460 mesh->delete_elem(elem);
462 for (
auto & new_elem_ptr : elements_to_add_to_mesh)
463 mesh->add_elem(std::move(new_elem_ptr));
465 _console <<
"Number of non-convex polygons which got split into convex polygons: " 466 << num_nonconvex << std::endl;
467 if (num_triangulated)
468 _console <<
"Number of non-convex polygons split using a triangulation: " << num_triangulated
469 <<
", using heuristic: " << num_nonconvex - num_triangulated << std::endl;
471 mooseWarning(
"No C0 polygons in mesh: the polyon convexity fix did nothing");
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
const unsigned int invalid_uint
Mesh generator to perform various improvement / fixing operations on an input mesh.
const bool _split_nonconvex_polygons
Whether to split non-convex polygons.
registerMooseObject("MooseApp", MeshRepairGenerator)
void fixOverlappingNodes(std::unique_ptr< MeshBase > &mesh) const
Removes nodes that overlap.
void splitNonConvexPolygons(std::unique_ptr< MeshBase > &mesh) const
Splits non-convex polygonal elements to keep only convex elements.
const bool _boundary_id_merge
Whether to merge boundaries with the same name but different ID.
auto max(const L &left, const R &right)
void mooseWarning(Args &&... args) const
const dof_id_type n_nodes
const bool _elem_type_separation
whether to split subdomains using each element's type
const bool _fix_overlapping_nodes
fixing mesh by deleting overlapping nodes
const std::string & type() const
Get the type of this class.
void separateSubdomainsByElementType(std::unique_ptr< MeshBase > &mesh) const
Separate subdomain by element type because some output format (Exodus) do not support mixed element t...
static InputParameters validParams()
std::string stringify(const T &t)
conversion to string
const bool _fix_element_orientation
whether to flip element orientation such that they no longer have a negative volume ...
std::unique_ptr< MeshBase > & _input
the input mesh
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const Real _node_overlap_tol
tolerance for merging overlapping nodes
IntRange< T > make_range(T beg, T end)
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...
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
void mergeBoundaryIDsWithSameName(MeshBase &mesh)
Merges the boundary IDs of boundaries that have the same names but different IDs. ...
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
auto min(const L &left, const R &right)
MeshGenerators are objects that can modify or add to an existing mesh.
MeshRepairGenerator(const InputParameters ¶meters)
static InputParameters validParams()