CSGBase

CSGBase is the main class developers should interact with when implementing the generateCSG method for any mesh generator. This framework class acts as a container and driver for all methods necessary for creating a constructive solid geometry (CSG) representation such as generating surfaces, cells, and universes of the mesh generator under consideration.

commentnote

Throughout this documentation, csg_obj will be used in example code blocks to refer to a CSGBase instance.

Declaring that a mesh generator supports the generation of CSG

In order to call generateCSG, the setHasGenerateCSG method must be called on the mesh generator to declare that the method has been implemented.

InputParameters
TestCSGAxialSurfaceMeshGenerator::validParams()
{
  InputParameters params = MeshGenerator::validParams();
  // input parameter that is an existing mesh generator
  params.addRequiredParam<MeshGeneratorName>("input", "The input MeshGenerator.");
  // additional params for this specific mesh generator
  params.addRequiredParam<Real>("axial_height", "Axial height of the model.");
  // Declare that this generator has a generateCSG method
  MeshGenerator::setHasGenerateCSG(params);
  return params;
}
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)

How to implement the generateCSG routine

This section will describe the various components developers should implement into the generateCSG method for a given MeshGenerator. This method will return a unique pointer to the CSGBase object that was created or modified by the mesh generator in the generateCSG method.

Initialization

A new CSGBase object can be initialized with:

  auto csg_obj = std::make_unique<CSG::CSGBase>();
(moose/test/src/csg/ExampleCSGInfiniteSquareMeshGenerator.C)

Once initialized, surfaces, cells, and universes can be created and manipulated. The following sections explain in detail how to do this as a part of the generateCSG method.

Surfaces

Surfaces are used to define the spatial extent of the region of a CSGCell. To create a CSGSurface object, the surface constructor must be called directly to create a unique pointer. This pointer then has to be passed to the current CSGBase instance with addSurface which will then return a const reference to that generated surface (const & CSGSurface). The syntax to do this is as follows, where SurfaceType should be replaced with the specific type of surface being created (e.g., CSG::CSGPlane):


// the unique surface pointer is made first, creating the surface object
std::unique_ptr<CSG::CSGSurface> surf_ptr = std::make_unique<SurfaceType>(arguments);
// and then it is explicitly passed to this CSGBase instance, which holds the memory ownership for the object
const auto & surface = csg_obj->addSurface(std::move(surf_ptr));
commentnote:Adding surfaces to the CSGBase instance

Surfaces need to be added to the CSGBase instance with addSurface as described above. If this is not done and these surfaces are referenced in regions used to define cells within the CSGBase instance, an error will occur.

The CSG framework in MOOSE provides various classes for creating basic surfaces (see table below). Information about how to define new types of surfaces can be found in CSGSurface.

Surface TypeClassDescription
PlaneCSGPlanecreate a plane defined by 3 points or from coefficients a, b, c, and d for the equation ax + by + cz = d
SphereCSGSpherecreates a sphere of radius r at an optionally specified center point (default is (0, 0, 0))
CylinderCSGXCylindercreates a cylinder aligned with the x-axis at the specified center location (y, z)
CylinderCSGYCylindercreates a cylinder aligned with the y-axis at the specified center location (x, z)
CylinderCSGZCylindercreates a cylinder aligned with the z-axis at the specified center location (x, y)

Example:

    // create a plane using the coefficients for the equation of a plane
    // z plane equation: 0.0*x + 0.0*y + 1.0*z = (+/-)0.5 * axial_height
    std::unique_ptr<CSG::CSGSurface> surface_ptr =
        std::make_unique<CSG::CSGPlane>(default_surf_name, 0.0, 0.0, 1.0, coeffs[i]);
    auto & csg_plane = csg_obj->addSurface(std::move(surface_ptr));
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)
commentnote:Including Surface Types

