LCOV - code coverage report
Current view: top level - src/meshgenerators - GapLineMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #32971 (54bef8) with base c6cf66 Lines: 80 80 100.0 %
Date: 2026-05-29 20:39:24 Functions: 3 3 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 "GapLineMeshGenerator.h"
      11             : 
      12             : #include "MooseMeshUtils.h"
      13             : #include "MooseUtils.h"
      14             : #include "GeometryUtils.h"
      15             : 
      16             : #include "libmesh/mesh_serializer.h"
      17             : #include "libmesh/mesh_triangle_holes.h"
      18             : #include "libmesh/poly2tri_triangulator.h"
      19             : 
      20             : registerMooseObject("ReactorApp", GapLineMeshGenerator);
      21             : 
      22             : InputParameters
      23          60 : GapLineMeshGenerator::validParams()
      24             : {
      25          60 :   InputParameters params = PolygonMeshGeneratorBase::validParams();
      26             : 
      27         120 :   params.addRequiredParam<MeshGeneratorName>("input", "The input mesh to create the gap based on.");
      28             : 
      29         120 :   params.addRequiredParam<Real>("thickness", "The thickness of the gap to be created.");
      30             : 
      31         120 :   MooseEnum gap_direction("OUTWARD INWARD", "OUTWARD");
      32             : 
      33         120 :   params.addParam<MooseEnum>("gap_direction",
      34             :                              gap_direction,
      35             :                              "In which direction the gap is created with respect to the side "
      36             :                              "normal of the elements along the boundary of the input mesh.");
      37             : 
      38          60 :   params.addParam<std::vector<boundary_id_type>>(
      39             :       "boundary_ids",
      40          60 :       std::vector<boundary_id_type>(),
      41             :       "The boundary IDs around which the gap will be created.");
      42             : 
      43         120 :   params.addParam<Real>("max_elem_size", "The maximum element size for the generated gap mesh.");
      44             : 
      45          60 :   params.addClassDescription(
      46             :       "Generates a polyline mesh that is based on an input 2D-XY mesh. The 2D-XY mesh needs to be "
      47             :       "a "
      48             :       "connected mesh with only one outer boundary manifold. The polyline mesh generated along "
      49             :       "with the boundary of the input mesh form an unmeshed gap with a specified thickness.");
      50             : 
      51          60 :   return params;
      52          60 : }
      53             : 
      54          30 : GapLineMeshGenerator::GapLineMeshGenerator(const InputParameters & parameters)
      55             :   : PolygonMeshGeneratorBase(parameters),
      56          30 :     _input(getMesh("input")),
      57          60 :     _thickness(getParam<Real>("thickness")),
      58          60 :     _gap_direction(getParam<MooseEnum>("gap_direction").template getEnum<GapDirection>()),
      59          90 :     _boundary_ids(getParam<std::vector<boundary_id_type>>("boundary_ids"))
      60             : {
      61          30 : }
      62             : 
      63             : std::unique_ptr<MeshBase>
      64          30 : GapLineMeshGenerator::generate()
      65             : {
      66             :   // Put the boundary mesh in a local pointer
      67             :   std::unique_ptr<UnstructuredMesh> mesh =
      68          30 :       dynamic_pointer_cast<UnstructuredMesh>(std::move(_input));
      69             : 
      70          30 :   std::set<std::size_t> mesh_bdry_ids(_boundary_ids.begin(), _boundary_ids.end());
      71             :   // MeshedHole is a good tool to extract and sort boundary points
      72          60 :   TriangulatorInterface::MeshedHole bdry_mh(*mesh, mesh_bdry_ids);
      73             : 
      74             :   // Reduce the point list to only contain vertices
      75             :   std::vector<Point> reduced_pts_list;
      76         390 :   for (const auto i : make_range(bdry_mh.n_points()))
      77             :   {
      78         360 :     if (!geom_utils::arePointsColinear(
      79         360 :             bdry_mh.point((i - 1 + bdry_mh.n_points()) % bdry_mh.n_points()),
      80         360 :             bdry_mh.point(i),
      81         720 :             bdry_mh.point((i + 1) % bdry_mh.n_points())))
      82         720 :       reduced_pts_list.push_back(bdry_mh.point(i));
      83             :   }
      84             :   // Here we need a method to generate the outward normals of each external side
      85          30 :   auto ply_mesh = buildMeshBaseObject();
      86             : 
      87          30 :   MooseMeshUtils::buildPolyLineMesh(*ply_mesh,
      88             :                                     reduced_pts_list,
      89             :                                     /*loop*/ true,
      90          30 :                                     BoundaryName(),
      91          30 :                                     BoundaryName(),
      92          60 :                                     std::vector<unsigned int>({1}));
      93             : 
      94             :   std::unique_ptr<UnstructuredMesh> ply_mesh_u =
      95          30 :       dynamic_pointer_cast<UnstructuredMesh>(std::move(ply_mesh));
      96             : 
      97             :   // Generate a very simple triangulation mesh so that we can get the outward normal vectors
      98          30 :   libMesh::Poly2TriTriangulator poly2tri(*ply_mesh_u);
      99          30 :   poly2tri.triangulation_type() = libMesh::TriangulatorInterface::PSLG;
     100             : 
     101          30 :   poly2tri.set_interpolate_boundary_points(0);
     102             :   poly2tri.set_refine_boundary_allowed(false);
     103             :   poly2tri.set_verify_hole_boundaries(false);
     104          30 :   poly2tri.desired_area() = 0;
     105          30 :   poly2tri.minimum_angle() = 0; // Not yet supported
     106          30 :   poly2tri.smooth_after_generating() = false;
     107          30 :   poly2tri.triangulate();
     108             : 
     109             :   // We need to serialize the mesh for next steps
     110          30 :   libMesh::MeshSerializer serial(*ply_mesh_u);
     111             :   // The mesh now only contains one side set that corresponds to the outer boundary with an ID of 0
     112          30 :   auto bdry_list(ply_mesh_u->get_boundary_info().build_side_list());
     113             : 
     114             :   // For each vertex, the shifting direction to form the gap is defined by the normal vectors of the
     115             :   // two sides that contain the vertex
     116             :   // We gather the normals for each node on the boundary here, which are all vertices because of the
     117             :   // pre-selection of nodes
     118             :   std::map<dof_id_type, std::vector<Point>> node_normal_map;
     119         390 :   for (const auto & bside : bdry_list)
     120             :   {
     121         360 :     const auto & side = ply_mesh_u->elem_ptr(std::get<0>(bside))->side_ptr(std::get<1>(bside));
     122             :     const Point side_normal =
     123         360 :         ply_mesh_u->elem_ptr(std::get<0>(bside))->side_vertex_average_normal(std::get<1>(bside));
     124         360 :     if (node_normal_map.count(side->node_ptr(0)->id()))
     125         218 :       node_normal_map[side->node_ptr(0)->id()].push_back(side_normal);
     126             :     else
     127         142 :       node_normal_map[side->node_ptr(0)->id()] = {side_normal};
     128         360 :     if (node_normal_map.count(side->node_ptr(1)->id()))
     129         142 :       node_normal_map[side->node_ptr(1)->id()].push_back(side_normal);
     130             :     else
     131         218 :       node_normal_map[side->node_ptr(1)->id()] = {side_normal};
     132         360 :   }
     133             : 
     134          30 :   std::vector<Point> mod_reduced_pts_list(reduced_pts_list);
     135         390 :   for (const auto & [node_id, normal_vecs] : node_normal_map)
     136             :   {
     137             :     mooseAssert(normal_vecs.size() == 2,
     138             :                 "Each vertex should be connected to exactly two sides in a polygon.");
     139             : 
     140         360 :     const Point original_pt = *(ply_mesh_u->node_ptr(node_id));
     141             :     // Form an average normal at the vertex from the two connected sides' normals
     142         360 :     const Point move_dir = (normal_vecs.front() + normal_vecs.back()).unit() *
     143         360 :                            ((_gap_direction == GapDirection::OUTWARD) ? 1.0 : -1.0);
     144             :     // Consider four points of interest to determine the moving distance
     145             :     // 1. the vertex point
     146             :     // 2. point along normal_vecs.front() from the vertex with a distance _thickness
     147             :     // 3. point along normal_vecs.back() from the vertex with a distance _thickness
     148             :     // 4. point along move_dir from the vertex with a distance mov_dist
     149             :     // The four points form a kite shape with its symmetry axis along 1-4 direction
     150             :     // Angle 1-2-4 and angle 1-3-4 are right angles
     151             :     // Angle 2-1-3 (i.e., theta) can be calculated using the interior product of
     152             :     // v1 = normal_vecs.front() and v2 = normal_vecs.back():
     153             :     // cos(theta) = (v1 . v2) / (|v1| * |v2|)
     154             :     // Because of the right angles, the distance between 1 and 4 can be calculated as:
     155             :     // mov_dist = _thickness / cos(theta/2)
     156             :     // and cos(theta/2) = sqrt((1 + cos(theta)) / 2)
     157             :     const Real mov_dist =
     158         360 :         _thickness /
     159         360 :         std::sqrt((1.0 + (normal_vecs.front() * normal_vecs.back()) /
     160         360 :                              (normal_vecs.front().norm() * normal_vecs.back().norm())) /
     161             :                   2.0);
     162             : 
     163             :     mooseAssert(std::count(reduced_pts_list.begin(), reduced_pts_list.end(), original_pt) == 1,
     164             :                 "The original point should be found exactly once in the reduced points list.");
     165         360 :     mod_reduced_pts_list[std::distance(
     166             :         reduced_pts_list.begin(),
     167         360 :         std::find(reduced_pts_list.begin(), reduced_pts_list.end(), original_pt))] =
     168             :         original_pt + move_dir * mov_dist;
     169             :   }
     170             : 
     171             :   // To ensure no overlapping, we need to check set of four points
     172             :   // p1 and p2 should be the pair of points before shifting the side
     173             :   // p3 and p4 should be the pair of points after shifting a side
     174         375 :   for (const auto & i_node_1 : make_range(mod_reduced_pts_list.size()))
     175             :   {
     176             :     const Point & p1 = reduced_pts_list[i_node_1];
     177             :     const Point & p2 = mod_reduced_pts_list[i_node_1];
     178        4504 :     for (const auto & i_node_2 : make_range(mod_reduced_pts_list.size()))
     179             :     {
     180        4159 :       if (i_node_2 == i_node_1 || (i_node_2 + 1) % mod_reduced_pts_list.size() == i_node_1)
     181             :         continue;
     182             :       const Point & p3 = mod_reduced_pts_list[i_node_2];
     183             :       const Point & p4 = mod_reduced_pts_list[(i_node_2 + 1) % mod_reduced_pts_list.size()];
     184        3465 :       if (geom_utils::segmentsIntersect(p1, p2, p3, p4))
     185           2 :         paramError("thickness",
     186             :                    "The thickness is so large that the mesh is tangled because the offset nodes "
     187             :                    "are no longer in the same order when following the original boundary. Please "
     188             :                    "reduce the thickness value.");
     189             :     }
     190             :   }
     191             : 
     192          28 :   auto ply_mesh_2 = buildMeshBaseObject();
     193             : 
     194          56 :   if (isParamValid("max_elem_size"))
     195           7 :     MooseMeshUtils::buildPolyLineMesh(*ply_mesh_2,
     196             :                                       mod_reduced_pts_list,
     197             :                                       true,
     198           7 :                                       BoundaryName(),
     199           7 :                                       BoundaryName(),
     200          14 :                                       getParam<Real>("max_elem_size"));
     201             :   else
     202          21 :     MooseMeshUtils::buildPolyLineMesh(*ply_mesh_2,
     203             :                                       mod_reduced_pts_list,
     204             :                                       true,
     205          21 :                                       BoundaryName(),
     206          21 :                                       BoundaryName(),
     207          42 :                                       std::vector<unsigned int>({1}));
     208             : 
     209          28 :   return ply_mesh_2;
     210          84 : }

Generated by: LCOV version 1.14