LCOV - code coverage report
Current view: top level - src/meshgenerators - MeshRepairGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 106 116 91.4 %
Date: 2026-05-29 20:35:17 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "MeshRepairGenerator.h"
      11             : #include "CastUniquePointer.h"
      12             : #include "MooseMeshUtils.h"
      13             : 
      14             : #include "libmesh/mesh_tools.h"
      15             : #include "libmesh/mesh_modification.h"
      16             : 
      17             : registerMooseObject("MooseApp", MeshRepairGenerator);
      18             : 
      19             : InputParameters
      20        3198 : MeshRepairGenerator::validParams()
      21             : {
      22             : 
      23        3198 :   InputParameters params = MeshGenerator::validParams();
      24        6396 :   params.addClassDescription(
      25             :       "Mesh generator to perform various improvement / fixing operations on an input mesh");
      26       12792 :   params.addRequiredParam<MeshGeneratorName>("input",
      27             :                                              "Name of the mesh generator providing the mesh");
      28             : 
      29       12792 :   params.addParam<bool>("fix_node_overlap", false, "Whether to merge overlapping nodes");
      30        9594 :   params.addParam<Real>(
      31        6396 :       "node_overlap_tol", 1e-8, "Absolute tolerance for merging overlapping nodes");
      32             : 
      33        9594 :   params.addParam<bool>(
      34        6396 :       "fix_elements_orientation", false, "Whether to flip elements with negative volumes");
      35             : 
      36        9594 :   params.addParam<bool>("separate_blocks_by_element_types",
      37        6396 :                         false,
      38             :                         "Create new blocks if multiple element types are present in a block");
      39             : 
      40        9594 :   params.addParam<bool>("merge_boundary_ids_with_same_name",
      41        6396 :                         false,
      42             :                         "Merge boundaries if they have the same name but different boundary IDs");
      43             : 
      44        6396 :   params.addParam<bool>(
      45             :       "renumber_contiguously",
      46        6396 :       false,
      47             :       "Whether to renumber the elements of the mesh so the numbering is contiguous");
      48             : 
      49        3198 :   return params;
      50           0 : }
      51             : 
      52          67 : MeshRepairGenerator::MeshRepairGenerator(const InputParameters & parameters)
      53             :   : MeshGenerator(parameters),
      54          67 :     _input(getMesh("input")),
      55         134 :     _fix_overlapping_nodes(getParam<bool>("fix_node_overlap")),
      56         134 :     _node_overlap_tol(getParam<Real>("node_overlap_tol")),
      57         134 :     _fix_element_orientation(getParam<bool>("fix_elements_orientation")),
      58         134 :     _elem_type_separation(getParam<bool>("separate_blocks_by_element_types")),
      59         201 :     _boundary_id_merge(getParam<bool>("merge_boundary_ids_with_same_name"))
      60             : {
      61          38 :   if (!_fix_overlapping_nodes && !_fix_element_orientation && !_elem_type_separation &&
      62         121 :       !_boundary_id_merge && !getParam<bool>("renumber_contiguously"))
      63           0 :     mooseError("No specific item to fix. Are any of the parameters misspelled?");
      64          67 : }
      65             : 
      66             : std::unique_ptr<MeshBase>
      67          60 : MeshRepairGenerator::generate()
      68             : {
      69          60 :   std::unique_ptr<MeshBase> mesh = std::move(_input);
      70             : 
      71             :   // We're trying to repair a potentially broken mesh; we'll just
      72             :   // start with a full prepare rather than trying to be efficient and
      73             :   // risking missing something.
      74          60 :   mesh->prepare_for_use();
      75             : 
      76             :   // Blanket ban on distributed. This can be relaxed for some operations if needed
      77          60 :   if (!mesh->is_serial())
      78           0 :     mooseError("MeshRepairGenerator requires a serial mesh. The mesh should not be distributed.");
      79             : 
      80          60 :   if (_fix_overlapping_nodes)
      81          25 :     fixOverlappingNodes(mesh);
      82             : 
      83             :   // Flip orientation of elements to keep positive volumes
      84          60 :   if (_fix_element_orientation)
      85           9 :     MeshTools::Modification::orient_elements(*mesh);
      86             : 
      87             :   // Disambiguate any block that has elements of multiple types
      88          60 :   if (_elem_type_separation)
      89           9 :     separateSubdomainsByElementType(mesh);
      90             : 
      91             :   // Assign a single boundary ID to boundaries that have the same name
      92          60 :   if (_boundary_id_merge)
      93           9 :     MooseMeshUtils::mergeBoundaryIDsWithSameName(*mesh);
      94             : 
      95             :   // Renumber the mesh despite any mesh flag
      96         180 :   if (getParam<bool>("renumber_contiguously"))
      97             :   {
      98           8 :     const auto prev_status = mesh->allow_renumbering();
      99           8 :     mesh->allow_renumbering(true);
     100           8 :     mesh->renumber_nodes_and_elements();
     101           8 :     mesh->allow_renumbering(prev_status);
     102             :   }
     103             : 
     104          60 :   mesh->unset_is_prepared();
     105          60 :   return mesh;
     106           0 : }
     107             : 
     108             : void
     109          25 : MeshRepairGenerator::fixOverlappingNodes(std::unique_ptr<MeshBase> & mesh) const
     110             : {
     111          25 :   unsigned int num_fixed_nodes = 0;
     112          25 :   auto pl = mesh->sub_point_locator();
     113          25 :   pl->set_close_to_point_tol(_node_overlap_tol);
     114             : 
     115          25 :   std::unordered_set<dof_id_type> nodes_removed;
     116             :   // loop on nodes
     117         517 :   for (auto & node : mesh->node_ptr_range())
     118             :   {
     119             :     // this node has already been removed
     120         246 :     if (nodes_removed.count(node->id()))
     121          66 :       continue;
     122             : 
     123             :     // find all the elements around this node
     124         180 :     std::set<const Elem *> elements;
     125         180 :     (*pl)(*node, elements);
     126             : 
     127         426 :     for (auto & elem : elements)
     128             :     {
     129         246 :       bool found = false;
     130        1103 :       for (auto & elem_node : elem->node_ref_range())
     131             :       {
     132        1037 :         if (node->id() == elem_node.id())
     133             :         {
     134         180 :           found = true;
     135         180 :           break;
     136             :         }
     137             :       }
     138         246 :       if (!found)
     139             :       {
     140         440 :         for (auto & elem_node : elem->node_ref_range())
     141             :         {
     142         374 :           if (elem_node.id() == node->id())
     143           0 :             continue;
     144         374 :           const Real tol = _node_overlap_tol;
     145             :           // Compares the coordinates
     146         374 :           const auto x_node = (*node)(0);
     147         374 :           const auto x_elem_node = elem_node(0);
     148         374 :           const auto y_node = (*node)(1);
     149         374 :           const auto y_elem_node = elem_node(1);
     150         374 :           const auto z_node = (*node)(2);
     151         374 :           const auto z_elem_node = elem_node(2);
     152             : 
     153         374 :           if (MooseUtils::absoluteFuzzyEqual(x_node, x_elem_node, tol) &&
     154         472 :               MooseUtils::absoluteFuzzyEqual(y_node, y_elem_node, tol) &&
     155          98 :               MooseUtils::absoluteFuzzyEqual(z_node, z_elem_node, tol))
     156             :           {
     157             :             // Merging two nodes from the same element is almost never a good idea
     158          66 :             if (elem->get_node_index(node) != libMesh::invalid_uint)
     159             :             {
     160           0 :               _console << "Two overlapping nodes in element " << elem->id() << " right by "
     161           0 :                        << elem->vertex_average() << ".\n They will not be stitched" << std::endl;
     162           0 :               continue;
     163             :             }
     164             : 
     165             :             // Coordinates are the same but it's not the same node
     166             :             // Replace the node in the element
     167          66 :             const_cast<Elem *>(elem)->set_node(elem->get_node_index(&elem_node), node);
     168          66 :             nodes_removed.insert(elem_node.id());
     169             : 
     170          66 :             num_fixed_nodes++;
     171          66 :             if (num_fixed_nodes < 10)
     172          66 :               _console << "Stitching nodes " << *node << " and            " << elem_node
     173          66 :                        << std::endl;
     174           0 :             else if (num_fixed_nodes == 10)
     175           0 :               _console << "Node stitching will now proceed silently." << std::endl;
     176             :           }
     177             :         }
     178             :       }
     179             :     }
     180         205 :   }
     181          25 :   _console << "Number of overlapping nodes which got merged: " << num_fixed_nodes << std::endl;
     182          25 :   if (mesh->allow_renumbering())
     183           9 :     mesh->renumber_nodes_and_elements();
     184             :   else
     185             :   {
     186          16 :     mesh->remove_orphaned_nodes();
     187          16 :     mesh->update_parallel_id_counts();
     188             :   }
     189          25 : }
     190             : 
     191             : void
     192           9 : MeshRepairGenerator::separateSubdomainsByElementType(std::unique_ptr<MeshBase> & mesh) const
     193             : {
     194           9 :   std::set<subdomain_id_type> ids;
     195           9 :   mesh->subdomain_ids(ids);
     196             :   // loop on sub-domain
     197          18 :   for (const auto id : ids)
     198             :   {
     199             :     // Gather all the element types and blocks
     200             :     // ElemType defines an enum for geometric element types
     201           9 :     std::set<ElemType> types;
     202             :     // loop on elements within this sub-domain
     203          27 :     for (auto & elem : mesh->active_subdomain_elements_ptr_range(id))
     204          27 :       types.insert(elem->type());
     205             : 
     206             :     // This call must be performed on all processes
     207           9 :     auto next_block_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
     208             : 
     209           9 :     if (types.size() > 1)
     210             :     {
     211           9 :       subdomain_id_type i = 0;
     212          27 :       for (const auto type : types)
     213             :       {
     214          18 :         auto new_id = next_block_id + i++;
     215             :         // Create blocks when a block has multiple element types
     216          18 :         mesh->subdomain_name(new_id) = mesh->subdomain_name(id) + "_" + Moose::stringify(type);
     217             : 
     218             :         // Re-assign elements to the new blocks
     219          72 :         for (auto elem : mesh->active_subdomain_elements_ptr_range(id))
     220          27 :           if (elem->type() == type)
     221          36 :             elem->subdomain_id() = new_id;
     222             :       }
     223             :     }
     224           9 :   }
     225           9 : }

Generated by: LCOV version 1.14