/// This module contains functions for modifying solids, e.g., by adding a fillet or chamfer, or
/// removing part of a solid.

@no_std
@settings(defaultLengthUnit = mm, kclVersion = 1.0, experimentalFeatures = allow)

import Face from "std::types"

/// Blend a transitional edge along a tagged path, smoothing the sharp edge.
///
/// Fillet is similar in function and use to a chamfer, except
/// a chamfer will cut a sharp transition along an edge while fillet
/// will smoothly blend the transition.
///
/// ```
/// width = 20
/// length = 10
/// thickness = 1
/// filletRadius = 2
///
/// mountingPlateSketch = startSketchOn(XY)
///   |> startProfile(at = [-width/2, -length/2])
///   |> line(endAbsolute = [width/2, -length/2], tag = $edge1)
///   |> line(endAbsolute = [width/2, length/2], tag = $edge2)
///   |> line(endAbsolute = [-width/2, length/2], tag = $edge3)
///   |> close(tag = $edge4)
///
/// mountingPlate = extrude(mountingPlateSketch, length = thickness)
///   |> fillet(
///     radius = filletRadius,
///     tags = [
///       getNextAdjacentEdge(edge1),
///       getNextAdjacentEdge(edge2),
///       getNextAdjacentEdge(edge3),
///       getNextAdjacentEdge(edge4)
///     ],
///   )
/// ```
///
/// ```
/// width = 20
/// length = 10
/// thickness = 1
/// filletRadius = 1
///
/// mountingPlateSketch = startSketchOn(XY)
///   |> startProfile(at = [-width/2, -length/2])
///   |> line(endAbsolute = [width/2, -length/2], tag = $edge1)
///   |> line(endAbsolute = [width/2, length/2], tag = $edge2)
///   |> line(endAbsolute = [-width/2, length/2], tag = $edge3)
///   |> close(tag = $edge4)
///
/// mountingPlate = extrude(mountingPlateSketch, length = thickness)
///   |> fillet(
///     radius = filletRadius,
///     tolerance = 0.000001,
///     tags = [
///       getNextAdjacentEdge(edge1),
///       getNextAdjacentEdge(edge2),
///       getNextAdjacentEdge(edge3),
///       getNextAdjacentEdge(edge4)
///     ],
///   )
/// ```
@(impl = std_rust, feature_tree = true)
export fn fillet(
  /// The solid whose edges should be filletted
  @solid: Solid,
  /// The radius of the fillet
  radius: number(Length),
  /// The paths you want to fillet
  tags: [Edge; 1+],
  /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
  tolerance?: number(Length),
  /// Create a new tag which refers to this fillet
  tag?: TagDecl,
): Solid {}

/// Cut a straight transitional edge along a tagged path.
///
/// Chamfer is similar in function and use to a fillet, except
/// a fillet will blend the transition along an edge, rather than cut
/// a sharp, straight transitional edge.
///
/// ```
/// // Chamfer a mounting plate.
/// width = 20
/// length = 10
/// thickness = 1
/// chamferLength = 2
///
/// mountingPlateSketch = startSketchOn(XY)
///   |> startProfile(at = [-width/2, -length/2])
///   |> line(endAbsolute = [width/2, -length/2], tag = $edge1)
///   |> line(endAbsolute = [width/2, length/2], tag = $edge2)
///   |> line(endAbsolute = [-width/2, length/2], tag = $edge3)
///   |> close(tag = $edge4)
///
/// mountingPlate = extrude(mountingPlateSketch, length = thickness)
///   |> chamfer(
///     length = chamferLength,
///     tags = [
///       getNextAdjacentEdge(edge1),
///       getNextAdjacentEdge(edge2),
///       getNextAdjacentEdge(edge3),
///       getNextAdjacentEdge(edge4)
///     ],
///   )
/// ```
///
/// ```
/// // Sketch on the face of a chamfer.
/// fn cube(pos, scale) {
/// sg = startSketchOn(XY)
///     |> startProfile(at = pos)
///     |> line(end = [0, scale])
///     |> line(end = [scale, 0])
///     |> line(end = [0, -scale])
///
///     return sg
/// }
///
/// part001 = cube(pos = [0,0], scale = 20)
///     |> close(tag = $line1)
///     |> extrude(length = 20)
///     // We tag the chamfer to reference it later.
///     |> chamfer(
///         length = 10,
///         tags = [getOppositeEdge(line1)],
///         tag = $chamfer1,
///     )  
///
/// sketch001 = startSketchOn(part001, face = chamfer1)
///     |> startProfile(at = [10, 10])
///     |> line(end = [2, 0])
///     |> line(end = [0, 2])
///     |> line(end = [-2, 0])
///     |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
///     |> close()
///     |> extrude(length = 10)
/// ```
///
/// ```
/// // Specify a custom chamfer angle.
/// fn cube(pos, scale) {
/// sg = startSketchOn(XY)
///     |> startProfile(at = pos)
///     |> line(end = [0, scale])
///     |> line(end = [scale, 0])
///     |> line(end = [0, -scale])
///
///     return sg
/// }
///
/// part001 = cube(pos = [0,0], scale = 20)
///     |> close(tag = $line1)
///     |> extrude(length = 20)
///     |> chamfer(
///         length = 10,
///         angle = 30deg,
///         tags = [getOppositeEdge(line1)],
///     )
/// ```
@(impl = std_rust, feature_tree = true)
export fn chamfer(
  /// The solid whose edges should be chamfered
  @solid: Solid,
  /// Chamfering cuts away two faces to create a third face.
  /// This is the length to chamfer away from each face.
  /// The larger this length to chamfer away, the larger the
  /// new face will be.
  length: number(Length),
  /// The paths you want to chamfer
  tags: [Edge; 1+],
  /// Chamfering cuts away two faces to create a third face.
  /// If this argument isn't given, the lengths chamfered away from both
  /// the first and second face are both given by `length`.
  /// If this argument _is_ given, it determines how much is cut away
  /// from the second face. Incompatible with `angle`.
  secondLength?: number(Length),
  /// Chamfering cuts away two faces to create a third face.
  /// This argument determines the angle between the two cut edges.
  /// Requires `length`, incompatible with `secondLength`.
  /// The valid range is 0deg < angle < 90deg.
  angle?: number(Angle),
  /// Create a new tag which refers to this chamfer
  tag?: TagDecl,
): Solid {}

