LCOV - code coverage report
Current view: top level - src/meshgenerators - PinMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #31782 (60f35e) with base ee0740 Lines: 403 418 96.4 %
Date: 2025-10-31 18:31:38 Functions: 7 7 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 "PinMeshGenerator.h"
      11             : 
      12             : #include "ReactorGeometryMeshBuilderBase.h"
      13             : #include <cmath>
      14             : #include "MooseApp.h"
      15             : #include "MooseMeshUtils.h"
      16             : #include "Factory.h"
      17             : #include "CSGZCylinder.h"
      18             : #include "CSGPlane.h"
      19             : #include "CSGRegion.h"
      20             : #include "CSGUtils.h"
      21             : #include "libmesh/elem.h"
      22             : 
      23             : registerMooseObject("ReactorApp", PinMeshGenerator);
      24             : 
      25             : InputParameters
      26        2036 : PinMeshGenerator::validParams()
      27             : {
      28        2036 :   auto params = ReactorGeometryMeshBuilderBase::validParams();
      29             : 
      30        4072 :   params.addRequiredParam<MeshGeneratorName>(
      31             :       "reactor_params",
      32             :       "The ReactorMeshParams MeshGenerator that is the basis for this component conformal mesh.");
      33             : 
      34        4072 :   params.addRequiredParam<subdomain_id_type>("pin_type",
      35             :                                              "The integer ID for this pin type definition");
      36             : 
      37        4072 :   params.addRequiredRangeCheckedParam<Real>(
      38             :       "pitch", "pitch>0.0", "The pitch for the outermost boundary polygon");
      39             : 
      40        4072 :   params.addRangeCheckedParam<unsigned int>(
      41             :       "num_sectors", "num_sectors>0", "Number of azimuthal sectors in each quadrant");
      42             : 
      43        4072 :   params.addRangeCheckedParam<std::vector<Real>>(
      44             :       "ring_radii",
      45             :       "ring_radii>0.0",
      46             :       "Radii of major concentric circles of the pin. If unspecified, no pin is present.");
      47             : 
      48        4072 :   params.addRangeCheckedParam<std::vector<Real>>(
      49             :       "duct_halfpitch",
      50             :       "duct_halfpitch>0.0",
      51             :       "Apothem of the ducts. If unspecified, no duct is present.");
      52             : 
      53        6108 :   params.addRangeCheckedParam<std::vector<unsigned int>>(
      54             :       "mesh_intervals",
      55        4072 :       std::vector<unsigned int>{1},
      56             :       "mesh_intervals>0",
      57             :       "The number of meshing intervals for each region starting at the center. Parameter should be "
      58             :       "size:"
      59             :       "((length(ring_radii) + length(duct_halfpitch) + 1");
      60             : 
      61        4072 :   params.addParam<std::vector<std::vector<std::string>>>(
      62             :       "block_names",
      63             :       "Block names for each radial and axial zone. "
      64             :       "Inner indexing is radial zones (pin/background/duct), outer indexing is axial");
      65             : 
      66        4072 :   params.addParam<std::vector<std::vector<subdomain_id_type>>>(
      67             :       "region_ids",
      68             :       "IDs for each radial and axial zone for assignment of region_id extra element "
      69             :       "id. "
      70             :       "Inner indexing is radial zones (pin/background/duct), outer indexing is axial");
      71             : 
      72        4072 :   params.addParam<bool>("extrude",
      73        4072 :                         false,
      74             :                         "Determines if this is the final step in the geometry construction"
      75             :                         " and extrudes the 2D geometry to 3D. If this is true then this mesh "
      76             :                         "cannot be used in further mesh building in the Reactor workflow");
      77        4072 :   params.addParam<bool>(
      78        4072 :       "homogenized", false, "Determines whether homogenized pin mesh should be generated");
      79        4072 :   params.addParam<bool>(
      80        4072 :       "use_as_assembly", false, "Determines whether pin mesh should be used as an assembly mesh");
      81             : 
      82        4072 :   params.addParam<bool>(
      83        4072 :       "quad_center_elements", true, "Whether the center elements are quad or triangular.");
      84        4072 :   params.addParamNamesToGroup("region_ids pin_type", "ID assigment");
      85        4072 :   params.addParamNamesToGroup(
      86             :       "mesh_intervals ring_radii num_sectors pin_type homogenized use_as_assembly",
      87             :       "Pin specifications");
      88        4072 :   params.addParamNamesToGroup("mesh_intervals duct_halfpitch num_sectors", "Duct specifications");
      89             : 
      90        2036 :   params.addClassDescription("This PinMeshGenerator object is designed to generate pin-like "
      91             :                              "structures, with IDs, from a reactor geometry. "
      92             :                              "Whether it be a square or hexagonal pin, they are divided into three "
      93             :                              "substructures - the innermost "
      94             :                              "radial pin regions, the single bridging background region, and the "
      95             :                              "square or hexagonal ducts regions.");
      96             : 
      97             :   // Declare that this generator has a generateCSG method
      98        2036 :   MeshGenerator::setHasGenerateCSG(params);
      99             : 
     100        2036 :   return params;
     101           0 : }
     102             : 
     103        1015 : PinMeshGenerator::PinMeshGenerator(const InputParameters & parameters)
     104             :   : ReactorGeometryMeshBuilderBase(parameters),
     105        1015 :     _pin_type(getParam<subdomain_id_type>("pin_type")),
     106        2030 :     _pitch(getParam<Real>("pitch")),
     107        3854 :     _num_sectors(isParamValid("num_sectors") ? getParam<unsigned int>("num_sectors") : 0),
     108        3687 :     _ring_radii(isParamValid("ring_radii") ? getParam<std::vector<Real>>("ring_radii")
     109             :                                            : std::vector<Real>()),
     110        3352 :     _duct_halfpitch(isParamValid("duct_halfpitch") ? getParam<std::vector<Real>>("duct_halfpitch")
     111             :                                                    : std::vector<Real>()),
     112        2030 :     _intervals(getParam<std::vector<unsigned int>>("mesh_intervals")),
     113        4060 :     _region_ids(isParamValid("region_ids")
     114        1015 :                     ? getParam<std::vector<std::vector<subdomain_id_type>>>("region_ids")
     115             :                     : std::vector<std::vector<subdomain_id_type>>()),
     116        2030 :     _extrude(getParam<bool>("extrude")),
     117        2030 :     _quad_center(getParam<bool>("quad_center_elements")),
     118        2030 :     _homogenized(getParam<bool>("homogenized")),
     119        3045 :     _is_assembly(getParam<bool>("use_as_assembly"))
     120             : {
     121             :   // Initialize ReactorMeshParams object
     122        3045 :   initializeReactorMeshParams(getParam<MeshGeneratorName>("reactor_params"));
     123             : 
     124        1015 :   _mesh_dimensions = getReactorParam<unsigned int>(RGMB::mesh_dimensions);
     125        1015 :   _mesh_geometry = getReactorParam<std::string>(RGMB::mesh_geometry);
     126             : 
     127        1015 :   if (_is_assembly)
     128             :   {
     129         160 :     auto assembly_pitch = getReactorParam<Real>(RGMB::assembly_pitch);
     130         160 :     if (assembly_pitch != _pitch)
     131           0 :       mooseError("Pitch defined in PinMeshGenerator must match assembly_pitch defined in "
     132             :                  "ReactorMeshParams if use_as_assembly is set to true");
     133             :   }
     134             : 
     135        1015 :   if (_extrude && _mesh_dimensions != 3)
     136           0 :     paramError("extrude",
     137             :                "In order to extrude this mesh, ReactorMeshParams/dim needs to be set to 3\n");
     138        2106 :   if (_extrude && (!hasReactorParam<boundary_id_type>(RGMB::top_boundary_id) ||
     139        1091 :                    !hasReactorParam<boundary_id_type>(RGMB::bottom_boundary_id)))
     140           0 :     mooseError("Both top_boundary_id and bottom_boundary_id must be provided in ReactorMeshParams "
     141             :                "if using extruded geometry");
     142             : 
     143        1015 :   if (_homogenized)
     144             :   {
     145         103 :     if (_mesh_geometry == "Square")
     146           0 :       mooseError("Homogenization in PinMeshGenerator is only supported for hexagonal geometries");
     147             :     const std::vector<std::string> disallowed_parameters = {
     148         103 :         "num_sectors", "ring_radii", "duct_halfpitch", "mesh_intervals"};
     149         515 :     for (const auto & parameter : disallowed_parameters)
     150         412 :       if (parameters.isParamSetByUser(parameter))
     151           0 :         paramError(parameter,
     152           0 :                    "Parameter " + parameter + " should not be defined for a homogenized pin mesh");
     153         103 :   }
     154             :   else
     155             :   {
     156         912 :     if (_num_sectors == 0)
     157           0 :       mooseError(
     158             :           "Number of sectors must be assigned with parameter num_sectors for non-homogenized pins");
     159         912 :     if (_intervals.size() != (_ring_radii.size() + _duct_halfpitch.size() + 1))
     160           0 :       mooseError(
     161             :           "The number of mesh intervals must be equal to the number of annular regions + the "
     162             :           "number of duct regions + 1"
     163             :           " for the region between the rings and ducts\n");
     164             :   }
     165             : 
     166        2030 :   if (isParamValid("region_ids"))
     167             :   {
     168             :     unsigned int n_axial_levels =
     169        1015 :         (_mesh_dimensions == 3)
     170             :             ? getReactorParam<std::vector<unsigned int>>(RGMB::axial_mesh_intervals).size()
     171        1840 :             : 1;
     172        1015 :     if (_region_ids.size() != n_axial_levels)
     173           0 :       mooseError("The size of region IDs must be equal to the number of axial levels as defined in "
     174             :                  "the ReactorMeshParams object");
     175        1015 :     if (_region_ids[0].size() != (_ring_radii.size() + _duct_halfpitch.size() + 1))
     176           0 :       mooseError("The number of region IDs given needs to be one more than the number of "
     177             :                  "ring_radii + the number of duct_radii\n");
     178             :   }
     179             :   else
     180             :   {
     181           0 :     mooseError("Region IDs must be assigned with parameter region_ids");
     182             :   }
     183        2030 :   if (isParamValid("block_names"))
     184             :   {
     185         173 :     if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     186           2 :       paramError("block_names",
     187             :                  "If ReactorMeshParams/region_id_as_block_name is set, block_names should not be "
     188             :                  "specified in PinMeshGenerator");
     189         171 :     _has_block_names = true;
     190         513 :     _block_names = getParam<std::vector<std::vector<std::string>>>("block_names");
     191         171 :     if (_region_ids.size() != _block_names.size())
     192           0 :       mooseError("The size of block_names must match the size of region_ids");
     193         513 :     for (const auto i : index_range(_region_ids))
     194         342 :       if (_region_ids[i].size() != _block_names[i].size())
     195           0 :         mooseError("The size of block_names must match the size of region_ids");
     196             :   }
     197             :   else
     198         842 :     _has_block_names = false;
     199             : 
     200        1013 :   const auto use_flexible_stitching = getReactorParam<bool>(RGMB::flexible_assembly_stitching);
     201             :   std::string build_mesh_name;
     202             : 
     203             :   // No subgenerators will be called if option to bypass mesh generators is enabled
     204        1013 :   if (!getReactorParam<bool>(RGMB::bypass_meshgen))
     205             :   {
     206         854 :     if (_homogenized)
     207             :     {
     208             :       // If flexible assembly stitching is invoked and this is a homogeneous assembly mesh, do not
     209             :       // call mesh subgenerators here. The homogeneous assembly mesh should be created entirely in
     210             :       // generateFlexibleAssemblyBoundaries()
     211          84 :       bool skip_assembly_generation = _is_assembly && use_flexible_stitching;
     212             : 
     213          84 :       auto params = _app.getFactory().getValidParams("SimpleHexagonGenerator");
     214             : 
     215          84 :       params.set<Real>("hexagon_size") = _pitch / 2.0;
     216          84 :       params.set<boundary_id_type>("external_boundary_id") =
     217          84 :           RGMB::PIN_BOUNDARY_ID_START + _pin_type;
     218             :       const auto boundary_name =
     219          84 :           (_is_assembly ? RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX : RGMB::PIN_BOUNDARY_NAME_PREFIX) +
     220         252 :           std::to_string(_pin_type);
     221         168 :       params.set<BoundaryName>("external_boundary_name") = boundary_name;
     222          84 :       params.set<std::vector<subdomain_id_type>>("block_id") = {
     223         216 :           _quad_center ? RGMB::PIN_BLOCK_ID_START : RGMB::PIN_BLOCK_ID_TRI};
     224         216 :       params.set<MooseEnum>("element_type") = _quad_center ? "QUAD" : "TRI";
     225          84 :       auto block_name = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R0";
     226          84 :       if (!_quad_center)
     227             :         block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     228         252 :       params.set<std::vector<SubdomainName>>("block_name") = {block_name};
     229             : 
     230          84 :       if (!skip_assembly_generation)
     231             :       {
     232          54 :         build_mesh_name = name() + "_2D";
     233         108 :         addMeshSubgenerator("SimpleHexagonGenerator", build_mesh_name, params);
     234             :       }
     235          84 :     }
     236             :     else
     237             :     {
     238             :       // Define all id variables used in the pin
     239             :       std::vector<unsigned int> ring_intervals;
     240             :       std::vector<subdomain_id_type> ring_blk_ids;
     241             :       std::vector<SubdomainName> ring_blk_names;
     242             :       unsigned int background_intervals = 1;
     243             :       std::vector<subdomain_id_type> background_blk_ids;
     244             :       std::vector<SubdomainName> background_blk_names;
     245             :       std::vector<unsigned int> duct_intervals;
     246             :       std::vector<subdomain_id_type> duct_blk_ids;
     247             :       std::vector<SubdomainName> duct_blk_names;
     248             : 
     249        2363 :       for (const auto i : index_range(_intervals))
     250             :       {
     251             :         const auto block_name =
     252        4779 :             RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R" + std::to_string(i);
     253        1593 :         const auto block_id = RGMB::PIN_BLOCK_ID_START + i;
     254             : 
     255        1593 :         if (i < _ring_radii.size())
     256             :         {
     257         565 :           ring_intervals.push_back(_intervals[i]);
     258         565 :           ring_blk_ids.push_back(block_id);
     259         565 :           ring_blk_names.push_back(block_name);
     260             :         }
     261        1028 :         else if (i > _ring_radii.size())
     262             :         {
     263         258 :           duct_intervals.push_back(_intervals[i]);
     264         258 :           duct_blk_ids.push_back(block_id);
     265         258 :           duct_blk_names.push_back(block_name);
     266             :         }
     267             :         else
     268             :         {
     269         770 :           background_intervals = _intervals[i];
     270         770 :           background_blk_ids.push_back(block_id);
     271         770 :           background_blk_names.push_back(block_name);
     272             :         }
     273             :       }
     274         770 :       if (ring_intervals.size() > 0)
     275             :       {
     276         529 :         if (ring_intervals.front() != 1)
     277             :         {
     278             :           // If quad center elements, copy element at beginning of block names and
     279             :           // block ids. Otherwise add RGMB::TRI_BLOCK_NAME_SUFFIX to block names and generate new
     280             :           // block id
     281          18 :           if (_quad_center)
     282             :           {
     283           9 :             ring_blk_ids.insert(ring_blk_ids.begin(), ring_blk_ids.front());
     284           9 :             ring_blk_names.insert(ring_blk_names.begin(), ring_blk_names.front());
     285             :           }
     286             :           else
     287             :           {
     288           9 :             const auto block_name = ring_blk_names.front() + RGMB::TRI_BLOCK_NAME_SUFFIX;
     289           9 :             const auto block_id = RGMB::PIN_BLOCK_ID_TRI;
     290           9 :             ring_blk_ids.insert(ring_blk_ids.begin(), block_id);
     291           9 :             ring_blk_names.insert(ring_blk_names.begin(), block_name);
     292             :           }
     293             :         }
     294             :         // Add RGMB::TRI_BLOCK_NAME_SUFFIX if only one radial region and tri center elements
     295         511 :         else if (!_quad_center)
     296             :         {
     297         276 :           ring_blk_ids[0] = RGMB::PIN_BLOCK_ID_TRI;
     298         276 :           ring_blk_names[0] += RGMB::TRI_BLOCK_NAME_SUFFIX;
     299             :         }
     300             :       }
     301             :       else
     302             :       {
     303         241 :         if (background_intervals > 1)
     304             :         {
     305             :           // If quad center elements, copy element at beginning of block names and
     306             :           // block ids. Otherwise add RGMB::TRI_BLOCK_NAME_SUFFIX to block names and generate new
     307             :           // block id
     308         168 :           if (_quad_center)
     309             :           {
     310         159 :             background_blk_ids.insert(background_blk_ids.begin(), background_blk_ids.front());
     311         159 :             background_blk_names.insert(background_blk_names.begin(), background_blk_names.front());
     312             :           }
     313             :           else
     314             :           {
     315           9 :             const auto block_name = background_blk_names.front() + RGMB::TRI_BLOCK_NAME_SUFFIX;
     316           9 :             const auto block_id = RGMB::PIN_BLOCK_ID_TRI;
     317           9 :             background_blk_ids.insert(background_blk_ids.begin(), block_id);
     318           9 :             background_blk_names.insert(background_blk_names.begin(), block_name);
     319             :           }
     320             :         }
     321             :         // Add RGMB::TRI_BLOCK_NAME_SUFFIX if only one background region and tri center elements
     322             :         // and no ring regions
     323          73 :         else if (!_quad_center)
     324             :         {
     325          64 :           background_blk_ids[0] = RGMB::PIN_BLOCK_ID_TRI;
     326          64 :           background_blk_names[0] += RGMB::TRI_BLOCK_NAME_SUFFIX;
     327             :         }
     328             :       }
     329             : 
     330             :       // If flexible assembly stitching is invoked and this is an assembly mesh with only a
     331             :       // background region, do not call mesh subgenerators here. This assembly mesh should be
     332             :       // created entirely in generateFlexibleAssemblyBoundaries()
     333             :       bool skip_assembly_generation =
     334         770 :           _is_assembly && use_flexible_stitching && _intervals.size() == 1;
     335             : 
     336             :       if (!skip_assembly_generation)
     337             :       {
     338             :         // Generate Cartesian/hex pin using PolygonConcentricCircleMeshGenerator
     339             :         {
     340             :           // Get and assign parameters for the main geometry feature of the Pin
     341             :           // which is created with a PolygonConcentricCircleMeshGenerator subgenerator
     342         760 :           auto params = _app.getFactory().getValidParams("PolygonConcentricCircleMeshGenerator");
     343         760 :           params.set<bool>("preserve_volumes") = true;
     344         760 :           params.set<bool>("quad_center_elements") = _quad_center;
     345        1520 :           params.set<MooseEnum>("polygon_size_style") = "apothem";
     346         760 :           params.set<Real>("polygon_size") = _pitch / 2.0;
     347         760 :           params.set<boundary_id_type>("external_boundary_id") =
     348         760 :               RGMB::PIN_BOUNDARY_ID_START + _pin_type;
     349         760 :           const auto boundary_name = (_is_assembly ? RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX
     350             :                                                    : RGMB::PIN_BOUNDARY_NAME_PREFIX) +
     351        2280 :                                      std::to_string(_pin_type);
     352        1520 :           params.set<BoundaryName>("external_boundary_name") = boundary_name;
     353         760 :           bool flat_side_up = (_mesh_geometry == "Square");
     354         760 :           params.set<bool>("flat_side_up") = flat_side_up;
     355         760 :           params.set<bool>("create_outward_interface_boundaries") = false;
     356             : 
     357         760 :           const auto num_sides = (_mesh_geometry == "Square") ? 4 : 6;
     358         760 :           params.set<unsigned int>("num_sides") = num_sides;
     359         760 :           params.set<std::vector<unsigned int>>("num_sectors_per_side") =
     360        1520 :               std::vector<unsigned int>(num_sides, _num_sectors);
     361             : 
     362         760 :           if (ring_intervals.size() > 0)
     363             :           {
     364         529 :             params.set<std::vector<Real>>("ring_radii") = _ring_radii;
     365         529 :             params.set<std::vector<subdomain_id_type>>("ring_block_ids") = ring_blk_ids;
     366         529 :             params.set<std::vector<SubdomainName>>("ring_block_names") = ring_blk_names;
     367        1058 :             params.set<std::vector<unsigned int>>("ring_intervals") = ring_intervals;
     368             :           }
     369             : 
     370         760 :           params.set<std::vector<subdomain_id_type>>("background_block_ids") = background_blk_ids;
     371         760 :           params.set<std::vector<SubdomainName>>("background_block_names") = background_blk_names;
     372         760 :           params.set<unsigned int>("background_intervals") = background_intervals;
     373             : 
     374         760 :           if (duct_intervals.size() > 0)
     375             :           {
     376         516 :             params.set<MooseEnum>("duct_sizes_style") = "apothem";
     377         258 :             params.set<std::vector<Real>>("duct_sizes") = _duct_halfpitch;
     378         258 :             params.set<std::vector<subdomain_id_type>>("duct_block_ids") = duct_blk_ids;
     379         258 :             params.set<std::vector<SubdomainName>>("duct_block_names") = duct_blk_names;
     380         516 :             params.set<std::vector<unsigned int>>("duct_intervals") = duct_intervals;
     381             :           }
     382             : 
     383        1520 :           addMeshSubgenerator("PolygonConcentricCircleMeshGenerator", name() + "_2D", params);
     384         760 :         }
     385             : 
     386             :         // Remove extra sidesets created by PolygonConcentricCircleMeshGenerator
     387             :         {
     388        1520 :           auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
     389             : 
     390        2280 :           params.set<MeshGeneratorName>("input") = name() + "_2D";
     391             : 
     392         760 :           auto num_sides = (_mesh_geometry == "Square") ? 4 : 6;
     393             :           std::vector<BoundaryName> boundaries_to_delete = {};
     394        4680 :           for (const auto i : make_range(num_sides))
     395       11760 :             boundaries_to_delete.insert(boundaries_to_delete.end(),
     396       11760 :                                         {std::to_string(10001 + i), std::to_string(15001 + i)});
     397        1520 :           params.set<std::vector<BoundaryName>>("boundary_names") = boundaries_to_delete;
     398             : 
     399         760 :           build_mesh_name = name() + "_delbds";
     400        1520 :           addMeshSubgenerator("BoundaryDeletionGenerator", build_mesh_name, params);
     401         760 :         }
     402             :       }
     403         770 :     }
     404             : 
     405             :     // For pin acting as assembly, modify outermost mesh interval to enable flexible assembly
     406             :     // stitching
     407         854 :     if (_is_assembly && use_flexible_stitching)
     408             :     {
     409          55 :       generateFlexibleAssemblyBoundaries();
     410         110 :       build_mesh_name = name() + "_fpg_delbds";
     411             :     }
     412             : 
     413             :     // Pass mesh meta-data defined in subgenerator constructor to this MeshGenerator
     414        1708 :     if (hasMeshProperty<Real>("pitch_meta", name() + "_2D"))
     415        1628 :       copyMeshProperty<Real>("pitch_meta", name() + "_2D");
     416        1708 :     if (hasMeshProperty<std::vector<unsigned int>>("num_sectors_per_side_meta", name() + "_2D"))
     417        1628 :       copyMeshProperty<std::vector<unsigned int>>("num_sectors_per_side_meta", name() + "_2D");
     418        1708 :     if (hasMeshProperty<Real>("max_radius_meta", name() + "_2D"))
     419        1628 :       copyMeshProperty<Real>("max_radius_meta", name() + "_2D");
     420        1708 :     if (hasMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D"))
     421        1628 :       copyMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D");
     422        1708 :     if (hasMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D"))
     423        1628 :       copyMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D");
     424             : 
     425         854 :     if (_is_assembly)
     426         272 :       declareMeshProperty("pattern_pitch_meta", getReactorParam<Real>(RGMB::assembly_pitch));
     427        1436 :     else if (hasMeshProperty<Real>("pattern_pitch_meta", name() + "_2D"))
     428        1382 :       copyMeshProperty<Real>("pattern_pitch_meta", name() + "_2D");
     429         854 :     declareMeshProperty("is_control_drum_meta", false);
     430             : 
     431         854 :     if (_extrude)
     432          36 :       build_mesh_name = callExtrusionMeshSubgenerators(build_mesh_name);
     433             : 
     434             :     // Store final mesh subgenerator
     435         854 :     _build_mesh = &getMeshByName(build_mesh_name);
     436             :   }
     437             : 
     438        1013 :   generateMetadata();
     439        1013 : }
     440             : 
     441             : void
     442          55 : PinMeshGenerator::generateFlexibleAssemblyBoundaries()
     443             : {
     444             :   SubdomainName outermost_block_name;
     445             :   bool has_single_mesh_interval;
     446             : 
     447             :   // Assemblies that invoke this method are either homogenized or have a single pin. First check if
     448             :   // the assembly only has a single region. Otherwise, determine the outermost region for deletion
     449          55 :   if (_homogenized || (_intervals.size() == 1))
     450             :   {
     451          80 :     outermost_block_name = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R0";
     452             :     has_single_mesh_interval = true;
     453             :   }
     454             :   else
     455             :   {
     456          45 :     outermost_block_name = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R" +
     457          15 :                            std::to_string(_intervals.size() - 1);
     458             :     has_single_mesh_interval = false;
     459             : 
     460             :     // Invoke BlockDeletionGenerator to delete outermost mesh interval of assembly
     461          30 :     auto params = _app.getFactory().getValidParams("BlockDeletionGenerator");
     462             : 
     463          45 :     params.set<std::vector<SubdomainName>>("block") = {outermost_block_name};
     464          45 :     params.set<MeshGeneratorName>("input") = _homogenized ? name() + "_2D" : name() + "_delbds";
     465             : 
     466          30 :     addMeshSubgenerator("BlockDeletionGenerator", name() + "_del_outer", params);
     467          15 :   }
     468             : 
     469             :   {
     470             :     // Invoke FlexiblePatternGenerator to triangulate deleted mesh interval
     471          55 :     auto params = _app.getFactory().getValidParams("FlexiblePatternGenerator");
     472             : 
     473          55 :     if (has_single_mesh_interval)
     474          80 :       params.set<std::vector<MeshGeneratorName>>("inputs") = {};
     475             :     else
     476             :     {
     477          75 :       params.set<std::vector<MeshGeneratorName>>("inputs") = {name() + "_del_outer"};
     478          15 :       params.set<std::vector<libMesh::Point>>("extra_positions") = {libMesh::Point(0, 0, 0)};
     479          30 :       params.set<std::vector<unsigned int>>("extra_positions_mg_indices") = {0};
     480             :     }
     481          55 :     params.set<bool>("use_auto_area_func") = true;
     482          55 :     params.set<bool>("verify_holes") = false;
     483         125 :     params.set<MooseEnum>("boundary_type") = (_mesh_geometry == "Hex") ? "HEXAGON" : "CARTESIAN";
     484          55 :     params.set<unsigned int>("boundary_sectors") =
     485          55 :         getReactorParam<unsigned int>(RGMB::num_sectors_flexible_stitching);
     486          55 :     params.set<Real>("boundary_size") = getReactorParam<Real>(RGMB::assembly_pitch);
     487          55 :     params.set<boundary_id_type>("external_boundary_id") = RGMB::PIN_BOUNDARY_ID_START + _pin_type;
     488         110 :     params.set<BoundaryName>("external_boundary_name") =
     489          55 :         RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX + std::to_string(_pin_type);
     490         110 :     params.set<SubdomainName>("background_subdomain_name") =
     491          55 :         outermost_block_name + RGMB::TRI_BLOCK_NAME_SUFFIX;
     492          55 :     params.set<unsigned short>("background_subdomain_id") = RGMB::PIN_BLOCK_ID_TRI_FLEXIBLE;
     493             : 
     494         110 :     addMeshSubgenerator("FlexiblePatternGenerator", name() + "_fpg", params);
     495          55 :   }
     496             :   {
     497             :     // Delete extra boundary created by FlexiblePatternGenerator
     498         110 :     auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
     499             : 
     500         220 :     params.set<MeshGeneratorName>("input") = name() + "_fpg";
     501             :     std::vector<BoundaryName> boundaries_to_delete = {};
     502          55 :     if (!has_single_mesh_interval)
     503          30 :       boundaries_to_delete.push_back(std::to_string(1));
     504          55 :     params.set<std::vector<BoundaryName>>("boundary_names") = boundaries_to_delete;
     505             : 
     506         110 :     addMeshSubgenerator("BoundaryDeletionGenerator", name() + "_fpg_delbds", params);
     507          55 :   }
     508          55 : }
     509             : 
     510             : void
     511        1013 : PinMeshGenerator::generateMetadata()
     512             : {
     513             :   // Store pin region ids and block names for id swap after extrusion if needed
     514             :   // by future mesh generators
     515             :   std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>> region_id_map{
     516        2026 :       {_pin_type, _region_ids}};
     517             : 
     518             :   // Declare mesh properties that need to be moved up to the assembly level
     519        1013 :   if (_is_assembly)
     520             :   {
     521         160 :     declareMeshProperty(RGMB::assembly_type, _pin_type);
     522         160 :     declareMeshProperty(RGMB::background_block_name, std::vector<std::string>());
     523         160 :     declareMeshProperty(RGMB::duct_block_names, std::vector<std::vector<std::string>>());
     524         160 :     declareMeshProperty(RGMB::is_single_pin, _is_assembly);
     525         160 :     declareMeshProperty(RGMB::is_control_drum, false);
     526             :     // Following metadata is only relevant if an output mesh is generated by RGMB
     527             :     // because it pertains to region & block ids of elements in the output mesh
     528         160 :     if (!getReactorParam<bool>(RGMB::bypass_meshgen))
     529             :     {
     530             :       std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>> pin_region_id_map;
     531         136 :       pin_region_id_map.insert(
     532         136 :           std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
     533         136 :               region_id_map.begin()->first, region_id_map.begin()->second));
     534             :       declareMeshProperty(RGMB::pin_region_id_map, pin_region_id_map);
     535             :       std::map<subdomain_id_type, std::vector<std::vector<std::string>>> pin_block_name_map;
     536         272 :       pin_block_name_map.insert(std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(
     537         136 :           _pin_type, _block_names));
     538             :       declareMeshProperty(RGMB::pin_block_name_map, pin_block_name_map);
     539             :     }
     540             :   }
     541             :   // Declare mesh properties that are only relevant to pin meshes
     542             :   else
     543             :   {
     544         853 :     declareMeshProperty(RGMB::pin_type, _pin_type);
     545             :     declareMeshProperty(RGMB::pin_region_ids, region_id_map);
     546         853 :     declareMeshProperty(RGMB::pin_block_names, _block_names);
     547             :   }
     548             : 
     549             :   // Set metadata to describe pin attributes
     550        1013 :   declareMeshProperty(RGMB::pitch, _pitch);
     551        1013 :   declareMeshProperty(RGMB::is_homogenized, _homogenized);
     552        1013 :   declareMeshProperty(RGMB::ring_radii, _ring_radii);
     553        1013 :   declareMeshProperty(RGMB::duct_halfpitches, _duct_halfpitch);
     554        1013 :   declareMeshProperty(RGMB::extruded, _extrude);
     555             : 
     556             :   unsigned int n_axial_levels =
     557        1013 :       (_mesh_dimensions == 3)
     558             :           ? getReactorParam<std::vector<unsigned int>>(RGMB::axial_mesh_intervals).size()
     559        1838 :           : 1;
     560             :   std::vector<std::vector<subdomain_id_type>> ring_region_ids(
     561        1013 :       n_axial_levels, std::vector<subdomain_id_type>(_ring_radii.size()));
     562             :   std::vector<std::vector<subdomain_id_type>> duct_region_ids(
     563        1013 :       n_axial_levels, std::vector<subdomain_id_type>(_duct_halfpitch.size()));
     564        1013 :   std::vector<subdomain_id_type> background_region_ids(n_axial_levels);
     565             : 
     566        2341 :   for (const auto axial_idx : make_range(n_axial_levels))
     567             :   {
     568        2228 :     for (const auto ring_idx : index_range(_ring_radii))
     569         900 :       ring_region_ids[axial_idx][ring_idx] = _region_ids[axial_idx][ring_idx];
     570             : 
     571        1328 :     background_region_ids[axial_idx] = _region_ids[axial_idx][_ring_radii.size()];
     572             : 
     573        1328 :     for (unsigned int duct_idx = _ring_radii.size() + 1;
     574        1770 :          duct_idx < _duct_halfpitch.size() + _ring_radii.size() + 1;
     575             :          ++duct_idx)
     576         442 :       duct_region_ids[axial_idx][duct_idx - _ring_radii.size() - 1] =
     577             :           _region_ids[axial_idx][duct_idx];
     578             :   }
     579             : 
     580             :   // Define mesh properties related to region ids
     581             :   declareMeshProperty(RGMB::ring_region_ids, ring_region_ids);
     582             :   declareMeshProperty(RGMB::background_region_id, background_region_ids);
     583             :   declareMeshProperty(RGMB::duct_region_ids, duct_region_ids);
     584        2026 : }
     585             : 
     586             : std::unique_ptr<MeshBase>
     587         828 : PinMeshGenerator::generate()
     588             : {
     589             :   // Must be called to free the ReactorMeshParams mesh
     590         828 :   freeReactorParamsMesh();
     591             : 
     592             :   // If bypass_mesh is true, return a null mesh. In this mode, an output mesh is not
     593             :   // generated and only metadata is defined on the generator, so logic related to
     594             :   // generation of output mesh will not be called
     595         828 :   if (getReactorParam<bool>(RGMB::bypass_meshgen))
     596             :   {
     597             :     auto null_mesh = nullptr;
     598             :     return null_mesh;
     599             :   }
     600             : 
     601             :   // Update metadata at this point since values for these metadata only get set by PCCMG
     602             :   // at generate() stage
     603        1656 :   if (hasMeshProperty<Real>("max_radius_meta", name() + "_2D"))
     604             :   {
     605         788 :     const auto max_radius_meta = getMeshProperty<Real>("max_radius_meta", name() + "_2D");
     606        1576 :     setMeshProperty("max_radius_meta", max_radius_meta);
     607             :   }
     608        1656 :   if (hasMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D"))
     609             :   {
     610             :     const auto background_intervals_meta =
     611         788 :         getMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D");
     612        1576 :     setMeshProperty("background_intervals_meta", background_intervals_meta);
     613             :   }
     614        1656 :   if (hasMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D"))
     615             :   {
     616             :     const auto node_id_background_meta =
     617         788 :         getMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D");
     618        1576 :     setMeshProperty("node_id_background_meta", node_id_background_meta);
     619             :   }
     620             : 
     621             :   // This generate() method will be called once the subgenerators that we depend on
     622             :   // have been called. This is where we reassign subdomain ids/names according to what
     623             :   // the user has provided, and also where we set region_id, pin_type_id, and radial_id
     624             :   // extra element integers
     625             : 
     626             :   // Add region id, pin type id, and radial id to the mesh as element integers
     627         828 :   std::string region_id_name = "region_id";
     628         828 :   std::string pin_type_id_name = "pin_type_id";
     629         828 :   std::string assembly_type_id_name = "assembly_type_id";
     630         828 :   std::string plane_id_name = "plane_id";
     631         828 :   std::string radial_id_name = "radial_id";
     632             :   const std::string default_block_name =
     633         828 :       (_is_assembly ? RGMB::ASSEMBLY_BLOCK_NAME_PREFIX : RGMB::PIN_BLOCK_NAME_PREFIX) +
     634        1656 :       std::to_string(_pin_type);
     635             : 
     636         828 :   auto region_id_int = getElemIntegerFromMesh(*(*_build_mesh), region_id_name);
     637         828 :   auto radial_id_int = getElemIntegerFromMesh(*(*_build_mesh), radial_id_name);
     638         828 :   auto pin_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), pin_type_id_name);
     639             :   unsigned int plane_id_int = 0;
     640             :   unsigned int assembly_type_id_int = 0;
     641         828 :   if (_extrude)
     642          36 :     plane_id_int = getElemIntegerFromMesh(*(*_build_mesh), plane_id_name, true);
     643         828 :   if (_is_assembly)
     644         272 :     assembly_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), assembly_type_id_name);
     645             : 
     646             :   // Get next free block ID in mesh in case subdomain ids need to be remapped
     647         828 :   auto next_block_id = MooseMeshUtils::getNextFreeSubdomainID(*(*(_build_mesh)));
     648             :   std::map<std::string, SubdomainID> rgmb_name_id_map;
     649             : 
     650             :   // Loop through all elements and set regions ids, pin type id, and radial idx.
     651             :   // These element integers are also used to infer the block name for the region,
     652             :   // and block IDs/names will be reassigned on the pin mesh if necessary
     653       64208 :   for (auto & elem : (*_build_mesh)->active_element_ptr_range())
     654             :   {
     655       31276 :     const auto base_block_id = elem->subdomain_id();
     656       31276 :     const auto base_block_name = (*_build_mesh)->subdomain_name(base_block_id);
     657             : 
     658             :     // Check if block name has correct prefix
     659       62552 :     std::string prefix = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R";
     660       31276 :     if (!(base_block_name.find(prefix, 0) == 0))
     661             :       continue;
     662             :     // Radial index is integer value of substring after prefix
     663       31276 :     std::string radial_str = base_block_name.substr(prefix.length());
     664             : 
     665             :     // Filter out RGMB::TRI_BLOCK_NAME_SUFFIX if needed
     666       31276 :     const std::string suffix = RGMB::TRI_BLOCK_NAME_SUFFIX;
     667             :     const std::size_t found = radial_str.find(suffix);
     668       31276 :     if (found != std::string::npos)
     669       14984 :       radial_str.replace(found, suffix.length(), "");
     670       31276 :     const unsigned int radial_idx = std::stoi(radial_str);
     671             : 
     672             :     // Region id is inferred from z_id and radial_idx
     673       31276 :     dof_id_type z_id = _extrude ? elem->get_extra_integer(plane_id_int) : 0;
     674       31276 :     const subdomain_id_type elem_region_id = _region_ids[std::size_t(z_id)][radial_idx];
     675             : 
     676             :     // Set element integers
     677       31276 :     elem->set_extra_integer(region_id_int, elem_region_id);
     678       31276 :     elem->set_extra_integer(pin_type_id_int, _pin_type);
     679       31276 :     elem->set_extra_integer(radial_id_int, radial_idx);
     680       31276 :     if (_is_assembly)
     681       12462 :       elem->set_extra_integer(assembly_type_id_int, _pin_type);
     682             : 
     683             :     // Set element block name and block id
     684       31276 :     auto elem_block_name = default_block_name;
     685       31276 :     if (_has_block_names)
     686        7020 :       elem_block_name += "_" + _block_names[std::size_t(z_id)][radial_idx];
     687       27766 :     else if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     688       29720 :       elem_block_name += "_REG" + std::to_string(elem_region_id);
     689       31276 :     if (elem->type() == TRI3 || elem->type() == PRISM6)
     690             :       elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     691       62552 :     updateElementBlockNameId(
     692       31276 :         *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
     693         828 :   }
     694             : 
     695             :   // Mark mesh as not prepared, as block IDs were re-assigned in this method
     696         828 :   (*_build_mesh)->set_isnt_prepared();
     697             : 
     698             :   return std::move(*_build_mesh);
     699             : }
     700             : 
     701             : std::unique_ptr<CSG::CSGBase>
     702          45 : PinMeshGenerator::generateCSG()
     703             : {
     704             :   // Must be called to free the ReactorMeshParams mesh
     705          45 :   freeReactorParamsCSG();
     706             : 
     707          45 :   auto csg_obj = std::make_unique<CSG::CSGBase>();
     708             : 
     709             :   unsigned int radial_index = 0;
     710             :   std::vector<std::vector<std::reference_wrapper<const CSG::CSGSurface>>> surfaces_by_radial_region;
     711             : 
     712             :   // Add surfaces corresponding to pin rings
     713         100 :   for (const auto & radius : _ring_radii)
     714             :   {
     715         110 :     const auto surf_name = name() + "_radial_ring_" + std::to_string(radial_index);
     716             :     std::unique_ptr<CSG::CSGSurface> ring_surf_ptr =
     717          55 :         std::make_unique<CSG::CSGZCylinder>(surf_name, 0, 0, radius);
     718          55 :     const auto & ring_surf = csg_obj->addSurface(std::move(ring_surf_ptr));
     719         110 :     surfaces_by_radial_region.push_back({ring_surf});
     720          55 :     ++radial_index;
     721          55 :   }
     722             : 
     723             :   // Add surfaces corresponding to pin ducts
     724          65 :   for (const auto & duct_halfpitch : _duct_halfpitch)
     725             :   {
     726          20 :     const auto & duct_surfaces = getOuterRadialSurfaces(radial_index, duct_halfpitch, *csg_obj);
     727          20 :     surfaces_by_radial_region.push_back(duct_surfaces);
     728          20 :     ++radial_index;
     729          20 :   }
     730             : 
     731             :   // Add surfaces corresponding to outer pin boundary
     732          45 :   const auto & duct_surfaces = getOuterRadialSurfaces(radial_index, _pitch / 2., *csg_obj);
     733          45 :   surfaces_by_radial_region.push_back(duct_surfaces);
     734             : 
     735             :   // Define all radial regions
     736             :   std::vector<CSG::CSGRegion> radial_regions;
     737          45 :   CSG::CSGRegion inner_region, outer_region;
     738         165 :   for (const auto & radial_surfaces : surfaces_by_radial_region)
     739             :   {
     740         120 :     CSG::CSGRegion radial_region;
     741         120 :     if (inner_region.getRegionType() == CSG::CSGRegion::RegionType::EMPTY)
     742             :     {
     743             :       // We are in the innermost radial region, the radial region is inner_region
     744          45 :       inner_region = CSGUtils::getInnerRegion(radial_surfaces);
     745          45 :       radial_region = inner_region;
     746             :     }
     747             :     else
     748             :     {
     749             :       // For all other regions, the radial region is the intersection of inner_region and
     750             :       // outer_region
     751          75 :       outer_region = ~inner_region;
     752          75 :       inner_region = CSGUtils::getInnerRegion(radial_surfaces);
     753          75 :       radial_region = inner_region & outer_region;
     754             :     }
     755         120 :     radial_regions.push_back(radial_region);
     756         120 :   }
     757             : 
     758             :   // Define all axial surfaces and regions
     759             :   std::vector<CSG::CSGRegion> axial_regions;
     760             :   std::vector<std::reference_wrapper<const CSG::CSGSurface>> surfaces_by_axial_region;
     761          45 :   if (_extrude)
     762             :   {
     763          20 :     const auto axial_boundaries = getReactorParam<std::vector<Real>>(RGMB::axial_mesh_sizes);
     764          20 :     Real axial_level = 0.;
     765          70 :     for (const auto i : make_range(axial_boundaries.size() + 1))
     766             :     {
     767          50 :       axial_level += (i != 0) ? axial_boundaries[i - 1] : 0.;
     768         100 :       const auto surf_name = name() + "_axial_plane_" + std::to_string(i);
     769             :       std::unique_ptr<CSG::CSGSurface> plane_surf_ptr =
     770          50 :           std::make_unique<CSG::CSGPlane>(surf_name, 0, 0, 1, axial_level);
     771          50 :       const auto & plane_surf = csg_obj->addSurface(std::move(plane_surf_ptr));
     772          50 :       surfaces_by_axial_region.push_back(plane_surf);
     773          50 :       if (i != 0)
     774             :       {
     775          30 :         const auto & lower_surf = surfaces_by_axial_region[i - 1].get();
     776             :         const auto & upper_surf = surfaces_by_axial_region[i].get();
     777          30 :         axial_regions.push_back((+lower_surf & -upper_surf));
     778             :       }
     779          50 :     }
     780          20 :   }
     781             : 
     782             :   // Define all cells within pin domain
     783         165 :   for (const auto i : index_range(radial_regions))
     784             :   {
     785         330 :     for (const auto j : make_range(_extrude ? axial_regions.size() : 1))
     786             :     {
     787         155 :       auto cell_region = radial_regions[i];
     788         310 :       auto cell_name = name() + "_cell_radial_" + std::to_string(i);
     789         155 :       const auto region_id = _region_ids[j][i];
     790         155 :       const auto mat_name = "rgmb_region_" + std::to_string(region_id);
     791         155 :       if (_extrude)
     792             :       {
     793             :         // update name and region with axial info only if extruded
     794          90 :         const auto axial_region = axial_regions[j];
     795          90 :         cell_region &= axial_region;
     796          90 :         cell_name += "_axial_" + std::to_string(j);
     797          90 :       }
     798         155 :       csg_obj->createCell(cell_name, mat_name, cell_region);
     799         155 :     }
     800             :   }
     801             : 
     802             :   // Define void cell to cover region outside pin domain, where the complement of the last set
     803             :   // inner_region is the outermost defined zone of the pin
     804          45 :   const auto void_cell_name = name() + "_void_cell";
     805          45 :   auto void_region = ~inner_region;
     806          45 :   if (_extrude)
     807             :   {
     808             :     const auto & lowest_axial_surf = surfaces_by_axial_region.front().get();
     809             :     const auto & highest_axial_surf = surfaces_by_axial_region.back().get();
     810          40 :     void_region = (~inner_region & +lowest_axial_surf & -highest_axial_surf) | -lowest_axial_surf |
     811          60 :                   +highest_axial_surf;
     812             :   }
     813          45 :   csg_obj->createCell(void_cell_name, void_region);
     814             : 
     815          45 :   return csg_obj;
     816          90 : }
     817             : 
     818             : std::vector<std::reference_wrapper<const CSG::CSGSurface>>
     819          65 : PinMeshGenerator::getOuterRadialSurfaces(unsigned int radial_index,
     820             :                                          Real halfpitch,
     821             :                                          CSG::CSGBase & csg_obj)
     822             : {
     823             :   std::vector<std::reference_wrapper<const CSG::CSGSurface>> duct_surfaces;
     824          65 :   auto n_surfaces = _mesh_geometry == "Square" ? 4 : 6;
     825             : 
     826             :   // Convert halfpitch to radius (distance from vertex to center)
     827          65 :   Real angle_offset_degrees = _mesh_geometry == "Square" ? 45 : 30;
     828          65 :   Real angle_offset_radians = angle_offset_degrees * (M_PI / 180.);
     829          65 :   const auto radius = halfpitch / std::cos(angle_offset_radians);
     830             : 
     831          65 :   Real angle_increment_radians = 360. / n_surfaces * (M_PI / 180.);
     832             : 
     833         415 :   for (const auto i : make_range(n_surfaces))
     834             :   {
     835             :     const auto surf_name =
     836        1050 :         name() + "_radial_duct_" + std::to_string(radial_index) + "_surf_" + std::to_string(i);
     837             : 
     838             :     // Define 3 points on the surface
     839         350 :     const auto current_angle = i * angle_increment_radians + angle_offset_radians;
     840         350 :     const auto next_angle = (i + 1) * angle_increment_radians + angle_offset_radians;
     841         350 :     libMesh::Point p0(radius * std::cos(current_angle), radius * std::sin(current_angle), 0.);
     842         350 :     libMesh::Point p1(radius * std::cos(next_angle), radius * std::sin(next_angle), 0.);
     843             :     libMesh::Point p2 = (p0 + p1) / 2.;
     844             :     // Place third point above the two others to form a vertical plane
     845         350 :     p2(2) = angle_offset_degrees;
     846             : 
     847             :     std::unique_ptr<CSG::CSGSurface> duct_surf_ptr =
     848         350 :         std::make_unique<CSG::CSGPlane>(surf_name, p0, p1, p2);
     849         350 :     const auto & duct_surf = csg_obj.addSurface(std::move(duct_surf_ptr));
     850         350 :     duct_surfaces.push_back(duct_surf);
     851         350 :   }
     852             : 
     853          65 :   return duct_surfaces;
     854           0 : }

Generated by: LCOV version 1.14