Skip to content

Commit

Permalink
KCL: Update patternTransform and 2d to use kwargs
Browse files Browse the repository at this point in the history
  • Loading branch information
adamchalmers committed Feb 11, 2025
1 parent c68e5d7 commit 53ba23a
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 45 deletions.
2 changes: 1 addition & 1 deletion src/wasm-lib/kcl/src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ fn layer = () => {
const x = 5
// The 10 layers are replicas of each other, with a transform applied to each.
let shape = layer() |> patternTransform(10, transform, %)
let shape = layer() |> patternTransform(instances = 10, transform = transform)
"#;

let result = parse_execute(ast).await;
Expand Down
4 changes: 0 additions & 4 deletions src/wasm-lib/kcl/src/std/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,10 +358,6 @@ impl Args {
Ok(numbers)
}

pub(crate) fn get_pattern_transform_args(&self) -> Result<(u32, FnAsArg<'_>, SolidSet, Option<bool>), KclError> {
FromArgs::from_args(self, 0)
}

pub(crate) fn get_hypotenuse_leg(&self) -> Result<(f64, f64), KclError> {
let numbers = self.get_number_array()?;

Expand Down
4 changes: 2 additions & 2 deletions src/wasm-lib/kcl/src/std/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ pub async fn int(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
/// startSketchOn('XZ')
/// |> circle({ center = [0, 0], radius = 2 }, %)
/// |> extrude(length = 5)
/// |> patternTransform(n, fn(id) {
/// |> patternTransform(instances = n, transform = fn(id) {
/// return { translate = [4 * id, 0, 0] }
/// }, %)
/// })
/// ```
#[stdlib {
name = "int",
Expand Down
105 changes: 70 additions & 35 deletions src/wasm-lib/kcl/src/std/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::args::Arg;
use super::{args::Arg, FnAsArg};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
Expand Down Expand Up @@ -47,18 +47,21 @@ pub struct LinearPattern3dData {

/// Repeat some 3D solid, changing each repetition slightly.
pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (num_repetitions, transform, extr, use_original) = args.get_pattern_transform_args()?;
let solid_set = args.get_unlabeled_kw_arg("solidSet")?;
let instances: u32 = args.get_kw_arg("instances")?;
let transform: FnAsArg<'_> = args.get_kw_arg("transform")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;

let solids = inner_pattern_transform(
num_repetitions,
solid_set,
instances,
FunctionParam {
inner: transform.func,
fn_expr: transform.expr,
meta: vec![args.source_range.into()],
ctx: args.ctx.clone(),
memory: transform.memory,
},
extr,
use_original,
exec_state,
&args,
Expand All @@ -69,19 +72,21 @@ pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result

/// Repeat some 2D sketch, changing each repetition slightly.
pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (num_repetitions, transform, sketch, use_original): (u32, super::FnAsArg<'_>, SketchSet, Option<bool>) =
super::args::FromArgs::from_args(&args, 0)?;
let sketch_set = args.get_unlabeled_kw_arg("sketchSet")?;
let instances: u32 = args.get_kw_arg("instances")?;
let transform: FnAsArg<'_> = args.get_kw_arg("transform")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;

let sketches = inner_pattern_transform_2d(
num_repetitions,
sketch_set,
instances,
FunctionParam {
inner: transform.func,
fn_expr: transform.expr,
meta: vec![args.source_range.into()],
ctx: args.ctx.clone(),
memory: transform.memory,
},
sketch,
use_original,
exec_state,
&args,
Expand All @@ -96,7 +101,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// 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(4, transform)`
/// 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.
///
Expand Down Expand Up @@ -140,7 +145,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// sketch001 = startSketchOn('XZ')
/// |> circle({ center = [0, 0], radius = 2 }, %)
/// |> extrude(length = 5)
/// |> patternTransform(4, transform, %)
/// |> patternTransform(instances = 4, transform = transform)
/// ```
/// ```no_run
/// // Each instance will be shifted along the X axis,
Expand All @@ -153,7 +158,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// sketch001 = startSketchOn('XZ')
/// |> circle({ center = [0, 0], radius = 2 }, %)
/// |> extrude(length = 5)
/// |> patternTransform(4, transform, %)
/// |> patternTransform(instances = 4, transform = transform)
/// ```
/// ```no_run
/// fn cube(length, center) {
Expand Down Expand Up @@ -192,7 +197,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
///
/// myCubes =
/// cube(width, [100,0])
/// |> patternTransform(25, transform, %)
/// |> patternTransform(instances = 25, transform = transform)
/// ```
///
/// ```no_run
Expand Down Expand Up @@ -228,7 +233,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// }
/// myCubes =
/// cube(width, [100,100])
/// |> patternTransform(4, transform, %)
/// |> patternTransform(instances = 4, transform = transform)
/// ```
/// ```no_run
/// // Parameters
Expand All @@ -252,7 +257,7 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// }
/// // The vase is 100 layers tall.
/// // The 100 layers are replica of each other, with a slight transformation applied to each.
/// vase = layer() |> patternTransform(100, transform, %)
/// vase = layer() |> patternTransform(instances = 100, transform = transform)
/// ```
/// ```
/// fn transform(i) {
Expand All @@ -271,33 +276,48 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// inscribed: false
/// }, %)
/// |> extrude(length = 4)
/// |> patternTransform(3, transform, %)
/// |> patternTransform(instances = 3, transform = transform)
/// ```
#[stdlib {
name = "patternTransform",
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
solid_set = { docs = "The solid(s) to duplicate" },
instances = { docs = "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." },
transform = { docs = "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." },
use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false." },
}
}]
async fn inner_pattern_transform<'a>(
total_instances: u32,
transform_function: FunctionParam<'a>,
solid_set: SolidSet,
instances: u32,
transform: FunctionParam<'a>,
use_original: Option<bool>,
exec_state: &mut ExecState,
args: &'a Args,
) -> Result<Vec<Box<Solid>>, KclError> {
// Build the vec of transforms, one for each repetition.
let mut transform = Vec::with_capacity(usize::try_from(total_instances).unwrap());
if total_instances < 1 {
let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap());
if instances < 1 {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: MUST_HAVE_ONE_INSTANCE.to_owned(),
}));
}
for i in 1..total_instances {
let t = make_transform::<Box<Solid>>(i, &transform_function, args.source_range, exec_state).await?;
transform.push(t);
for i in 1..instances {
let t = make_transform::<Box<Solid>>(i, &transform, args.source_range, exec_state).await?;
transform_vec.push(t);
}
execute_pattern_transform(transform, solid_set, use_original.unwrap_or_default(), exec_state, args).await
execute_pattern_transform(
transform_vec,
solid_set,
use_original.unwrap_or_default(),
exec_state,
args,
)
.await
}

/// Just like patternTransform, but works on 2D sketches not 3D solids.
Expand All @@ -310,32 +330,47 @@ async fn inner_pattern_transform<'a>(
/// // Sketch 4 circles.
/// sketch001 = startSketchOn('XZ')
/// |> circle({ center: [0, 0], radius: 2 }, %)
/// |> patternTransform2d(4, transform, %)
/// |> patternTransform2d(instances = 4, transform = transform)
/// ```
#[stdlib {
name = "patternTransform2d",
keywords = true,
unlabeled_first = true,
args = {
sketch_set = { docs = "The sketch(es) to duplicate" },
instances = { docs = "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." },
transform = { docs = "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." },
use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false." },
}
}]
async fn inner_pattern_transform_2d<'a>(
total_instances: u32,
transform_function: FunctionParam<'a>,
solid_set: SketchSet,
sketch_set: SketchSet,
instances: u32,
transform: FunctionParam<'a>,
use_original: Option<bool>,
exec_state: &mut ExecState,
args: &'a Args,
) -> Result<Vec<Box<Sketch>>, KclError> {
// Build the vec of transforms, one for each repetition.
let mut transform = Vec::with_capacity(usize::try_from(total_instances).unwrap());
if total_instances < 1 {
let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap());
if instances < 1 {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: MUST_HAVE_ONE_INSTANCE.to_owned(),
}));
}
for i in 1..total_instances {
let t = make_transform::<Box<Sketch>>(i, &transform_function, args.source_range, exec_state).await?;
transform.push(t);
for i in 1..instances {
let t = make_transform::<Box<Sketch>>(i, &transform, args.source_range, exec_state).await?;
transform_vec.push(t);
}
execute_pattern_transform(transform, solid_set, use_original.unwrap_or_default(), exec_state, args).await
execute_pattern_transform(
transform_vec,
sketch_set,
use_original.unwrap_or_default(),
exec_state,
args,
)
.await
}

