Programs in our language are built in two stages: one that creates local geometric structure, and a second that patterns this structure throughout space. Each of these is further broken down into subparts.


==================================
    API description (Boilerplate)
==================================
Each program is given as a python file (.py).
This program must import the metagen package and define a function called "make_structure()", which returns the final Structure object defined by the program. 
If parameters are present in make_structure(), they MUST have a default value.
Specifically, the file structure is as follows: 


from metagen import *

def make_structure() -> Structure:
    <content>



==================================
    DSL description
==================================

======= Skeleton Creation ========
vertex(cpEntity, t)
    @description:
        Create a new vertex. This vertex is defined relative to its containing convex polytope (CP). It will only have an embedding in R3 once the CP has been embedded.
    @params:
        cpEntity    - an entity of a convex polytope (CP), referenced by the entity names.
        t           - [OPTIONAL] list of floats in range [0,1], used to interpolate to a specific position on the cpEntity.
                        If cpEntity is a corner, t is ignored.
                        If cpEntity is an edge, t must contain exactly 1 value. t is used for linear interpolation between the endpoints of cpEntity.
                        If cpEntity is a face, t must contain exactly 2 values. If cpEntity is a triangular face, t is used to interpolate via barycentric coordinates. If cpEntity is a quad face, bilinear interpolation is used.
                        
                        If the optional interpolant t is omitted for a non-corner entity, the returned point will be at the midpoint (for edge) or the centroid (for face) of the entity. Semantically, we encourage that t be excluded (1) if the structure would be invalid given a different non-midpoint t, or (2) if the structure would remain unchanged in the presence a different t (e.g., in the case of a conjugate TPMS, where only the entity selection matters).
    @returns:
        vertex      - the new vertex object 
    @example_usage:
        v0 = vertex(cuboid.edges.BACK_RIGHT, [0.5])
        v1 = vertex(cuboid.edges.TOP_LEFT)


Polyline(ordered_verts)
    @description:
        Creates a piecewise-linear path along the ordered input vertices. All vertices must be referenced to the same CP (e.g., all relative to cuboid entities). The resulting path will remain a polyline in any structures that include it.
    @params:
        ordered_verts   - a list of vertices, in the order you'd like them to be traversed. A closed loop may be created by repeating the zeroth element at the end of the list. No other vertex may be repeated. Only simple paths are permitted.
    @returns:
        polyline        - the new polyline object
    @example_usage:
        p0 = Polyline([v2, v3])
        p0 = Polyline([v0, v1, v2, v3, v4, v5, v0])


Curve(ordered_verts)
    @description:
        Creates a path along the ordered input vertices. This path will be smoothed at a later stage (e.g., to a Bezier curve), depending on the lifting procedures that are chosen. All input vertices must be referenced to the same CP (e.g., all relative to cuboid entities). 
    @params:
        ordered_verts   - a list of vertices, in the order you'd like them to be traversed. A closed loop may be created by repeating the zeroth element at the end of the list. No other vertex may be repeated. Only simple paths are permitted.
    @returns:
        curve           - the new curve object
    @example_usage:
        c0 = Curve([v2, v3])
        c0 = Curve([v0, v1, v2, v3, v4, v5, v0])

skeleton(entities)
    @description:
        Combines a set of vertices OR polylines/curves into a larger structure, over which additional information can be inferred. For example, within a skeleton, multiple open polylines/curves may string together to create a closed loop, a branched path, or a set of disconnected components.
    @params:
        entities        - a list of entities (vertices or polylines/curves) to be combined. A given skeleton must only have entities with the same dimension -- that is, it must consist of all points or all polylines/curves.
    @returns:
        skeleton        - the new skeleton object
    @example_usage:
        skel = skeleton([curve0, polyline1, curve2, polyline3])
        skel = skeleton([v0])


======= Lifting Procedures ========
UniformBeams(skel, thickness)
    @description:
        Procedure to lift the input skeleton to a 3D volumetric structure by instantiating a beam of the given thickness centered along each polyline/curve of the input skeleton.
    @requirements:
        The skeleton must contain only polylines and/or curves. The skeleton must not contain any standalone vertices.
    @params:
        skel            - the skeleton to lift
        thickness       - the diameter of the beams
    @returns:
        liftProc        - the lifted skeleton
    @example_usage:
        liftProcedure = UniformBeams(skel, 0.03)

