Move some more functions to be declared in KCL (#6856)

* Move the leg functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move array functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move clone to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add a function type

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-05-13 08:29:38 +12:00
committed by GitHub
parent 818d9a0d77
commit a049768f1c
61 changed files with 948 additions and 35813 deletions

View File

@ -2,3 +2,169 @@
@no_std
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
/// Apply a function to every element of a list.
///
/// Given a list like `[a, b, c]`, and a function like `f`, returns
/// `[f(a), f(b), f(c)]`
///
/// ```kcl
/// r = 10 // radius
/// fn drawCircle(@id) {
/// return startSketchOn(XY)
/// |> circle( center= [id * 2 * r, 0], radius= r)
/// }
///
/// // Call `drawCircle`, passing in each element of the array.
/// // The outputs from each `drawCircle` form a new array,
/// // which is the return value from `map`.
/// circles = map(
/// [1..3],
/// f = drawCircle
/// )
/// ```
///
/// ```kcl
/// r = 10 // radius
/// // Call `map`, using an anonymous function instead of a named one.
/// circles = map(
/// [1..3],
/// f = fn(@id) {
/// return startSketchOn(XY)
/// |> circle( center= [id * 2 * r, 0], radius= r)
/// }
/// )
/// ```
@(impl = std_rust)
export fn map(
/// Input array. The output array is this input array, but every element has had the function `f` run on it.
@array: [any],
/// A function. The output array is just the input array, but `f` has been run on every item.
f: Fn,
): [any] {}
/// Take a starting value. Then, for each element of an array, calculate the next value,
/// using the previous value and the element.
///
/// ```kcl
/// // This function adds two numbers.
/// fn add(@a, accum) { return a + accum }
///
/// // This function adds an array of numbers.
/// // It uses the `reduce` function, to call the `add` function on every
/// // element of the `arr` parameter. The starting value is 0.
/// fn sum(@arr) { return reduce(arr, initial = 0, f = add) }
///
/// /*
/// The above is basically like this pseudo-code:
/// fn sum(arr):
/// sumSoFar = 0
/// for i in arr:
/// sumSoFar = add(i, sumSoFar)
/// return sumSoFar
/// */
///
/// // We use `assert` to check that our `sum` function gives the
/// // expected result. It's good to check your work!
/// assert(sum([1, 2, 3]), isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
/// ```
///
/// ```kcl
/// // This example works just like the previous example above, but it uses
/// // an anonymous `add` function as its parameter, instead of declaring a
/// // named function outside.
/// arr = [1, 2, 3]
/// sum = reduce(arr, initial = 0, f = fn (@i, accum) { return i + accum })
///
/// // We use `assert` to check that our `sum` function gives the
/// // expected result. It's good to check your work!
/// assert(sum, isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
/// ```
///
/// ```kcl
/// // Declare a function that sketches a decagon.
/// fn decagon(@radius) {
/// // Each side of the decagon is turned this many radians from the previous angle.
/// stepAngle = ((1/10) * TAU): number(rad)
///
/// // Start the decagon sketch at this point.
/// startOfDecagonSketch = startSketchOn(XY)
/// |> startProfile(at = [(cos(0)*radius), (sin(0) * radius)])
///
/// // Use a `reduce` to draw the remaining decagon sides.
/// // For each number in the array 1..10, run the given function,
/// // which takes a partially-sketched decagon and adds one more edge to it.
/// fullDecagon = reduce([1..10], initial = startOfDecagonSketch, f = fn(@i, accum) {
/// // Draw one edge of the decagon.
/// x = cos(stepAngle * i) * radius
/// y = sin(stepAngle * i) * radius
/// return line(accum, end = [x, y])
/// })
///
/// return fullDecagon
///
/// }
///
/// /*
/// The `decagon` above is basically like this pseudo-code:
/// fn decagon(radius):
/// stepAngle = ((1/10) * TAU): number(rad)
/// plane = startSketchOn(XY)
/// startOfDecagonSketch = startProfile(plane, at = [(cos(0)*radius), (sin(0) * radius)])
///
/// // Here's the reduce part.
/// partialDecagon = startOfDecagonSketch
/// for i in [1..10]:
/// x = cos(stepAngle * i) * radius
/// y = sin(stepAngle * i) * radius
/// partialDecagon = line(partialDecagon, end = [x, y])
/// fullDecagon = partialDecagon // it's now full
/// return fullDecagon
/// */
///
/// // Use the `decagon` function declared above, to sketch a decagon with radius 5.
/// decagon(5.0) |> close()
/// ```
@(impl = std_rust)
export fn reduce(
/// Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run.
@array: [any],
/// The first time `f` is run, it will be called with the first item of `array` and this initial starting value.
initial: any,
/// Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`.
f: Fn,
): [any] {}
/// Append an element to the end of an array.
///
/// Returns a new array with the element appended.
///
/// ```kcl
/// arr = [1, 2, 3]
/// new_arr = push(arr, item = 4)
/// assert(new_arr[3], isEqualTo = 4, tolerance = 0.1, error = "4 was added to the end of the array")
/// ```
@(impl = std_rust)
export fn push(
/// The array which you're adding a new item to.
@array: [any],
/// The new item to add to the array
item: any,
): [any; 1+] {}
/// Remove the last element from an array.
///
/// Returns a new array with the last element removed.
///
/// ```kcl
/// arr = [1, 2, 3, 4]
/// new_arr = pop(arr)
/// assert(new_arr[0], isEqualTo = 1, tolerance = 0.00001, error = "1 is the first element of the array")
/// assert(new_arr[1], isEqualTo = 2, tolerance = 0.00001, error = "2 is the second element of the array")
/// assert(new_arr[2], isEqualTo = 3, tolerance = 0.00001, error = "3 is the third element of the array")
/// ```
@(impl = std_rust)
export fn pop(
/// The array to pop from. Must not be empty.
@array: [any; 1+],
): [any] {}

View File

@ -436,3 +436,42 @@ export fn log10(@input: number): number {}
/// ```
@(impl = std_rust)
export fn ln(@input: number): number {}
/// Compute the length of the given leg.
///
/// ```kcl,no_run
/// legLen(hypotenuse = 5, leg = 3)
/// ```
@(impl = std_rust)
export fn legLen(
/// The length of the triangle's hypotenuse.
hypotenuse: number(Length),
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
leg: number(Length),
): number(deg) {}
/// Compute the angle of the given leg for x.
///
/// ```kcl,no_run
/// legAngX(hypotenuse = 5, leg = 3)
/// ```
@(impl = std_rust)
export fn legAngX(
/// The length of the triangle's hypotenuse.
hypotenuse: number(Length),
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
leg: number(Length),
): number(deg) {}
/// Compute the angle of the given leg for y.
///
/// ```kcl,no_run
/// legAngY(hypotenuse = 5, leg = 3)
/// ```
@(impl = std_rust)
export fn legAngY(
/// The length of the triangle's hypotenuse.
hypotenuse: number(Length),
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
leg: number(Length),
): number(deg) {}

View File

@ -251,3 +251,234 @@ export fn offsetPlane(
/// Distance from the standard plane this new plane will be created at.
offset: number(Length),
): Plane {}
/// Clone a sketch or solid.
///
/// This works essentially like a copy-paste operation. It creates a perfect replica
/// at that point in time that you can manipulate individually afterwards.
///
/// This doesn't really have much utility unless you need the equivalent of a double
/// instance pattern with zero transformations.
///
/// Really only use this function if YOU ARE SURE you need it. In most cases you
/// do not need clone and using a pattern with `instance = 2` is more appropriate.
///
/// ```kcl
/// // Clone a basic sketch and move it and extrude it.
/// exampleSketch = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
/// |> line(end = [-10, 0])
/// |> close()
///
/// clonedSketch = clone(exampleSketch)
/// |> scale(
/// x = 1.0,
/// y = 1.0,
/// z = 2.5,
/// )
/// |> translate(
/// x = 15.0,
/// y = 0,
/// z = 0,
/// )
/// |> extrude(length = 5)
/// ```
///
/// ```kcl
/// // Clone a basic solid and move it.
///
/// exampleSketch = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
/// |> line(end = [-10, 0])
/// |> close()
///
/// myPart = extrude(exampleSketch, length = 5)
/// clonedPart = clone(myPart)
/// |> translate(
/// x = 25.0,
/// )
/// ```
///
/// ```kcl
/// // Translate and rotate a cloned sketch to create a loft.
///
/// sketch001 = startSketchOn(XY)
/// |> startProfile(at = [-10, 10])
/// |> xLine(length = 20)
/// |> yLine(length = -20)
/// |> xLine(length = -20)
/// |> close()
///
/// sketch002 = clone(sketch001)
/// |> translate(x = 0, y = 0, z = 20)
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
///
/// loft([sketch001, sketch002])
/// ```
///
/// ```kcl
/// // Translate a cloned solid. Fillet only the clone.
///
/// sketch001 = startSketchOn(XY)
/// |> startProfile(at = [-10, 10])
/// |> xLine(length = 20)
/// |> yLine(length = -20)
/// |> xLine(length = -20, tag = $filletTag)
/// |> close()
/// |> extrude(length = 5)
///
///
/// sketch002 = clone(sketch001)
/// |> translate(x = 0, y = 0, z = 20)
/// |> fillet(
/// radius = 2,
/// tags = [getNextAdjacentEdge(filletTag)],
/// )
/// ```
///
/// ```kcl
/// // You can reuse the tags from the original geometry with the cloned geometry.
///
/// sketch001 = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> line(end = [10, 0])
/// |> line(end = [0, 10], tag = $sketchingFace)
/// |> line(end = [-10, 0])
/// |> close()
///
/// sketch002 = clone(sketch001)
/// |> translate(x = 10, y = 20, z = 0)
/// |> extrude(length = 5)
///
/// startSketchOn(sketch002, face = sketchingFace)
/// |> startProfile(at = [1, 1])
/// |> line(end = [8, 0])
/// |> line(end = [0, 8])
/// |> line(end = [-8, 0])
/// |> close(tag = $sketchingFace002)
/// |> extrude(length = 10)
/// ```
///
/// ```kcl
/// // You can also use the tags from the original geometry to fillet the cloned geometry.
///
/// 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)
///
/// clonedMountingPlate = clone(mountingPlate)
/// |> fillet(
/// radius = filletRadius,
/// tags = [
/// getNextAdjacentEdge(edge1),
/// getNextAdjacentEdge(edge2),
/// getNextAdjacentEdge(edge3),
/// getNextAdjacentEdge(edge4)
/// ],
/// )
/// |> translate(x = 0, y = 50, z = 0)
/// ```
///
/// ```kcl
/// // Create a spring by sweeping around a helix path from a cloned sketch.
///
/// // Create a helix around the Z axis.
/// helixPath = helix(
/// angleStart = 0,
/// ccw = true,
/// revolutions = 4,
/// length = 10,
/// radius = 5,
/// axis = Z,
/// )
///
///
/// springSketch = startSketchOn(YZ)
/// |> circle( center = [0, 0], radius = 1)
///
/// // Create a spring by sweeping around the helix path.
/// sweepedSpring = clone(springSketch)
/// |> translate(x=100)
/// |> sweep(path = helixPath)
/// ```
///
/// ```kcl
/// // A donut shape from a cloned sketch.
/// sketch001 = startSketchOn(XY)
/// |> circle( center = [15, 0], radius = 5 )
///
/// sketch002 = clone(sketch001)
/// |> translate( z = 30)
/// |> revolve(
/// angle = 360,
/// axis = Y,
/// )
/// ```
///
/// ```kcl
/// // Sketch on the end of a revolved face by tagging the end face.
/// // This shows the cloned geometry will have the same tags as the original geometry.
///
/// exampleSketch = startSketchOn(XY)
/// |> startProfile(at = [4, 12])
/// |> line(end = [2, 0])
/// |> line(end = [0, -6])
/// |> line(end = [4, -6])
/// |> line(end = [0, -6])
/// |> line(end = [-3.75, -4.5])
/// |> line(end = [0, -5.5])
/// |> line(end = [-2, 0])
/// |> close()
///
/// example001 = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01)
///
/// // example002 = clone(example001)
/// // |> translate(x = 0, y = 20, z = 0)
///
/// // Sketch on the cloned face.
/// // exampleSketch002 = startSketchOn(example002, face = end01)
/// // |> startProfile(at = [4.5, -5])
/// // |> line(end = [0, 5])
/// // |> line(end = [5, 0])
/// // |> line(end = [0, -5])
/// // |> close()
///
/// // example003 = extrude(exampleSketch002, length = 5)
/// ```
///
/// ```kcl
/// // Clone an imported model.
///
/// import "tests/inputs/cube.sldprt" as cube
///
/// myCube = cube
///
/// clonedCube = clone(myCube)
/// |> translate(
/// x = 1020,
/// )
/// |> appearance(
/// color = "#ff0000",
/// metalness = 50,
/// roughness = 50
/// )
/// ```
@(impl = std_rust)
export fn clone(
/// The sketch, solid, or imported geometry to be cloned.
@geometry: Sketch | Solid | ImportedGeometry,
): Sketch | Solid | ImportedGeometry {}

View File

@ -163,6 +163,14 @@ export type string
@(impl = primitive)
export type tag
/// Represents geometry which is defined using some other CAD system and imported into KCL.
@(impl = primitive)
export type ImportedGeometry
/// The type of any function in KCL.
@(impl = primitive)
export type Fn
/// An abstract plane.
///
/// A plane has a position and orientation in space defined by its origin and axes. A plane is abstract