/// Remove volume from a 3-dimensional shape such that a wall of the
/// provided thickness remains, taking volume starting at the provided
/// face, leaving it open in that direction.
///
/// ```
/// // Remove the end face for the extrusion.
/// firstSketch = startSketchOn(XY)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0])
///     |> close()
///     |> extrude(length = 6)
///
/// // Remove the end face for the extrusion.
/// shell(
///     firstSketch,
///     faces = [END],
///     thickness = 0.25,
/// )
/// ```
///
/// ```
/// // Remove the start face for the extrusion.
/// firstSketch = startSketchOn(-XZ)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0])
///     |> close()
///     |> extrude(length = 6)
///
/// // Remove the start face for the extrusion.
/// shell(
///     firstSketch,
///     faces = [START],
///     thickness = 0.25,
/// )
/// ```
///
/// ```
/// // Remove a tagged face and the end face for the extrusion.
/// firstSketch = startSketchOn(XY)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0], tag = $myTag)
///     |> close()
///     |> extrude(length = 6)
///
/// // Remove a tagged face for the extrusion.
/// shell(
///     firstSketch,
///     faces = [myTag],
///     thickness = 0.25,
/// )
/// ```
///
/// ```
/// // Remove multiple faces at once.
/// firstSketch = startSketchOn(XY)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0], tag = $myTag)
///     |> close()
///     |> extrude(length = 6)
///
/// // Remove a tagged face and the end face for the extrusion.
/// shell(
///     firstSketch,
///     faces = [myTag, END],
///     thickness = 0.25,
/// )
/// ```
///
/// ```
/// // Shell a sketch on face.
/// size = 100
/// case = startSketchOn(-XZ)
///     |> startProfile(at = [-size, -size])
///     |> line(end = [2 * size, 0])
///     |> line(end = [0, 2 * size])
///     |> tangentialArc(endAbsolute = [-size, size])
///     |> close()
///     |> extrude(length = 65)
///
/// thing1 = startSketchOn(case, face = END)
///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
///     |> extrude(length = 50)
///
/// thing2 = startSketchOn(case, face = END)
///     |> circle( center = [size / 2, -size / 2], radius = 25 )
///     |> extrude(length = 50)
///
/// // We put "case" in the shell function to shell the entire object.
/// shell(case, faces = [START], thickness = 5)
/// ```
///
/// ```
/// // Shell a sketch on face object on the end face.
/// size = 100
/// case = startSketchOn(XY)
///     |> startProfile(at = [-size, -size])
///     |> line(end = [2 * size, 0])
///     |> line(end = [0, 2 * size])
///     |> tangentialArc(endAbsolute = [-size, size])
///     |> close()
///     |> extrude(length = 65)
///
/// thing1 = startSketchOn(case, face = END)
///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
///     |> extrude(length = 50)
///
/// thing2 = startSketchOn(case, face = END)
///     |> circle( center = [size / 2, -size / 2], radius = 25 )
///     |> extrude(length = 50)
///
/// // We put "thing1" in the shell function to shell the end face of the object.
/// shell(thing1, faces = [END], thickness = 5)
/// ```
///
/// ```
/// // Shell sketched on face objects on the end face, include all sketches to shell
/// // the entire object.
///
/// size = 100
/// case = startSketchOn(XY)
///     |> startProfile(at = [-size, -size])
///     |> line(end = [2 * size, 0])
///     |> line(end = [0, 2 * size])
///     |> tangentialArc(endAbsolute = [-size, size])
///     |> close()
///     |> extrude(length = 65)
///
/// thing1 = startSketchOn(case, face = END)
///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
///     |> extrude(length = 50)
///
/// thing2 = startSketchOn(case, face = END)
///     |> circle( center = [size / 2, -size / 2], radius = 25)
///     |> extrude(length = 50)
///
/// // We put "thing1" and "thing2" in the shell function to shell the end face of the object.
/// shell([thing1, thing2], faces = [END], thickness = 5)
/// ```
@(impl = std_rust, feature_tree = true)
export fn shell(
  /// Which solid (or solids) to shell out
  @solids: [Solid; 1+],
  /// The thickness of the shell
  thickness: number(Length),
  /// The faces you want removed
  faces: [TaggedFace; 1+],
): [Solid] {}