async fn execute_pattern_transform<T: GeometryTrait>(
Expand Down Expand Up @@ -406,7 +441,7 @@ async fn send_pattern_transform<T: GeometryTrait>(

async fn make_transform<T: GeometryTrait>(
i: u32,
transform_function: &FunctionParam<'_>,
transform: &FunctionParam<'_>,
source_range: SourceRange,
exec_state: &mut ExecState,
) -> Result<Vec<Transform>, KclError> {
Expand All @@ -416,7 +451,7 @@ async fn make_transform<T: GeometryTrait>(
meta: vec![source_range.into()],
};
let transform_fn_args = vec![Arg::synthetic(repetition_num)];
let transform_fn_return = transform_function.call(exec_state, transform_fn_args).await?;
let transform_fn_return = transform.call(exec_state, transform_fn_args).await?;

// Unpack the returned transform object.
let source_ranges = vec![source_range];
Expand Down
2 changes: 1 addition & 1 deletion src/wasm-lib/kcl/tests/multi_transform/input.kcl
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ startSketchAt([0, 0])
inscribed: false
}, %)
|> extrude(length = 4)
|> patternTransform(3, transform, %)
|> patternTransform(instances = 3, transform = transform)

2 changes: 1 addition & 1 deletion src/wasm-lib/tests/executor/inputs/pattern_vase.kcl
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ fn layer = () => {
}
// The vase is 100 layers tall.
// The 100 layers are replica of each other, with a slight transformation applied to each.
let vase = layer() |> patternTransform(100, transform, %)
let vase = layer() |> patternTransform(instances = 100, transform = transform)
2 changes: 1 addition & 1 deletion src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,5 @@ const peg = startSketchOn(s, 'end')
distance = pitch
)
|> extrude(bumpHeight, %)
// |> patternTransform(int(totalBumps-1), tr, %)
// |> patternTransform(instances = int(totalBumps-1), transform = tr)

0 comments on commit 53ba23a

Please sign in to comment.