In order to define a surface, the header file for that surface type must be included in the MeshGenerator.C file (i.e., #include "CSGPlane.h" to create planes).

The CSGSurface objects can then be accessed or updated with the following methods from CSGBase:

  • addSurface: add a unique pointer to a CSGSurface object to this CSGBase instance

  • getAllSurfaces: retrieve a list of const references to each CSGSurface object in the CSGBase instance

  • getSurfaceByName: retrieve a const reference to the CSGSurface of the specified name

  • renameSurface: change the name of the CSGSurface

Regions

A region is a space defined by boolean operations applied to surfaces and other regions. Half-space regions are defined as the positive and negative space separated by a surface. These regions can be unionized, intersected, or the complement taken to further define more complex regions. Series of operations can be defined using parentheses ( ) to indicate which operations to perform first. The types of operators available to define a CSGRegion using CSGSurface objects are:

OperatorDescriptionExample Use
+positive half-space+surf
-negative half-space-surf
&intersection-surfA & +surfB
|union-surfA | +surfB
~complement~(-surfA & +surfB)
&=update existing region with an intersectionregion1 &= -surfA
|=update existing region with a unionregion1 |= +surfB

The following is an example of using a combination of all operators to define the space outside a cylinder of a finite height that is topped with a half-sphere. Each of the half-spaces associated with each surface are shown in Figure 1. The cylinder and planes are then combined via intersection to form the region inside a finite cylinder, and the space above the top plane is intersected with the sphere to define a half sphere (Figure 2). These two regions are unionized as shown in Figure 3. The complement of the previous combination then defines the final region ~((-cylinder_surf & -top_plane & +bottom_plane) | (+top_plane & -sphere_surf)), as shown in blue in Figure 4.

Four different surfaces: an infinite cylinder (blue), a top plane (orange), a bottom plane (red), and a sphere (green)

Figure 1: Four different surfaces: an infinite cylinder (blue), a top plane (orange), a bottom plane (red), and a sphere (green)

Two separate regions both defined as *intersections* of *half-spaces*.

Figure 2: Two separate regions both defined as intersections of half-spaces.

One region defined by the *union* of two other regions.

Figure 3: One region defined by the union of two other regions.

A region defined as the *complement* of an existing region.

Figure 4: A region defined as the complement of an existing region.

Cells

A cell is an object defined by a region and a fill. To create any CSGCell, use the method createCell from CSGBase which will return a const reference to the CSGCell object that is created (const CSGCell &). At the time of calling createCell, a unique cell name, the cell region (CSGRegion), and an indicator of the fill must be provided. The CSGRegion is defined by boolean combinations of CSGSurfaces as described below. Three types of cell fills are currently supported: void, material, and universe. If creating a void cell, no fill has to be passed to the creation method. To create a cell with a material fill, simply provide it with a name of a material as a string. For a cell with a CSGUniverse fill, pass it a shared pointer to the CSGUniverse. Some examples of creating the different types of cells are shown below:

    // create a void cell with name cname1 and defined by region reg1
    csg_obj->createCell(cname1, reg1);
(moose/unit/src/CSGBaseTest.C)
    // create a material-filled cell with name cname1, a fill with material matname,
    // and defined by region reg1
    csg_obj->createCell(cname1, "matname", reg1);
(moose/unit/src/CSGBaseTest.C)
    // create a universe-filled cell with name cname1, a fill of universe new_univ,
    // and defined by region reg1
    csg_obj->createCell(cname1, new_univ, reg1);
(moose/unit/src/CSGBaseTest.C)
commentnote:Materials as Placeholders

A cell with a material fill is not connected to a MOOSE material definition at this time. The "material" is currently just a string to represent the name of a CSG material or other type of fill that is otherwise undefined.

The CSGCell objects can then be accessed or updated with the following methods from CSGBase:

  • getAllCells: retrieve a list of const references to each CSGCell object in the CSGBase instance

  • getCellByName: retrieve a const reference to the CSGCell object of the specified name

  • renameCell: change the name of the CSGCell object

  • updateCellRegion: change the region of the cell; if used, all CSGSurface objects used to define the new CSGRegion must also be a part of the current CSGBase

Universes

A universe is a collection of cells and is created by calling createUniverse from CSGBase which will return a const reference to the CSGUniverse object (const CSGUniverse &). A CSGUniverse can be initialized as an empty universe, or by passing a vector of shared pointers to CSGCell objects. Any CSGUniverse object can be renamed (including the root universe) with renameUniverse.

The CSGUniverse objects can then be accessed or updated with the following methods from CSGBase:

  • getAllUniverses: retrieve a list of const references to each CSGUniverse object in the CSGBase instance

  • getUniverseByName: retrieve a const reference to the CSGUniverse of the specified name

  • renameUniverse: change the name of the CSGUniverse

Examples:

    auto new_univ = csg_obj->createUniverse("new_univ");
(moose/unit/src/CSGBaseTest.C)
    // create a list of cells to be added to the universe
    std::vector<std::reference_wrapper<const CSG::CSGCell>> cells = {c1, c2};
    auto & univ = csg_obj->createUniverse("louise", cells);
(moose/unit/src/CSGBaseTest.C)

Root Universe

All universes in a model should be able to be traced back, through the hierarchical tree of cells and universes, to a singular overarching universe known as the root universe. Because universes are a collection of cells and cells can be filled with universe, a tree of universes can be constructed such that the root universe contains the collection of all cells in the model. When a CSGBase object is first initialized, a root CSGUniverse called ROOT_UNIVERSE is created by default. Every CSGCell that is created will be added to the root universe unless otherwise specified (as described below). The root universe exists by default, and which universe is set as the root cannot be changed, except when joining CSGBase objects, as described below. However, the name of the root universe can be updated and cells can be manually added or removed using the same methods described above.

Methods available for managing the root universe:

  • getRootUniverse: returns a const reference to the root universe of the CSGBase instance

  • renameRootUniverse: change the name of the root universe

Adding or Removing Cells

There are multiple ways in which cells can be added to a universe:

  1. At the time of universe creation, a vector of references to CSGCell objects can be passed into createUniverse (as described above). Example:

    // create a list of cells to be added to the universe
    std::vector<std::reference_wrapper<const CSG::CSGCell>> cells = {c1, c2};
    auto & univ = csg_obj->createUniverse("louise", cells);
(moose/unit/src/CSGBaseTest.C)
  1. When a CSGCell is created with createCell, a pointer to a CSGUniverse can be passed as the final argument to indicate that the cell will be created and added directly to that specified universe. In this case, the cell will not be added to the root universe. A cell that has a universe fill type cannot be added to the same universe that is being used for the fill. For example, the two snippets below come from the same file where a new universe is initialized and passed by reference to the cell when it is created:

  // make a new universe to which the new cells can be added at time of creation
  auto & add_to_univ = csg_obj->createUniverse("add_univ");
(moose/unit/src/CSGBaseTest.C)
    // create a cell and add to different universe, not root
    std::string cname2 = "void_cell2";
    csg_obj->createCell(cname2, reg1, &add_to_univ);
(moose/unit/src/CSGBaseTest.C)
  1. A cell or list of cells can be added to an existing universe with the addCellToUniverse and addCellsToUniverse methods. In this case, if a CSGCell exists in another CSGUniverse (such as the root universe), it will not be removed when being added to another (i.e. if the same behavior as option 2 above is desired, the cell will have to be manually removed from the root universe, as described below). The following is an example where the list of cells is collected first and then added at one time to the existing universe, but this could also be accomplished by using addCellToUniverse in a for-loop after each cell is initially created.

  // add a list of cells to an existing universe
  {
    std::vector<std::reference_wrapper<const CSG::CSGCell>> cells = {c1, c2};
    csg_obj->addCellsToUniverse(univ, cells);
(moose/unit/src/CSGBaseTest.C)

Cells can also be removed from a universe in the same way as method 3 above by using the removeCellFromUniverse and removeCellsFromUniverse methods. An example is shown above where the cells are removed from the root universe after they are added to the new universe. Doing this in multiple steps has the same outcome as that of method 2 for adding cells to a universe at the time of cell creation.

commentnote:Maintaining Connectivity

When adding and removing cells to/from universes, it is important to maintain the connectivity of all universes meaning all universes should be nested under the root universe at the end of the generation process, in order to have a consistent model.

Updating Existing CSGBase Objects

An empty CSGBase object can be initialized on its own in each generateCSG method for each mesh generator. However, in most cases, it is necessary to update an existing CSGBase object from a previous MeshGenerator or join multiple CSGBase together such that only one CSGBase object is ultimately produced at the end of the mesh/CSG generation process. There are two main ways to handle this: passing and joining.

Passing between Mesh Generators

CSGBase objects from other mesh generators can be accessed through methods that parallel those available for accessing other MeshGenerator objects. For all methods listed below, a unique pointer to the CSGBase object(s) created by generateCSG for the specified MeshGenerator names are returned.

  • getCSGBase: get the CSGBase object given a parameter name represented as a std::string that stores the mesh generator name

  • getCSGBases: get the CSGBase objects given a parameter name represented as a std::string that stores a list of mesh generator names

  • getCSGBaseByName: get the CSGBase object given a MeshGeneratorName

  • getCSGBasesByName: get all CSGBase objects given a list of MeshGeneratorNames

For example:

  // get the existing CSGBase associated with the input mesh generator
  // this is the CSGBase object that will be updated
  std::unique_ptr<CSG::CSGBase> csg_obj = std::move(getCSGBase("input"));
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)
commentnote:Accessing other MeshGenerator objects by name

A MeshGenerator object(s) can be passed to another mesh generator as input by providing InputParameters of type MeshGeneratorName. See the ExampleAxialSurfaceMeshGenerator implementation below for an example of this.

Joining Bases

When two or more existing CSGBase objects need to be combined to continue to use and update, the joinOtherBase method should be used. This method is called from another CSGBase and at a minimum takes a different existing CSGBase object as input. There are 3 different behaviors for joining bases that are supported depending on the additional arguments that are passed:

  1. No additional arguments: All cells that are in the root universe of the incoming CSGBase object will be added to the existing root universe of the current base object, and the root universe from the incoming base will no longer exist.

  // Case 1: Create two CSGBase objects to join together into a single root
  // CSGBase 1: only one cell, which lives in the ROOT_UNIVERSE
  std::unique_ptr<CSGBase> base1 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr1 = std::make_unique<CSG::CSGSphere>("s1", 1.0);
  const auto & surf1 = base1->addSurface(std::move(surf_ptr1));
  auto & c1 = base1->createCell("c1", +surf1);
  // CSGBase 2: two total unverses (ROOT_UNIVERSE and extra_univ) with a cell in each
  std::unique_ptr<CSGBase> base2 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr2 = std::make_unique<CSG::CSGSphere>("s2", 1.0);
  const auto & surf2 = base2->addSurface(std::move(surf_ptr2));
  auto & c2 = base2->createCell("c2", +surf2);
  auto & extra_univ = base2->createUniverse("extra_univ");
  auto & c3 = base2->createCell("c3", -surf2, &extra_univ);

  // Joining: two universes will remain
  // base1 ROOT_UNIVERSE will gain all cells from base2 ROOT_UNIVERSE
  // base2 ROOT_UNIVERSE will not exist as a separate universe
  // the "extra_univ" in base2 will remain a separate universe
  base1->joinOtherBase(std::move(base2));
(moose/unit/src/CSGBaseTest.C)
  1. One new root universe name (new_root_name_join): All cells in the root universe of the incoming base will be used to create a new universe of the name specified by the new_root_name_join parameter. These cells will not be added to the existing root universe, which will remain unchanged. This new universe will be added as a new non-root universe in the existing base object. This newly created universe will not be connected to the root universe of the existing CSGBase object by default.

  // Case 2: Create two CSGBase objects to join together but keep incoming root separate
  // CSGBase 1: only one cell, which lives in the ROOT_UNIVERSE
  std::unique_ptr<CSGBase> base1 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr1 = std::make_unique<CSG::CSGSphere>("s1", 1.0);
  const auto & surf1 = base1->addSurface(std::move(surf_ptr1));
  auto & c1 = base1->createCell("c1", +surf1);
  // CSGBase 2: two total unverses (ROOT_UNIVERSE and extra_univ) with a cell in each
  std::unique_ptr<CSGBase> base2 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr2 = std::make_unique<CSG::CSGSphere>("s2", 1.0);
  const auto & surf2 = base2->addSurface(std::move(surf_ptr2));
  auto & c2 = base2->createCell("c2", +surf2);
  auto & extra_univ = base2->createUniverse("extra_univ");
  auto & c3 = base2->createCell("c3", -surf2, &extra_univ);

  // Joining: three universes will remain
  // base1 ROOT_UNIVERSE will remain untouched
  // all cells from ROOT_UNIVERSE in base2 create new universe called "new_univ"
  // the "extra_univ" in base2 will remain a separate universe
  std::string new_root_name = "new_univ";
  base1->joinOtherBase(std::move(base2), new_root_name);
(moose/unit/src/CSGBaseTest.C)
  1. Two new root universe names (new_root_name_base and new_root_name_join): The cells in the root universe of the current CSGBase object will be used to create a new non-root universe of the name specified by the new_root_name_base parameter, and the cells in the root universe of the incoming CSGBase object will be used to create a separate non-root universe of the name specified by the new_root_name_join parameter. At the end of this join method, the root universe of the current base object will be empty and neither of the two new non-root universes will be connected to the root universe by default.

  // Case 3: Create two CSGBase objects to join together with each root becoming a new universe
  // CSGBase 1: only one cell, which lives in the ROOT_UNIVERSE
  std::unique_ptr<CSGBase> base1 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr1 = std::make_unique<CSG::CSGSphere>("s1", 1.0);
  const auto & surf1 = base1->addSurface(std::move(surf_ptr1));
  auto & c1 = base1->createCell("c1", +surf1);
  // CSGBase 2: two total unverses (ROOT_UNIVERSE and extra_univ) with a cell in each
  std::unique_ptr<CSGBase> base2 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr2 = std::make_unique<CSG::CSGSphere>("s2", 1.0);
  const auto & surf2 = base2->addSurface(std::move(surf_ptr2));
  auto & c2 = base2->createCell("c2", +surf2);
  auto & extra_univ = base2->createUniverse("extra_univ");
  auto & c3 = base2->createCell("c3", -surf2, &extra_univ);

  // Joining: four universes will remain
  // all cells from base1 ROOT_UNIVERSE will be moved to a new universe called "new_univ1"
  // all cells from base2 ROOT_UNIVERSE will be moved to a new universe called "new_univ2"
  // base1 ROOT_UNIVERSE will be empty
  // the "extra_univ" in base2 will remain a separate universe
  std::string new_name1 = "new_univ1";
  std::string new_name2 = "new_univ2";
  base1->joinOtherBase(std::move(base2), new_name1, new_name2);
(moose/unit/src/CSGBaseTest.C)

For all of these join methods, any non-root universes will remain unchanged and simply added to the list of universes for the current CSGBase object. Similarly, all incoming cells and surfaces are added alongside existing cells and surfaces.

commentnote:Object Naming Uniqueness

It is very important when using the joinOtherBase method that all CSGSurfaces, CSGCells, and CSGSurfaces are uniquely named so that errors are not encountered when combining sets of objects. An error will be produced during the join process if an object of the same type and name already exists. See recommendations for naming below.

All CSG methods related to creating or changing a CSG object must be called through CSGBase. Calls that retrieve information only but do not manipulate an object (such as getName methods) can be called on the object directly. For example, if a cell were to be created, the current name and region could be retrieved directly from the CSGCell object, but if the name or region needed to be changed, that would need to be handled through CSGBase.

This ensures proper accounting of all CSG-related objects in the CSGBase instance. Consult the Doxygen documentation for information on all object-specific methods.

Object Naming Recommendations

For each new CSG element (CSGSurface, CSGCell, and CSGUniverse) that is created, a unique name identifier (of type std::string) must be provided (name parameter for all creation methods). A recommended best practice is to include the mesh generator name (which can be accessed with this->getName() in any MeshGenerator class) as a part of that object name. This name is used as the unique identifier within the CSGBase instance. Methods for renaming objects are available as described in the above sections to help prevent issues and errors.

Example Implementation

Provided here is an example implementation of the generateCSG method for a simple example MeshGenerator that creates an infinite rectangular prism given an input parameter for side_length. The code snippets provided here correspond to the .C file.

InputParameters
ExampleCSGInfiniteSquareMeshGenerator::validParams()
{
  InputParameters params = MeshGenerator::validParams();

  params.addRequiredParam<Real>("side_length", "Side length of infinite square.");
  // Declare that this generator has a generateCSG method
  MeshGenerator::setHasGenerateCSG(params);
  return params;
}

ExampleCSGInfiniteSquareMeshGenerator::ExampleCSGInfiniteSquareMeshGenerator(
    const InputParameters & params)
  : MeshGenerator(params), _side_length(getParam<Real>("side_length"))
{
}

std::unique_ptr<MeshBase>
ExampleCSGInfiniteSquareMeshGenerator::generate()
{
  auto null_mesh = nullptr;
  return null_mesh;
}

std::unique_ptr<CSG::CSGBase>
ExampleCSGInfiniteSquareMeshGenerator::generateCSG()
{
  // name of the current mesh generator to use for naming generated objects
  auto mg_name = this->name();

  // initialize a CSGBase object
  auto csg_obj = std::make_unique<CSG::CSGBase>();

  // Add surfaces and halfspaces corresponding to 4 planes of infinite square
  std::vector<std::vector<Point>> points_on_planes{{Point(1. * _side_length / 2., 0., 0.),
                                                    Point(1. * _side_length / 2., 1., 0.),
                                                    Point(1. * _side_length / 2., 0., 1.)},
                                                   {Point(-1. * _side_length / 2., 0., 0.),
                                                    Point(-1. * _side_length / 2., 1., 0.),
                                                    Point(-1. * _side_length / 2., 0., 1.)},
                                                   {Point(0., 1. * _side_length / 2., 0.),
                                                    Point(1., 1. * _side_length / 2., 0.),
                                                    Point(0., 1. * _side_length / 2., 1.)},
                                                   {Point(0., -1. * _side_length / 2., 0.),
                                                    Point(1., -1. * _side_length / 2., 0.),
                                                    Point(0., -1. * _side_length / 2., 1.)}};
  std::vector<std::string> surf_names{"plus_x", "minus_x", "plus_y", "minus_y"};

  // initialize cell region to be updated
  CSG::CSGRegion region;

  // set the center of the prism to be used for determining half-spaces
  const auto centroid = Point(0, 0, 0);

  for (unsigned int i = 0; i < points_on_planes.size(); ++i)
  {
    // object name includes the mesh generator name for uniqueness
    const auto surf_name = mg_name + "_surf_" + surf_names[i];
    // create the plane for one face of the prism
    std::unique_ptr<CSG::CSGSurface> plane_ptr = std::make_unique<CSG::CSGPlane>(
        surf_name, points_on_planes[i][0], points_on_planes[i][1], points_on_planes[i][2]);
    auto & csg_plane = csg_obj->addSurface(std::move(plane_ptr));
    // determine where the plane is in relation to the centroid to be able to set the half-space
    const auto region_direction = csg_plane.getHalfspaceFromPoint(centroid);
    // half-space is either positive (+plane_ptr) or negative (-plane_ptr)
    // depending on the direction to the centroid
    auto halfspace =
        ((region_direction == CSG::CSGSurface::Halfspace::POSITIVE) ? +csg_plane : -csg_plane);
    // check if this is the first half-space to be added to the region,
    // if not, update the existing region with the intersection of the regions (&=)
    if (region.getRegionType() == CSG::CSGRegion::RegionType::EMPTY)
      region = halfspace;
    else
      region &= halfspace;
  }

  // create the cell defined by the surfaces and region just created
  const auto cell_name = mg_name + "_square_cell";
  const auto material_name = "square_material";
  csg_obj->createCell(cell_name, material_name, region);

  return csg_obj;
}
(moose/test/src/csg/ExampleCSGInfiniteSquareMeshGenerator.C)

The following example builds on the infinite prism example above by taking a MeshGeneratorName for an existing ExampleCSGInfiniteSquareMeshGenerator as input and adding planes to create a finite rectangular prism.

InputParameters
TestCSGAxialSurfaceMeshGenerator::validParams()
{
  InputParameters params = MeshGenerator::validParams();
  // input parameter that is an existing mesh generator
  params.addRequiredParam<MeshGeneratorName>("input", "The input MeshGenerator.");
  // additional params for this specific mesh generator
  params.addRequiredParam<Real>("axial_height", "Axial height of the model.");
  // Declare that this generator has a generateCSG method
  MeshGenerator::setHasGenerateCSG(params);
  return params;
}

TestCSGAxialSurfaceMeshGenerator::TestCSGAxialSurfaceMeshGenerator(const InputParameters & params)
  : MeshGenerator(params),
    _mesh_ptr(getMesh("input")),
    _axial_height(getParam<Real>("axial_height"))
{
}

std::unique_ptr<MeshBase>
TestCSGAxialSurfaceMeshGenerator::generate()
{
  auto null_mesh = nullptr;
  return null_mesh;
}

std::unique_ptr<CSG::CSGBase>
TestCSGAxialSurfaceMeshGenerator::generateCSG()
{
  // get the existing CSGBase associated with the input mesh generator
  // this is the CSGBase object that will be updated
  std::unique_ptr<CSG::CSGBase> csg_obj = std::move(getCSGBase("input"));

  // get the names of the current mesh generator and the input mesh generator
  // so that unique object naming can be enforced
  auto mg_name = this->name();
  auto inp_name = getParam<MeshGeneratorName>("input");

  // get the expected existing cell
  const auto cell_name = inp_name + "_square_cell";
  const auto & csg_cell = csg_obj->getCellByName(cell_name);

  // get the existing cell region to update
  auto cell_region = csg_cell.getRegion();

  // centroid used to determine direction for half-space
  const auto centroid = Point(0, 0, 0);

  // setting a default surface name purely for testing purposes
  const auto default_surf_name = "default_surf";

  // Add surfaces and halfspaces corresponding to top and bottom axial planes
  std::vector<std::string> surf_names{"plus_z", "minus_z"};
  std::vector<Real> coeffs{0.5 * _axial_height, -0.5 * _axial_height};
  for (unsigned int i = 0; i < coeffs.size(); ++i)
  {
    // create a plane using the coefficients for the equation of a plane
    // z plane equation: 0.0*x + 0.0*y + 1.0*z = (+/-)0.5 * axial_height
    std::unique_ptr<CSG::CSGSurface> surface_ptr =
        std::make_unique<CSG::CSGPlane>(default_surf_name, 0.0, 0.0, 1.0, coeffs[i]);
    auto & csg_plane = csg_obj->addSurface(std::move(surface_ptr));

    // Rename surface so that it has a unique surface name based on the mesh generator
    const auto surf_name = mg_name + "_surf_" + surf_names[i];
    csg_obj->renameSurface(csg_plane, surf_name);

    // determine the half-space to add as an updated intersection
    const auto region_direction = csg_plane.getHalfspaceFromPoint(centroid);
    auto halfspace =
        ((region_direction == CSG::CSGSurface::Halfspace::POSITIVE) ? +csg_plane : -csg_plane);

    // update the existing region with a half-space
    cell_region &= halfspace;
  }

  // set the new region for the existing cell
  csg_obj->updateCellRegion(csg_cell, cell_region);

  // Rename cell as it now defines a box region instead of an infinite square region
  csg_obj->renameCell(csg_cell, mg_name + "_box_cell");

  return csg_obj;
}
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)