/// Make the inside of a 3D object hollow.
///
/// Remove volume from a 3-dimensional shape such that a wall of the
/// provided thickness remains around the exterior of the shape.
///
/// ```
/// // Hollow a basic sketch.
/// firstSketch = startSketchOn(XY)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0])
///     |> close()
///     |> extrude(length = 6)
///     |> hollow (thickness = 0.25)
/// ```
///
/// ```
/// // Hollow a basic sketch.
/// firstSketch = startSketchOn(-XZ)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0])
///     |> close()
///     |> extrude(length = 6)
///     |> hollow (thickness = 0.5)
/// ```
///
/// ```
/// // Hollow a sketch on face object.
/// size = 100
/// case = startSketchOn(-XZ)
///     |> startProfile(at = [-size, -size])
///     |> line(end = [2 * size, 0])
///     |> line(end = [0, 2 * size])
///     |> tangentialArc(endAbsolute = [-size, size])
///     |> close()
///     |> extrude(length = 65)
///
/// thing1 = startSketchOn(case, face = END)
///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
///     |> extrude(length = 50)
///
/// thing2 = startSketchOn(case, face = END)
///     |> circle( center = [size / 2, -size / 2], radius = 25 )
///     |> extrude(length = 50)
///
/// hollow(case, thickness = 0.5)
/// ```
@(impl = std_rust, feature_tree = true)
export fn hollow(
  /// Which solid to hollow out
  @solid: Solid,
  /// The thickness of the remaining shell
  thickness: number(Length),
): Solid {}

