WIP: Move patterns to kwargs

This commit is contained in:
Adam Chalmers
2025-02-07 14:07:20 -06:00
parent 9c18060d73
commit d29d2d4a11
2 changed files with 148 additions and 114 deletions

View File

@ -943,72 +943,6 @@ impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
} }
} }
impl<'a> FromKclValue<'a> for super::patterns::CircularPattern3dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, instances);
let_field_of!(obj, arc_degrees "arcDegrees");
let_field_of!(obj, rotate_duplicates "rotateDuplicates");
let_field_of!(obj, axis);
let_field_of!(obj, center);
let_field_of!(obj, use_original? "useOriginal");
Some(Self {
instances,
axis,
center,
arc_degrees,
rotate_duplicates,
use_original,
})
}
}
impl<'a> FromKclValue<'a> for super::patterns::CircularPattern2dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, instances);
let_field_of!(obj, arc_degrees "arcDegrees");
let_field_of!(obj, rotate_duplicates "rotateDuplicates");
let_field_of!(obj, center);
let_field_of!(obj, use_original? "useOriginal");
Some(Self {
instances,
center,
arc_degrees,
rotate_duplicates,
use_original,
})
}
}
impl<'a> FromKclValue<'a> for super::patterns::LinearPattern3dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, distance);
let_field_of!(obj, instances);
let_field_of!(obj, axis);
Some(Self {
instances,
distance,
axis,
})
}
}
impl<'a> FromKclValue<'a> for super::patterns::LinearPattern2dData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, distance);
let_field_of!(obj, instances);
let_field_of!(obj, axis);
Some(Self {
instances,
distance,
axis,
})
}
}
impl<'a> FromKclValue<'a> for super::sketch::BezierData { impl<'a> FromKclValue<'a> for super::sketch::BezierData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?; let obj = arg.as_object()?;

View File

@ -29,22 +29,6 @@ use crate::{
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry"; const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
/// Data for a linear pattern on a 2D sketch.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct LinearPattern2dData {
/// 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.
pub instances: u32,
/// The distance between each repetition. This can also be referred to as spacing.
pub distance: f64,
/// The axis of the pattern. This is a 2D vector.
pub axis: [f64; 2],
}
/// Data for a linear pattern on a 3D model. /// Data for a linear pattern on a 3D model.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
@ -689,10 +673,13 @@ mod tests {
/// A linear pattern on a 2D sketch. /// A linear pattern on a 2D sketch.
pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch_set, use_original): (LinearPattern2dData, SketchSet, Option<bool>) = let sketch_set: SketchSet = args.get_unlabeled_kw_arg("sketchSet")?;
super::args::FromArgs::from_args(&args, 0)?; let instances: u32 = args.get_kw_arg("instances")?;
let distance: f64 = args.get_kw_arg("distance")?;
let axis: [f64; 2] = args.get_kw_arg("axis")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
if data.axis == [0.0, 0.0] { if axis == [0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -701,7 +688,8 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
})); }));
} }
let sketches = inner_pattern_linear_2d(data, sketch_set, use_original, exec_state, args).await?; let sketches =
inner_pattern_linear_2d(sketch_set, instances, distance, axis, use_original, exec_state, args).await?;
Ok(sketches.into()) Ok(sketches.into())
} }
@ -711,31 +699,42 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
/// ```no_run /// ```no_run
/// exampleSketch = startSketchOn('XZ') /// exampleSketch = startSketchOn('XZ')
/// |> circle({ center = [0, 0], radius = 1 }, %) /// |> circle({ center = [0, 0], radius = 1 }, %)
/// |> patternLinear2d({ /// |> patternLinear2d(
/// axis = [1, 0], /// axis = [1, 0],
/// instances = 7, /// instances = 7,
/// distance = 4 /// distance = 4
/// }, %) /// )
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternLinear2d", name = "patternLinear2d",
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." },
distance = { docs = "Distance between each repetition. Also known as 'spacing'."},
axis = { docs = "The axis of the pattern. A 2D vector." },
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_linear_2d( async fn inner_pattern_linear_2d(
data: LinearPattern2dData,
sketch_set: SketchSet, sketch_set: SketchSet,
instances: u32,
distance: f64,
axis: [f64; 2],
use_original: Option<bool>, use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Sketch>>, KclError> { ) -> Result<Vec<Box<Sketch>>, KclError> {
let axis = data.axis; let axis = axis;
let [x, y] = axis; let [x, y] = axis;
let axis_len = f64::sqrt(x * x + y * y); let axis_len = f64::sqrt(x * x + y * y);
let normalized_axis = kcmc::shared::Point2d::from([x / axis_len, y / axis_len]); let normalized_axis = kcmc::shared::Point2d::from([x / axis_len, y / axis_len]);
let transforms: Vec<_> = (1..data.instances) let transforms: Vec<_> = (1..instances)
.map(|i| { .map(|i| {
let d = data.distance * (i as f64); let d = distance * (i as f64);
let translate = (normalized_axis * d).with_z(0.0).map(LengthUnit); let translate = (normalized_axis * d).with_z(0.0).map(LengthUnit);
vec![Transform { vec![Transform {
translate, translate,
@ -755,10 +754,13 @@ async fn inner_pattern_linear_2d(
/// A linear pattern on a 3D model. /// A linear pattern on a 3D model.
pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, solid_set, use_original): (LinearPattern3dData, SolidSet, Option<bool>) = let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?;
super::args::FromArgs::from_args(&args, 0)?; let instances: u32 = args.get_kw_arg("instances")?;
let distance: f64 = args.get_kw_arg("distance")?;
let axis: [f64; 3] = args.get_kw_arg("axis")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
if data.axis == [0.0, 0.0, 0.0] { if axis == [0.0, 0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place." "The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -767,7 +769,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
})); }));
} }
let solids = inner_pattern_linear_3d(data, solid_set, use_original, exec_state, args).await?; let solids = inner_pattern_linear_3d(solid_set, instances, distance, axis, use_original, exec_state, args).await?;
Ok(solids.into()) Ok(solids.into())
} }
@ -783,30 +785,41 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
/// |> close() /// |> close()
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// |> patternLinear3d({ /// |> patternLinear3d(
/// axis = [1, 0, 1], /// axis = [1, 0, 1],
/// instances = 7, /// instances = 7,
/// distance = 6 /// distance = 6
/// }, %) /// )
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternLinear3d", name = "patternLinear3d",
feature_tree_operation = true, 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." },
distance = { docs = "Distance between each repetition. Also known as 'spacing'."},
axis = { docs = "The axis of the pattern. A 2D vector." },
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_linear_3d( async fn inner_pattern_linear_3d(
data: LinearPattern3dData,
solid_set: SolidSet, solid_set: SolidSet,
instances: u32,
distance: f64,
axis: [f64; 3],
use_original: Option<bool>, use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Solid>>, KclError> { ) -> Result<Vec<Box<Solid>>, KclError> {
let axis = data.axis; let axis = axis;
let [x, y, z] = axis; let [x, y, z] = axis;
let axis_len = f64::sqrt(x * x + y * y + z * z); let axis_len = f64::sqrt(x * x + y * y + z * z);
let normalized_axis = kcmc::shared::Point3d::from([x / axis_len, y / axis_len, z / axis_len]); let normalized_axis = kcmc::shared::Point3d::from([x / axis_len, y / axis_len, z / axis_len]);
let transforms: Vec<_> = (1..data.instances) let transforms: Vec<_> = (1..instances)
.map(|i| { .map(|i| {
let d = data.distance * (i as f64); let d = distance * (i as f64);
let translate = (normalized_axis * d).map(LengthUnit); let translate = (normalized_axis * d).map(LengthUnit);
vec![Transform { vec![Transform {
translate, translate,
@ -828,7 +841,7 @@ async fn inner_pattern_linear_3d(
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CircularPattern2dData { struct CircularPattern2dData {
/// The number of total instances. Must be greater than or equal to 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, /// This includes the original entity. For example, if instances is 2,
/// there will be two copies -- the original, and one new copy. /// there will be two copies -- the original, and one new copy.
@ -870,7 +883,7 @@ pub struct CircularPattern3dData {
pub use_original: Option<bool>, pub use_original: Option<bool>,
} }
pub enum CircularPattern { enum CircularPattern {
ThreeD(CircularPattern3dData), ThreeD(CircularPattern3dData),
TwoD(CircularPattern2dData), TwoD(CircularPattern2dData),
} }
@ -941,9 +954,24 @@ impl CircularPattern {
/// A circular pattern on a 2D sketch. /// A circular pattern on a 2D sketch.
pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch_set): (CircularPattern2dData, SketchSet) = args.get_data_and_sketch_set()?; let sketch_set: SketchSet = args.get_unlabeled_kw_arg("sketchSet")?;
let instances: u32 = args.get_kw_arg("instances")?;
let center: [f64; 2] = args.get_kw_arg("center")?;
let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?;
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let sketches = inner_pattern_circular_2d(data, sketch_set, exec_state, args).await?; let sketches = inner_pattern_circular_2d(
sketch_set,
instances,
center,
arc_degrees,
rotate_duplicates,
use_original,
exec_state,
args,
)
.await?;
Ok(sketches.into()) Ok(sketches.into())
} }
@ -959,21 +987,35 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
/// |> line(end = [-1, 0]) /// |> line(end = [-1, 0])
/// |> line(end = [0, -5]) /// |> line(end = [0, -5])
/// |> close() /// |> close()
/// |> patternCircular2d({ /// |> patternCircular2d(
/// center = [0, 0], /// center = [0, 0],
/// instances = 13, /// instances = 13,
/// arcDegrees = 360, /// arcDegrees = 360,
/// rotateDuplicates = true /// rotateDuplicates = true
/// }, %) /// )
/// ///
/// example = extrude(exampleSketch, length = 1) /// example = extrude(exampleSketch, length = 1)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternCircular2d", name = "patternCircular2d",
keywords = true,
unlabeled_first = true,
args = {
sketch_set = { docs = "Which sketch(es) to pattern" },
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."},
center = { docs = "The center about which to make the pattern. This is a 2D vector."},
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0."},
rotate_duplicates= { docs = "Whether or not to rotate the duplicates as they are copied."},
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_circular_2d( async fn inner_pattern_circular_2d(
data: CircularPattern2dData,
sketch_set: SketchSet, sketch_set: SketchSet,
instances: u32,
center: [f64; 2],
arc_degrees: f64,
rotate_duplicates: bool,
use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Sketch>>, KclError> { ) -> Result<Vec<Box<Sketch>>, KclError> {
@ -982,6 +1024,13 @@ async fn inner_pattern_circular_2d(
if args.ctx.context_type == crate::execution::ContextType::Mock { if args.ctx.context_type == crate::execution::ContextType::Mock {
return Ok(starting_sketches); return Ok(starting_sketches);
} }
let data = CircularPattern2dData {
instances,
center,
arc_degrees,
rotate_duplicates,
use_original,
};
let mut sketches = Vec::new(); let mut sketches = Vec::new();
for sketch in starting_sketches.iter() { for sketch in starting_sketches.iter() {
@ -1008,9 +1057,36 @@ async fn inner_pattern_circular_2d(
/// A circular pattern on a 3D model. /// A circular pattern on a 3D model.
pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, solid_set): (CircularPattern3dData, SolidSet) = args.get_data_and_solid_set()?; let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?;
// 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.
let instances: u32 = args.get_kw_arg("instances")?;
// The axis around which to make the pattern. This is a 3D vector.
let axis: [f64; 3] = args.get_kw_arg("axis")?;
// The center about which to make the pattern. This is a 3D vector.
let center: [f64; 3] = args.get_kw_arg("center")?;
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?;
// Whether or not to rotate the duplicates as they are copied.
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
// If the target being patterned is itself a pattern, then, should you use the original solid,
// or the pattern?
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let solids = inner_pattern_circular_3d(data, solid_set, exec_state, args).await?; let solids = inner_pattern_circular_3d(
solid_set,
instances,
axis,
center,
arc_degrees,
rotate_duplicates,
use_original,
exec_state,
args,
)
.await?;
Ok(solids.into()) Ok(solids.into())
} }
@ -1024,21 +1100,37 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
/// |> circle({ center = [0, 0], radius = 1 }, %) /// |> circle({ center = [0, 0], radius = 1 }, %)
/// ///
/// example = extrude(exampleSketch, length = -5) /// example = extrude(exampleSketch, length = -5)
/// |> patternCircular3d({ /// |> patternCircular3d(
/// axis = [1, -1, 0], /// axis = [1, -1, 0],
/// center = [10, -20, 0], /// center = [10, -20, 0],
/// instances = 11, /// instances = 11,
/// arcDegrees = 360, /// arcDegrees = 360,
/// rotateDuplicates = true /// rotateDuplicates = true
/// }, %) /// )
/// ``` /// ```
#[stdlib { #[stdlib {
name = "patternCircular3d", name = "patternCircular3d",
feature_tree_operation = true, feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
solid_set = { docs = "Which solid(s) to pattern" },
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."},
axis = { docs = "The axis around which to make the pattern. This is a 3D vector"},
center = { docs = "The center about which to make the pattern. This is a 3D vector."},
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0."},
rotate_duplicates= { docs = "Whether or not to rotate the duplicates as they are copied."},
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_circular_3d( async fn inner_pattern_circular_3d(
data: CircularPattern3dData,
solid_set: SolidSet, solid_set: SolidSet,
instances: u32,
axis: [f64; 3],
center: [f64; 3],
arc_degrees: f64,
rotate_duplicates: bool,
use_original: Option<bool>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Vec<Box<Solid>>, KclError> { ) -> Result<Vec<Box<Solid>>, KclError> {
@ -1055,6 +1147,14 @@ async fn inner_pattern_circular_3d(
} }
let mut solids = Vec::new(); let mut solids = Vec::new();
let data = CircularPattern3dData {
instances,
axis,
center,
arc_degrees,
rotate_duplicates,
use_original,
};
for solid in starting_solids.iter() { for solid in starting_solids.iter() {
let geometries = pattern_circular( let geometries = pattern_circular(
CircularPattern::ThreeD(data.clone()), CircularPattern::ThreeD(data.clone()),