SpatiallyVaryingBeams(skel, thicknessProfile)
    @description:
        Procedure to lift the input skeleton to a 3D volumetric structure by instantiating a beam of the given spatially-varying thickness profile centered along each polyline/curve of the input skeleton.
    @requirements:
        The skeleton must contain only polylines and/or curves. The skeleton must not contain any standalone vertices.
    @params:
        skel            - the skeleton to lift
        thicknessProfile- specifications for the diameter of the beams along each polyline/curve. Given as a list[list[floats]], where the each of the n inner lists gives the information for a single sample point along the polyline/curve. The first element in each inner list provides a position parameter t\in[0,1] along the polyline/curve, and the second element specifies the thickness of the beam at position t
    @returns:
        liftProc        - the lifted skeleton
    @example_usage:
        liftProcedure = SpatiallyVaryingBeams(skel, 0.03)

UniformDirectShell(skel, thickness)
    @description:
        Procedure to lift the input skeleton to a 3D volumetric structure by inferring a surface that conforms to the boundary provided by the input skeleton. The surface is given by a simple thin shell model: the resulting surface is incident on the provided boundary while minimizing a weighted sum of bending and stretching energies. The boundary is fixed, though it may be constructed with a mix of polylines and curves (which are first interpolated into a spline, then fixed as part of the boundary). The skeleton must contain a single closed loop composed of one or more polylines and/or curves. The skeleton must not contain any standalone vertices.
    @requirements:

    @params:
        skel            - the skeleton to lift
        thickness       - the thickness of the shell. The final offset is thickness/2 to each side of the inferred surface.
    @returns:
        liftProc        - the lifted skeleton
    @example_usage:
        liftProcedure = UniformDirectShell(skel, 0.1)

UniformTPMSShellViaConjugation(skel, thickness)
    @description:
        Procedure to lift the input skeleton to a 3D volumetric structure by inferring a triply periodic minimal surface (TPMS) that conforms to the boundary constraints provided by the input skeleton. The surface is computed via the conjugate surface construction method. 
    @requirements: 
        The skeleton must contain a single closed loop composed of one or more polylines and/or curves. The skeleton must not contain any standalone vertices.
        Each vertex in the polylines/curves must live on a CP edge.
        Adjacent vertices must have a shared face. 
        The loop must touch every face of the CP at least once.
        If the CP has N faces, the loop must contain at least N vertices.
    @params:
        skel            - the skeleton to lift
        thickness       - the thickness of the shell. The final offset is thickness/2 to each side of the inferred surface.
    @returns:
        liftProc        - the lifted skeleton
    @example_usage:
        liftProcedure = UniformTPMSShellViaConjugation(skel, 0.03)

UniformTPMSShellViaMixedMinimal(skel, thickness)
    @description:
        Procedure to lift the input skeleton to a 3D volumetric structure by inferring a triply periodic minimal surface (TPMS) that conforms to the boundary constraints provided by the input skeleton. The surface is computed via mean curvature flow. All polyline boundary regions are considered fixed, but any curved regions may slide within their respective planes in order to reduce surface curvature during the solve.
    @requirements: 
        The skeleton must contain a single closed loop composed of one or more polylines and/or curves. The skeleton must not contain any standalone vertices.
        Each vertex in the polylines/curves must live on a CP edge.
        Adjacent vertices must have a shared face. 
    @params:
        skel            - the skeleton to lift
        thickness       - the thickness of the shell. The final offset is thickness/2 to each side of the inferred surface.
    @returns:
        liftProc        - the lifted skeleton
    @example_usage:
        liftProcedure = UniformTPMSShellViaMixedMinimal(skel, 0.03)

Spheres(skel, thickness)
    @description:
        Procedure to lift the input skeleton to a 3D volumetric structure by instantiating a sphere of the given radius centered at vertex p, for each vertex in the skeleton.
    @requirements:
        The skeleton must only contain standalone vertices; no polylines or curves can be used.
    @params:
        skel            - the skeleton to lift
        thickness       - the sphere radius 
    @returns:
        liftProc        - the lifted skeleton
    @example_usage:
        s_lift = Spheres(skel, 0.25)