/// Repeat a 3-dimensional solid, changing it each time.
///
/// Replicates the 3D solid, applying a transformation function to each replica.
/// Transformation function could alter rotation, scale, visibility, position, etc.
///
/// The `patternTransform` call itself takes a number for how many total instances of
/// the shape should be. For example, if you use a circle with `patternTransform(instances = 4, transform = f)`
/// then there will be 4 circles: the original, and 3 created by replicating the original and
/// calling the transform function on each.
///
/// The transform function takes a single parameter: an integer representing which
/// number replication the transform is for. E.g. the first replica to be transformed
/// will be passed the argument `1`. This simplifies your math: the transform function can
/// rely on id `0` being the original instance passed into the `patternTransform`. See the examples.
///
/// The transform function returns a transform object. All properties of the object are optional,
/// they each default to "no change". So the overall transform object defaults to "no change" too.
/// Its properties are:
///
///  - `translate` (3D point)
///
///    Translates the replica, moving its position in space.      
///
///  - `replicate` (bool)
///
///    If false, this ID will not actually copy the object. It'll be skipped.
///
///  - `scale` (3D point)
///
///    Stretches the object, multiplying its width in the given dimension by the point's component in
///    that direction.      
///
///  - `rotation` (object, with the following properties)
///
///    - `rotation.axis` (a 3D point, defaults to the Z axis)
///
///    - `rotation.angle`
///
///    - `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
///
/// ```kcl
/// // Each instance will be shifted along the X axis.
/// fn transform(@id) {
///   return { translate = [4 * id, 0, 0] }
/// }
///
/// // Sketch 4 cylinders.
/// sketch001 = startSketchOn(XZ)
///   |> circle(center = [0, 0], radius = 2)
///   |> extrude(length = 5)
///   |> patternTransform(instances = 4, transform = transform)
/// ```
///
/// ```kcl
/// // Each instance will be shifted along the X axis,
/// // with a gap between the original (at x = 0) and the first replica
/// // (at x = 8). This is because `id` starts at 1.
/// fn transform(@id) {
///   return { translate = [4 * (1+id), 0, 0] }
/// }
///
/// sketch001 = startSketchOn(XZ)
///   |> circle(center = [0, 0], radius = 2)
///   |> extrude(length = 5)
///   |> patternTransform(instances = 4, transform = transform)
/// ```
///
/// ```kcl
/// fn cube(length, center) {
///   l = length/2
///   x = center[0]
///   y = center[1]
///   p0 = [-l + x, -l + y]
///   p1 = [-l + x,  l + y]
///   p2 = [ l + x,  l + y]
///   p3 = [ l + x, -l + y]
///
///   return startSketchOn(XY)
///   |> startProfile(at = p0)
///   |> line(endAbsolute = p1)
///   |> line(endAbsolute = p2)
///   |> line(endAbsolute = p3)
///   |> line(endAbsolute = p0)
///   |> close()
///   |> extrude(length = length)
/// }
///
/// width = 20
/// fn transform(@i) {
///   return {
///     // Move down each time.
///     translate = [0, 0, -i * width],
///     // Make the cube longer, wider and flatter each time.
///     scale = [pow(1.1, exp = i), pow(1.1, exp = i), pow(0.9, exp = i)],
///     // Turn by 15 degrees each time.
///     rotation = {
///       angle = 15deg * i,
///       origin = "local",
///     }
///   }
/// }
///
/// myCubes =
///   cube(length = width, center = [100,0])
///   |> patternTransform(instances = 25, transform = transform)
/// ```
///
/// ```kcl
/// fn cube(length, center) {
///   l = length/2
///   x = center[0]
///   y = center[1]
///   p0 = [-l + x, -l + y]
///   p1 = [-l + x,  l + y]
///   p2 = [ l + x,  l + y]
///   p3 = [ l + x, -l + y]
///   
///   return startSketchOn(XY)
///   |> startProfile(at = p0)
///   |> line(endAbsolute = p1)
///   |> line(endAbsolute = p2)
///   |> line(endAbsolute = p3)
///   |> line(endAbsolute = p0)
///   |> close()
///   |> extrude(length = length)
/// }
///
/// width = 20
/// fn transform(@i) {
///   return {
///     translate = [0, 0, -i * width],
///     rotation = {
///       angle = 90deg * i,
///       // Rotate around the overall scene's origin.
///       origin = "global",
///     }
///   }
/// }
/// myCubes =
///   cube(length = width, center = [100,100])
///   |> patternTransform(instances = 4, transform = transform)
/// ```
///
/// ```kcl
/// // Parameters
/// r = 50    // base radius
/// h = 10    // layer height
/// t = 0.005 // taper factor [0-1)
/// // Defines how to modify each layer of the vase.
/// // Each replica is shifted up the Z axis, and has a smoothly-varying radius
/// fn transform(@replicaId) {
///   scale = r * abs(1 - (t * replicaId)) * (5 + cos((replicaId / 8): number(rad)))
///   return {
///     translate = [0, 0, replicaId * 10],
///     scale = [scale, scale, 0],
///   }
/// }
/// // Each layer is just a pretty thin cylinder.
/// fn layer() {
///   return startSketchOn(XY) // or some other plane idk
///     |> circle(center = [0, 0], radius = 1, tag = $tag1)
///     |> extrude(length = h)
/// }
/// // The vase is 100 layers tall.
/// // The 100 layers are replica of each other, with a slight transformation applied to each.
/// vase = layer() |> patternTransform(instances = 100, transform = transform)
/// ```
///
/// ```kcl
/// fn transform(@i) {
///   // Transform functions can return multiple transforms. They'll be applied in order.
///   return [
///     { translate = [30 * i, 0, 0] },
///     { rotation = { angle = 45deg * i } },
///   ]
/// }
/// startSketchOn(XY)
///   |> startProfile(at = [0, 0])
///   |> polygon(
///        radius = 10,
///        numSides = 4,
///        center = [0, 0],
///        inscribed = false,
///      )
///   |> extrude(length = 4)
///   |> patternTransform(instances = 3, transform = transform)
/// ```
/// ```kcl
/// n = 5
/// width = 10
/// gap = 1.5 * width
/// 
/// // Transform function
/// fn chessboard(@i) {
///   row = rem(i, divisor = n)
///   column = floor(i / n)
///   isEven = rem(i, divisor = 2) == 0
///   return [{ translate = [row * gap, column * gap, 0], replicate = isEven }]
/// }
/// 
/// startSketchOn(XY)
///   |> polygon(numSides = 4, radius = width, center = [0, 0])
///   |> extrude(length = 2)
///   |> rotate(yaw = 45)
///   |> patternTransform(instances = n * n, transform = chessboard)
/// ```
@(impl = std_rust, feature_tree = true)
export fn patternTransform(
  /// The solid(s) to duplicate.
  @solids: [Solid; 1+],
  /// The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect.
  instances: number(Count),
  /// How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples.
  transform: fn(number(Count)): {},
  /// If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid.
  useOriginal?: bool = false,
): [Solid; 1+] {}