If the above methods were to be used, the following input would generate the corresponding JSON output below.

Example Input:

[Mesh<<<{"href": "../../syntax/Mesh/index.html"}>>>]
  [inf_square]
    type = ExampleCSGInfiniteSquareMeshGenerator
    side_length = 4
  []
  [cube]
    type = TestCSGAxialSurfaceMeshGenerator
    input = inf_square
    axial_height = 5
  []
[]
(moose/test/tests/csg/csg_only_chained.i)

Example Output:

{
  "cells": {
    "cube_box_cell": {
      "fill": "square_material",
      "filltype": "CSG_MATERIAL",
      "region":
          "(+inf_square_surf_plus_x & -inf_square_surf_minus_x & -inf_square_surf_plus_y & +inf_square_surf_minus_y & -cube_surf_plus_z & +cube_surf_minus_z)"
    }
  },
  "surfaces": {
    "inf_square_surf_minus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": 2.0
      },
      "type": "CSG::CSGPlane"
    },
    "inf_square_surf_minus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": -2.0
      },
      "type": "CSG::CSGPlane"
    },
    "cube_surf_minus_z": {
      "coefficients": {
        "a": 0.0,
        "b": 0.0,
        "c": 1.0,
        "d": -2.5
      },
      "type": "CSG::CSGPlane"
    },
    "inf_square_surf_plus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": -2.0
      },
      "type": "CSG::CSGPlane"
    },
    "inf_square_surf_plus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": 2.0
      },
      "type": "CSG::CSGPlane"
    },
    "cube_surf_plus_z": {
      "coefficients": {
        "a": 0.0,
        "b": 0.0,
        "c": 1.0,
        "d": 2.5
      },
      "type": "CSG::CSGPlane"
    }
  },
  "universes": {
    "ROOT_UNIVERSE": {
      "cells": [
        "cube_box_cell"
      ],
      "root": true
    }
  }
}
(moose/test/tests/csg/gold/csg_only_chained_out_csg.json)

To run the above example, use --allow-test-objects:


./moose_test-opt --allow-test-objects --csg-only -i tests/csg/csg_only_chained.i