======= Tile Creation ========
Tile(lifted_skeletons, embedding)
    @description:
        Procedure to embed a copy of the skeleton in R^3 using the provided embedding information. The embedding information can be computed by calling the "embed" method of the relevant CP. 
    @requirements:
        The embedding information must correspond to the same CP against which the vertices were defined. For example, if the vertices are defined relative to the cuboid, you must use the cuboid.embed() method.
    @params:
        lifted_skeletons- a list of lifted skeleton entities to embed in R^3. All entities must reside in the same CP type, and this type must have N corners.
        embedding       - information about how to embed the CP and its relative skeletons within R^3. Obtained using the CP's embed() method
    @returns:
        tile            - the new tile object
    @example_usage:
        embedding = cuboid.embed(side_len, side_len, side_len, cornerAtAABBMin=cuboid.corners.FRONT_BOTTOM_LEFT)
        s_tile = Tile([beams, shell], embedding)


======= Patterning Procedures ========
TetFullMirror()
    @description:
        Procedure which uses only mirrors to duplicate a tet-based tile such that it partitions R^3
    @params:
        N/A
    @returns:
        pat     - the patterning procedure
    @example_usage:
        pat = TetFullMirror()

TriPrismFullMirror()
    @description:
        Procedure which uses only mirrors to duplicate a triangular prism-based tile such that it partitions R^3
    @params:
        N/A
    @returns:
        pat     - the patterning procedure
    @example_usage:
        pat = TriPrismFullMirror()

CuboidFullMirror()
    @description:
        Procedure which uses only mirrors to duplicate an axis-aligned cuboid tile such that it fills a unit cube,  such that it partitions R^3. Eligible cuboid CPs must be such that all dimensions are 1/(2^k) for some positive integer k.
    @params:
        N/A
    @returns:
        pat     - the patterning procedure
    @example_usage:
        pat = CuboidFullMirror()

Identity()
    @description:
        No-op patterning procedure.
    @params:
        N/A
    @returns:
        pat     - the patterning procedure
    @example_usage:
        pat = Identity()

Custom(patternOp)
    @description:
        Environment used to compose a custom patterning procedure. Currently only implemented for the Cuboid CP.
    @params:
        patternOp- outermost pattern operation in the composition
    @returns:
        pat     - the complete patterning procedure
    @example_usage:
        pat = Custom(Rotate180([cuboid.edges.BACK_RIGHT, cuboid.edges.BACK_LEFT], True,
                        Rotate180([cuboid.edges.TOP_RIGHT], True)))

Mirror(entity, doCopy, patternOp)
    @description:
        Pattern operation specifying a mirror over the provided CP entity, which must be a CP Face. Can only be used inside of a Custom patterning environment.
    @params:
        entity   - CP Face that serves as the mirror plane. 
        doCopy   - boolean. When True, applies the operation to a copy of the input, such that the original and the transformed copy persist. When False, directly transforms the input.
        patternOp- [OPTIONAL] outermost pattern operation in the sub-composition, if any
    @returns:
        pat      - the composed patterning procedure, which may be used as is (within the Custom environment), or as the input for further composition
    @example_usage:
        pat = Custom(Mirror(cuboid.faces.TOP, True, 
                        Mirror(cuboid.faces.LEFT, True)))

Rotate180(entities, doCopy, patternOp)
    @description:
        Pattern operation specifying a 180 degree rotation about the provided CP entity. Can only be used inside of a Custom patterning environment.
    @params:
        entities - List of CP entities, which define the axis about which to rotate. If a single entity is provided, it must be a CP Edge. If multiple entities, they will be used to define a new entity that spans them. For example, if you provide two corners, the axis will go from one to the other. If you provide two CP Edges, the axis will reach from the midpoint of one to the midpoint of the other.
        doCopy   - boolean. When True, applies the operation to a copy of the input, such that the original and the transformed copy persist. When False, directly transforms the input.
        patternOp- [OPTIONAL] outermost pattern operation in the sub-composition, if any
    @returns:
        pat      - the composed patterning procedure, which may be used as is (within the Custom environment), or as the input for further composition
    @example_usage:
        pat = Custom(Rotate180([cuboid.edges.FRONT_LEFT, cuboid.edges.FRONT_RIGHT], True))