/// Repeat a 3-dimensional solid along a linear path, with a dynamic amount
/// of distance between each repetition, some specified number of times.
///
/// ```kcl
/// /// Pattern using a named axis.
///
/// exampleSketch = startSketchOn(XZ)
///   |> startProfile(at = [0, 0])
///   |> line(end = [0, 2])
///   |> line(end = [3, 1])
///   |> line(end = [0, -4])
///   |> close()
///
/// example = extrude(exampleSketch, length = 1)
///   |> patternLinear3d(
///       axis = X,
///       instances = 7,
///       distance = 6
///     )
/// ```
///
/// ```kcl
/// /// Pattern using a raw axis.
///
/// exampleSketch = startSketchOn(XZ)
///   |> startProfile(at = [0, 0])
///   |> line(end = [0, 2])
///   |> line(end = [3, 1])
///   |> line(end = [0, -4])
///   |> close()
///
/// example = extrude(exampleSketch, length = 1)
///   |> patternLinear3d(
///       axis = [1, 0, 1],
///       instances = 7,
///       distance = 6
///     )
/// ```
///
/// ```kcl
/// // Pattern a whole sketch on face.
/// size = 100
/// case = startSketchOn(XY)
///     |> startProfile(at = [-size, -size])
///     |> line(end = [2 * size, 0])
///     |> line(end = [0, 2 * size])
///     |> tangentialArc(endAbsolute = [-size, size])
///     |> close(%)
///     |> extrude(length = 65)
///
/// thing1 = startSketchOn(case, face = END)
///     |> circle(center = [-size / 2, -size / 2], radius = 25)
///     |> extrude(length = 50)
///
/// thing2 = startSketchOn(case, face = END)
///     |> circle(center = [size / 2, -size / 2], radius = 25)
///     |> extrude(length = 50)
///
/// // We pass in the "case" here since we want to pattern the whole sketch.
/// // And the case was the base of the sketch.
/// patternLinear3d(case,
///     axis= [1, 0, 0],
///     distance= 250,
///     instances=2,
///  )
/// ```
///
/// ```kcl
/// // Pattern an object on a face.
/// size = 100
/// case = startSketchOn(XY)
///     |> startProfile(at = [-size, -size])
///     |> line(end = [2 * size, 0])
///     |> line(end = [0, 2 * size])
///     |> tangentialArc(endAbsolute = [-size, size])
///     |> close(%)
///     |> extrude(length = 65)
///
/// thing1 = startSketchOn(case, face = END)
///     |> circle(center =[-size / 2, -size / 2], radius = 25)
///     |> extrude(length = 50)
///
/// // We pass in `thing1` here with `useOriginal` since we want to pattern just this object on the face.
/// patternLinear3d(thing1,
///     axis = [1, 0, 0],
///     distance = size,
///     instances =2,
///     useOriginal = true
/// )
/// ```
@(impl = std_rust, feature_tree = true)
export fn patternLinear3d(
  /// The solid(s) to duplicate.
  @solids: [Solid; 1+],
  /// The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect.
  instances: number(Count),
  /// Distance between each repetition. Also known as 'spacing'.
  distance: number(Length),
  /// The axis of the pattern. A 3D vector.
  @(snippetArray = ["1", "0", "0"])
  axis: Axis3d | Point3d,
  /// If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid.
  useOriginal?: bool = false,
): [Solid; 1+] {}

/// Repeat a 3-dimensional solid some number of times along a partial or
/// complete circle some specified number of times. Each object may
/// additionally be rotated along the circle, ensuring orientation of the
/// solid with respect to the center of the circle is maintained.
///
/// ```kcl
/// /// Pattern using a named axis.
///
/// exampleSketch = startSketchOn(XZ)
///   |> circle(center = [0, 0], radius = 1)
///
/// example = extrude(exampleSketch, length = -5)
///   |> patternCircular3d(
///        axis = X,
///        center = [10, -20, 0],
///        instances = 11,
///        arcDegrees = 360,
///        rotateDuplicates = true
///      )
/// ```
///
/// ```kcl
/// /// Pattern using a raw axis.
///
/// exampleSketch = startSketchOn(XZ)
///   |> circle(center = [0, 0], radius = 1)
///
/// example = extrude(exampleSketch, length = -5)
///   |> patternCircular3d(
///        axis = [1, -1, 0],
///        center = [10, -20, 0],
///        instances = 11,
///        arcDegrees = 360,
///        rotateDuplicates = true
///      )
/// ```
@(impl = std_rust, feature_tree = true)
export fn patternCircular3d(
  /// The solid(s) to pattern.
  @solids: [Solid; 1+],
  /// The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect.
  instances: number(Count),
  /// The axis of the pattern. A 3D vector.
  @(snippetArray = ["1", "0", "0"])
  axis: Axis3d | Point3d,
  /// The center about which to make the pattern. This is a 3D vector.
  @(snippetArray = ["0", "0", "0"])
  center: Point3d,
  /// "The arc angle to place the repetitions. Must be greater than 0.
  arcDegrees?: number(deg) = 360deg,
  /// Whether or not to rotate the duplicates as they are copied.
  rotateDuplicates?: bool = true,
  /// If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid.
  useOriginal?: bool = false,
): [Solid; 1+] {}