Translate(fromEntity, toEntity, doCopy, patternOp)
    @description:
        Pattern operation specifying a translation that effectively moves the fromEntity to the targetEntity. Can only be used inside of a Custom patterning environment.
    @params:
        fromEntity- CP Entity that serves as the origin of the translation vector. Currently only implemented for a CP Face.
        toEntity  - CP Entity that serves as the target of the translation vector. Currently only implemented for a CP Face.
        doCopy   - boolean. When True, applies the operation to a copy of the input, such that the original and the transformed copy persist. When False, directly transforms the input.
        patternOp- [OPTIONAL] outermost pattern operation in the sub-composition, if any
    @returns:
        pat      - the composed patterning procedure, which may be used as is (within the Custom environment), or as the input for further composition
    @example_usage:
        gridPat = Custom(Translate(cuboid.faces.LEFT, cuboid.faces.RIGHT, True,
                                Translate(cuboid.faces.FRONT, cuboid.faces.BACK, True)))


======= Structure Procedures ========
Structure(tile, pattern)
    @description:
        Combines local tile information (containing lifted skeletons) with the global patterning procedure to generate a complete metamaterial.
    @params:
        tile            - the tile object, which has (by construction) already been embedded in 3D space, along with all lifted skeletons it contains.
        pattern         - the patterning sequence to apply to extend this tile throughout space
    @returns:
        structure       - the new structure object
    @example_usage:
        obj = Structure(tile, pat)

Union(A, B)
    @description:
        Constructive solid geometry Boolean operation that computes the union of two input structures. The output of Union(A,B) is identical to Union(B,A)
    @params:
        A               - the first Structure to be unioned. This may be the output of Structure, Union, Subtract, or Intersect
        B               - the second Structure to be unioned. This may be the output of Structure, Union, Subtract, or Intersect
    @returns:
        structure       - the new structure object containing union(A,B)
    @example_usage:
        final_obj = Union(schwarzP_obj, Union(sphere_obj, beam_obj))

Subtract(A, B)
    @description:
        Constructive solid geometry Boolean operation that computes the difference (A - B) of two input structures. The relative input order is critical.
    @params:
        A               - the first Structure, from which B will be subtracted. This may be the output of Structure, Union, Subtract, or Intersect
        B               - the second Structure, to be subtracted from A. This may be the output of Structure, Union, Subtract, or Intersect
    @returns:
        structure       - the new structure object containing (A - B)
    @example_usage:
        final_obj = Subtract(c_obj, s_obj)

Intersect(A, B)
    @description:
        Constructive solid geometry Boolean operation that computes the intersection of two input structures, A and B. 
    @params:
        A               - the first Structure, which may be the output of Structure, Union, Subtract, or Intersect
        B               - the second Structure, which may be the output of Structure, Union, Subtract, or Intersect
    @returns:
        structure       - the new structure object containing the intersection of A and B
    @example_usage:
        final_obj = Intersect(c_obj, s_obj)




==================================
    Prebuilt Convex Polytopes
==================================
There are 3 prebuilt convex polytopes (CP) available for use: cuboid, triPrism, and tet. Each CP comprises a set of Entities, namely faces, edges and corners. 
For convenience, each individual entity can be referenced using the pattern <CP>.<entity_type>.<ENTITY_NAME>. 
For example, you can select a particular edge of the cuboid with the notation cuboid.edges.BOTTOM_RIGHT.
Each CP also has an embed() method which returns all necessary information to embed the CP within R^3.

The full list of entities and embed() method signatures for our predefined CPs are as follows:

tet.corners.{   BOTTOM_RIGHT,
                BOTTOM_LEFT,
                TOP_BACK,
                BOTTOM_BACK
            }
tet.edges.  {   BOTTOM_FRONT,
                TOP_LEFT,
                BACK,
                BOTTOM_RIGHT,
                TOP_RIGHT,
                BOTTOM_LEFT
            }
tet.faces.  {   BOTTOM,
                TOP,
                RIGHT,
                LEFT
            }
tet.embed(bounding_box_side_length)
    @description:
        Constructs the information required to embed the tet CP in R^3
    @params:
        bounding_box_side_length- length of axis-aligned bounding box containing the tet. Float in range [0,1]. Must be 1/2^k for some integer k
    @returns:
        embedding      - the embedding information. Specifically, the position in R^3 of all the CP corners. 
    @example_usage:
        side_len = 0.5 / num_tiling_unit_repeats_per_dim
        embedding = tet.embed(side_len)