/// Union two or more solids into a single solid.
///
/// ```kcl
/// // Union two cubes using the stdlib functions.
///
/// fn cube(center, size) {
///     return startSketchOn(XY)
///         |> startProfile(at = [center[0] - size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] + size])
///         |> line(endAbsolute = [center[0] - size, center[1] + size])
///         |> close()
///         |> extrude(length = 10)
/// }
///
/// part001 = cube(center = [0, 0], size = 10)
/// part002 = cube(center = [7, 3], size = 5)
///     |> translate(z = 1)
///
/// unionedPart = union([part001, part002])
/// ```
///
/// ```kcl
/// // Union two cubes using operators.
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center, size) {
///     return startSketchOn(XY)
///         |> startProfile(at = [center[0] - size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] + size])
///         |> line(endAbsolute = [center[0] - size, center[1] + size])
///         |> close()
///         |> extrude(length = 10)
/// }
///
/// part001 = cube(center = [0, 0], size = 10)
/// part002 = cube(center = [7, 3], size = 5)
///     |> translate(z = 1)
///
/// // This is the equivalent of: union([part001, part002])
/// unionedPart = part001 + part002
/// ```
///
/// ```kcl
/// // Union two cubes using the more programmer-friendly operator.
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center, size) {
///     return startSketchOn(XY)
///         |> startProfile(at = [center[0] - size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] + size])
///         |> line(endAbsolute = [center[0] - size, center[1] + size])
///         |> close()
///         |> extrude(length = 10)
/// }
///
/// part001 = cube(center = [0, 0], size = 10)
/// part002 = cube(center = [7, 3], size = 5)
///     |> translate(z = 1)
///
/// // This is the equivalent of: union([part001, part002])
/// // Programmers will understand `|` as a union operation, but mechanical engineers
/// // will understand `+`, we made both work.
/// unionedPart = part001 | part002
/// ```
@(impl = std_rust, feature_tree = true)
export fn union(
  /// The solids to union.
  @solids: [Solid; 2+],
  /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
  tolerance?: number(Length),
): [Solid; 1+] {}

/// Intersect returns the shared volume between multiple solids, preserving only
/// overlapping regions.
///
/// Intersect computes the geometric intersection of multiple solid bodies,
/// returning a new solid representing the volume that is common to all input
/// solids. This operation is useful for determining shared material regions,
/// verifying fit, and analyzing overlapping geometries in assemblies.
///
/// ```kcl
/// // Intersect two cubes using the stdlib functions.
///
/// fn cube(center, size) {
///     return startSketchOn(XY)
///         |> startProfile(at = [center[0] - size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] + size])
///         |> line(endAbsolute = [center[0] - size, center[1] + size])
///         |> close()
///         |> extrude(length = 10)
/// }
///
/// part001 = cube(center = [0, 0], size = 10)
/// part002 = cube(center = [7, 3], size = 5)
///     |> translate(z = 1)
///
/// intersectedPart = intersect([part001, part002])
/// ```
///
/// ```kcl
/// // Intersect two cubes using operators.
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center, size) {
///     return startSketchOn(XY)
///         |> startProfile(at = [center[0] - size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] + size])
///         |> line(endAbsolute = [center[0] - size, center[1] + size])
///         |> close()
///         |> extrude(length = 10)
/// }
///
/// part001 = cube(center = [0, 0], size = 10)
/// part002 = cube(center = [7, 3], size = 5)
///     |> translate(z = 1)
///
/// // This is the equivalent of: intersect([part001, part002])
/// intersectedPart = part001 & part002
/// ```
@(impl = std_rust, feature_tree = true)
export fn intersect(
  /// The solids to intersect.
  @solids: [Solid; 2+],
  /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
  tolerance?: number(Length),
): [Solid; 1+] {}

/// Subtract removes tool solids from base solids, leaving the remaining material.
///
/// Performs a bool subtraction operation, removing the volume of one or more
/// tool solids from one or more base solids. The result is a new solid
/// representing the material that remains after all tool solids have been cut
/// away. This function is essential for machining simulations, cavity creation,
/// and complex multi-body part modeling.
///
/// ```kcl
/// // Subtract a cylinder from a cube using the stdlib functions.
///
/// fn cube(center, size) {
///     return startSketchOn(XY)
///         |> startProfile(at = [center[0] - size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] + size])
///         |> line(endAbsolute = [center[0] - size, center[1] + size])
///         |> close()
///         |> extrude(length = 10)
/// }
///
/// part001 = cube(center = [0, 0], size = 10)
/// part002 = cube(center = [7, 3], size = 5)
///     |> translate(z = 1)
///
/// subtractedPart = subtract([part001], tools=[part002])
/// ```
///
/// ```kcl
/// // Subtract a cylinder from a cube using operators.
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center, size) {
///     return startSketchOn(XY)
///         |> startProfile(at = [center[0] - size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] - size])
///         |> line(endAbsolute = [center[0] + size, center[1] + size])
///         |> line(endAbsolute = [center[0] - size, center[1] + size])
///         |> close()
///         |> extrude(length = 10)
/// }
///
/// part001 = cube(center = [0, 0], size = 10)
/// part002 = cube(center = [7, 3], size = 5)
///     |> translate(z = 1)
///
/// // This is the equivalent of: subtract([part001], tools=[part002])
/// subtractedPart = part001 - part002
/// ```
/// ```kcl
/// @settings(defaultLengthUnit = in)
/// 
/// height = 2.5
/// width = 2.5
/// bodyLength = 6
/// mountCenterToCenter = 1.625
/// rodThreadLength = 0.75
/// strokeLength = 1
/// boreDiameter = 1.5
/// rodDiameter = 0.625
/// portCenterToCenter = 3.875
/// rodThreadSize = 0.438
/// 
/// // Sketch a cube.
/// sketch001 = startSketchOn(YZ)
/// profile001 = startProfile(sketch001, at = [-width / 2, -height / 2])
///   |> angledLine(angle = 0deg, length = width, tag = $rectangleSegmentA001)
///   |> angledLine(angle = segAng(rectangleSegmentA001) + 90deg, length = height, tag = $seg01)
///   |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg02)
///   |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
///   |> close()
/// blueCube = extrude(profile001, length = -1.5)
/// appearance(blueCube, color = '#9dcfed')
/// 
/// // Sketch a cylinder.
/// sketch002 = startSketchOn(blueCube, face = START)
/// profile002 = circle(sketch002, center = [0, 0], radius = boreDiameter / 2)
/// profile003 = circle(sketch002, center = [0, 0], radius = boreDiameter / 2 + 0.1)
///   |> subtract2d(tool = profile002)
/// cylinder = extrude(profile003, length = 2.375, method = NEW)
/// appearance(cylinder, color = '#9dcfed')
/// 
/// // Sketch a second cube.
/// sketch003 = startSketchOn(cylinder, face = END)
/// profile004 = startProfile(sketch003, at = [-width / 2, -height / 2])
///   |> angledLine(angle = 0deg, length = width, tag = $rectangleSegmentA002)
///   |> angledLine(angle = segAng(rectangleSegmentA002) + 90deg, length = height)
///   |> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002))
///   |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
///   |> close()
/// brownCube = extrude(profile004, length = 1.75)
/// |> appearance(color = "#da7333", roughness = 50, metalness = 90)
/// 
/// // Sketch 4 circles.
/// sketch006 = startSketchOn(blueCube, face = END)
/// profile008 = circle(
///   sketch006,
///   center = [
///     mountCenterToCenter / 2,
///     mountCenterToCenter / 2
///   ],
///   diameter = 0.375,
/// )
/// profile009 = circle(
///   sketch006,
///   center = [
///     -mountCenterToCenter / 2,
///     mountCenterToCenter / 2
///   ],
///   diameter = 0.375,
/// )
/// profile010 = circle(
///   sketch006,
///   center = [
///     -mountCenterToCenter / 2,
///     -mountCenterToCenter / 2
///   ],
///   diameter = 0.375,
/// )
/// profile011 = circle(
///   sketch006,
///   center = [
///     mountCenterToCenter / 2,
///     -mountCenterToCenter / 2
///   ],
///   diameter = 0.375,
/// )
///
/// // Extrude the 4 circles into 4 rods.
/// rods = extrude(
///   [
///     profile008,
///     profile009,
///     profile010,
///     profile011
///   ],
///   length = -6,
///   method = NEW,
/// )
/// |> appearance(color = "#ff2222", roughness = 50, metalness = 90)
/// 
/// // Subtract all 4 rods from both cubes.
/// subtract([blueCube, brownCube], tools = rods)
/// ```
@(impl = std_rust, feature_tree = true)
export fn subtract(
  /// The solids to use as the base to subtract from.
  @solids: [Solid; 1+],
  /// The solids to subtract.
  tools: [Solid],
  /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters.
  tolerance?: number(Length),
): [Solid; 1+] {}

/// Set the appearance of a solid. This only works on solids, not sketches or individual paths.
///
/// This will work on any solid, including extruded solids, revolved solids, and shelled solids.
///
/// ```kcl
/// // Add color to an extruded solid.
/// exampleSketch = startSketchOn(XZ)
///   |> startProfile(at = [0, 0])
///   |> line(endAbsolute = [10, 0])
///   |> line(endAbsolute = [0, 10])
///   |> line(endAbsolute = [-10, 0])
///   |> close()
///
/// example = extrude(exampleSketch, length = 5)
///  // There are other options besides 'color', but they're optional.
///  |> appearance(color='#ff0000')
/// ```
///
/// ```kcl
/// // Add color to a revolved solid.
/// sketch001 = startSketchOn(XY)
///     |> circle( center = [15, 0], radius = 5 )
///     |> revolve( angle = 360deg, axis = Y)
///     |> appearance(
///         color = '#ff0000',
///         metalness = 90,
///         roughness = 90
///     )
/// ```
///
/// ```kcl
/// // Add color to different solids.
/// fn cube(center) {
///    return startSketchOn(XY)
///    |> startProfile(at = [center[0] - 10, center[1] - 10])
///    |> line(endAbsolute = [center[0] + 10, center[1] - 10])
///     |> line(endAbsolute = [center[0] + 10, center[1] + 10])
///     |> line(endAbsolute = [center[0] - 10, center[1] + 10])
///     |> close()
///    |> extrude(length = 10)
/// }
///
/// example0 = cube(center = [0, 0])
/// example1 = cube(center = [20, 0])
/// example2 = cube(center = [40, 0])
///
///  appearance([example0, example1], color='#ff0000', metalness=50, roughness=50)
///  appearance(example2, color='#00ff00', metalness=50, roughness=50)
/// ```
///
/// ```kcl
/// // You can set the appearance before or after you shell it will yield the same result.
/// // This example shows setting the appearance _after_ the shell.
/// firstSketch = startSketchOn(XY)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0])
///     |> close()
///     |> extrude(length = 6)
///
/// shell(
///     firstSketch,
///     faces = [END],
///     thickness = 0.25,
/// )
///     |> appearance(
///         color = '#ff0000',
///         metalness = 90,
///         roughness = 90
///     )
/// ```
///
/// ```kcl
/// // You can set the appearance before or after you shell it will yield the same result.
/// // This example shows setting the appearance _before_ the shell.
/// firstSketch = startSketchOn(XY)
///     |> startProfile(at = [-12, 12])
///     |> line(end = [24, 0])
///     |> line(end = [0, -24])
///     |> line(end = [-24, 0])
///     |> close()
///     |> extrude(length = 6)
///     |> appearance(
///         color = '#ff0000',
///         metalness = 90,
///         roughness = 90
///     )
///
/// shell(
///     firstSketch,
///     faces = [END],
///     thickness = 0.25,
/// )
/// ```
///
/// ```kcl
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
/// // This example shows _before_ the pattern.
/// exampleSketch = startSketchOn(XZ)
///   |> startProfile(at = [0, 0])
///   |> line(end = [0, 2])
///   |> line(end = [3, 1])
///   |> line(end = [0, -4])
///   |> close()
///
/// example = extrude(exampleSketch, length = 1)
///     |> appearance(
///         color = '#ff0000',
///         metalness = 90,
///         roughness = 90
///        )
///     |> patternLinear3d(
///         axis = [1, 0, 1],
///         instances = 7,
///         distance = 6
///        )
/// ```
///
/// ```kcl
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
/// // This example shows _after_ the pattern.
/// exampleSketch = startSketchOn(XZ)
///   |> startProfile(at = [0, 0])
///   |> line(end = [0, 2])
///   |> line(end = [3, 1])
///   |> line(end = [0, -4])
///   |> close()
///
/// example = extrude(exampleSketch, length = 1)
///   |> patternLinear3d(
///       axis = [1, 0, 1],
///       instances = 7,
///       distance = 6
///      )
///   |> appearance(
///       color = '#ff0000',
///       metalness = 90,
///       roughness = 90
///      )
/// ```
///
/// ```kcl
/// // Color the result of a 2D pattern that was extruded.
/// exampleSketch = startSketchOn(XZ)
///   |> startProfile(at = [.5, 25])
///   |> line(end = [0, 5])
///   |> line(end = [-1, 0])
///   |> line(end = [0, -5])
///   |> close()
///   |> patternCircular2d(
///        center = [0, 0],
///        instances = 13,
///        arcDegrees = 360,
///        rotateDuplicates = true
///      )
///
/// example = extrude(exampleSketch, length = 1)
///     |> appearance(
///         color = '#ff0000',
///         metalness = 90,
///         roughness = 90
///     )
/// ```
///
/// ```kcl
/// // Color the result of a sweep.
///
/// // Create a path for the sweep.
/// sweepPath = startSketchOn(XZ)
///     |> startProfile(at = [0.05, 0.05])
///     |> line(end = [0, 7])
///     |> tangentialArc(angle = 90deg, radius = 5)
///     |> line(end = [-3, 0])
///     |> tangentialArc(angle = -90deg, radius = 5)
///     |> line(end = [0, 7])
///
/// pipeHole = startSketchOn(XY)
///     |> circle(
///         center = [0, 0],
///         radius = 1.5,
///     )
///
/// sweepSketch = startSketchOn(XY)
///     |> circle(
///         center = [0, 0],
///         radius = 2,
///         )              
///     |> subtract2d(tool = pipeHole)
///     |> sweep(path = sweepPath)
///     |> appearance(
///         color = "#ff0000",
///         metalness = 50,
///         roughness = 50
///     )
/// ```
///
/// ```kcl
/// // Change the appearance of an imported model.
///
/// import "tests/inputs/cube.sldprt" as cube
///
/// cube
///    |> appearance(
///        color = "#ff0000",
///        metalness = 50,
///        roughness = 50
///    )
/// ```
@(impl = std_rust, feature_tree = true)
export fn appearance(
  /// The The solid(s) whose appearance is being set.
  @solids: [Solid; 1+] | ImportedGeometry,
  /// Color of the new material, a hex string like '#ff0000'.
  color: string,
  /// Metalness of the new material, a percentage like 95.7.
  metalness?: number(Count),
  /// Roughness of the new material, a percentage like 95.7.
  roughness?: number(Count),
): [Solid; 1+] | ImportedGeometry {}