triPrism.corners.{FRONT_BOTTOM_LEFT,
                FRONT_TOP,
                FRONT_BOTTOM_RIGHT,
                BACK_BOTTOM_LEFT,
                BACK_TOP,
                BACK_BOTTOM_RIGHT
            }
triPrism.edges.{FRONT_LEFT,
                FRONT_RIGHT,
                FRONT_BOTTOM,
                BACK_LEFT,
                BACK_RIGHT,
                BACK_BOTTOM,
                BOTTOM_LEFT,
                TOP,
                BOTTOM_RIGHT
            }
triPrism.faces.{FRONT_TRI,
                BACK_TRI,
                LEFT_QUAD,
                RIGHT_QUAD,
                BOTTOM_QUAD
            }
triPrism.embed(bounding_box_side_length)
    @description:
        Constructs the information required to embed the triangular prism CP in R^3
    @params:
        bounding_box_side_length - length of axis-aligned bounding box containing the triangular prism. Float in range [0,1]. Must be 1/2^k for some integer k
    @returns:
        embedding      - the embedding information. Specifically, the position in R^3 of all the CP corners. 
    @example_usage:
        side_len = 0.5 / num_tiling_unit_repeats_per_dim
        embedding = triPrism.embed(side_len)


cuboid.corners.{FRONT_BOTTOM_LEFT,
                FRONT_BOTTOM_RIGHT,
                FRONT_TOP_LEFT,
                FRONT_TOP_RIGHT,
                BACK_BOTTOM_LEFT,
                BACK_BOTTOM_RIGHT,
                BACK_TOP_LEFT,
                BACK_TOP_RIGHT
            }
cuboid.edges.{  FRONT_BOTTOM,
                FRONT_LEFT,
                FRONT_TOP,
                FRONT_RIGHT,
                BACK_BOTTOM,
                BACK_LEFT,
                BACK_TOP,
                BACK_RIGHT,
                BOTTOM_LEFT,
                TOP_LEFT,
                TOP_RIGHT,
                BOTTOM_RIGHT
            }
cuboid.faces.{  FRONT,
                BACK,
                TOP,
                BOTTOM,
                LEFT,
                RIGHT
            }
            
cuboid.embed(width, height, depth, cornerAtAABBMin)
    @description:
        Constructs the information required to embed the cuboid CP in R^3
    @params:
        width          - length of cuboid side from left to right. float in range [0,1]. Must be 1/2^k for some integer k
        height         - length of cuboid side from top to bottom. float in range [0,1]. Must be 1/2^k for some integer k
        depth          - length of cuboid side from front to back. float in range [0,1]. Must be 1/2^k for some integer k
        cornerAtAABBMin- CP corner entity (e.g., cuboid.corners.FRONT_BOTTOM_LEFT) that should be collocated with the cuboid's minimum position in R^3
    @returns:
        embedding      - the embedding information. Specifically, the position in R^3 of all the CP corners. 
    @example_usage:
        side_len = 0.5 / num_tiling_unit_repeats_per_dim
        embedding = cuboid.embed(side_len, side_len, side_len, cornerAtAABBMin=cuboid.corners.FRONT_BOTTOM_LEFT)

cuboid.embed_via_minmax(aabb_min_pt, aabb_max_pt, cornerAtMinPt)
    @description:
        Constructs the information required to embed the cuboid CP in R^3
    @params:
        aabb_min_pt    - Minimum point of the cuboid, in R^3. Given as a list of length 3, where each component must be a float in range [0,1], with 1/2^k for some integer k
        aabb_max_pt    - Maximum point of the cuboid, in R^3. Given as a list of length 3, where each component must be a float in range [0,1], with 1/2^k for some integer k
        cornerAtMinPt  - CP corner entity (e.g., cuboid.corners.FRONT_BOTTOM_LEFT) that should be collocated with the cuboid's minimum position in R^3
    @returns:
        embedding      - the embedding information. Specifically, the position in R^3 of all the CP corners. 
    @example_usage:
        side_len = 0.5 / num_tiling_unit_repeats_per_dim
        embedding = cuboid.embed_via_minmax([0,0,0], [side_len, side_len, side_len], cuboid.corners.BACK_BOTTOM_RIGHT)
