BREAKING: More units of measure work and keyword args (#6291)

* More units of measure work

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

* Update CSG output since engine change

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Jonathan Tran
2025-04-14 05:58:19 -04:00
committed by GitHub
parent 7d7b153085
commit 160f55ede5
447 changed files with 60364 additions and 34465 deletions

View File

@ -16,15 +16,18 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::args::Arg;
use super::{
args::Arg,
utils::{untype_point, untype_point_3d},
};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
kcl_value::FunctionSource,
types::{NumericType, RuntimeType},
ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, Sketch, Solid,
ExecState, Geometries, Geometry, KclObjectFields, KclValue, Sketch, Solid,
},
std::Args,
std::{args::TyF64, Args},
ExecutorContext, SourceRange,
};
@ -472,13 +475,14 @@ async fn make_transform<T: GeometryTrait>(
transforms
.into_iter()
.map(|obj| transform_from_obj_fields::<T>(obj, source_ranges.clone()))
.map(|obj| transform_from_obj_fields::<T>(obj, source_ranges.clone(), exec_state))
.collect()
}
fn transform_from_obj_fields<T: GeometryTrait>(
transform: KclObjectFields,
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<Transform, KclError> {
// Apply defaults to the transform.
let replicate = match transform.get("replicate") {
@ -494,13 +498,26 @@ fn transform_from_obj_fields<T: GeometryTrait>(
};
let scale = match transform.get("scale") {
Some(x) => T::array_to_point3d(x, source_ranges.clone())?,
None => Point3d { x: 1.0, y: 1.0, z: 1.0 },
Some(x) => untype_point_3d(T::array_to_point3d(x, source_ranges.clone(), exec_state)?)
.0
.into(),
None => kcmc::shared::Point3d { x: 1.0, y: 1.0, z: 1.0 },
};
let translate = match transform.get("translate") {
Some(x) => T::array_to_point3d(x, source_ranges.clone())?,
None => Point3d { x: 0.0, y: 0.0, z: 0.0 },
Some(x) => {
let (arr, _) = untype_point_3d(T::array_to_point3d(x, source_ranges.clone(), exec_state)?);
kcmc::shared::Point3d::<LengthUnit> {
x: LengthUnit(arr[0]),
y: LengthUnit(arr[1]),
z: LengthUnit(arr[2]),
}
}
None => kcmc::shared::Point3d::<LengthUnit> {
x: LengthUnit(0.0),
y: LengthUnit(0.0),
z: LengthUnit(0.0),
},
};
let mut rotation = Rotation::default();
@ -513,7 +530,9 @@ fn transform_from_obj_fields<T: GeometryTrait>(
}));
};
if let Some(axis) = rot.get("axis") {
rotation.axis = T::array_to_point3d(axis, source_ranges.clone())?.into();
rotation.axis = untype_point_3d(T::array_to_point3d(axis, source_ranges.clone(), exec_state)?)
.0
.into();
}
if let Some(angle) = rot.get("angle") {
match angle {
@ -533,7 +552,9 @@ fn transform_from_obj_fields<T: GeometryTrait>(
KclValue::String { value: s, meta: _ } if s == "local" => OriginType::Local,
KclValue::String { value: s, meta: _ } if s == "global" => OriginType::Global,
other => {
let origin = T::array_to_point3d(other, source_ranges.clone())?.into();
let origin = untype_point_3d(T::array_to_point3d(other, source_ranges.clone(), exec_state)?)
.0
.into();
OriginType::Custom { origin }
}
};
@ -542,73 +563,50 @@ fn transform_from_obj_fields<T: GeometryTrait>(
Ok(Transform {
replicate,
scale: scale.into(),
translate: translate.into(),
scale,
translate,
rotation,
})
}
fn array_to_point3d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError> {
let KclValue::MixedArray { value: arr, meta } = val else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected an array of 3 numbers (i.e. a 3D point)".to_string(),
source_ranges,
}));
};
let len = arr.len();
if len != 3 {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected an array of 3 numbers (i.e. a 3D point) but found {len} items"),
source_ranges,
}));
};
// Gets an f64 from a KCL value.
let f = |k: &KclValue, component: char| {
use super::args::FromKclValue;
if let Some(value) = f64::from_kcl_val(k) {
Ok(value)
} else {
Err(KclError::Semantic(KclErrorDetails {
message: format!("{component} component of this point was not a number"),
source_ranges: meta.iter().map(|m| m.source_range).collect(),
}))
}
};
let x = f(&arr[0], 'x')?;
let y = f(&arr[1], 'y')?;
let z = f(&arr[2], 'z')?;
Ok(Point3d { x, y, z })
fn array_to_point3d(
val: &KclValue,
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<[TyF64; 3], KclError> {
val.coerce(&RuntimeType::point3d(), exec_state)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: format!(
"Expected an array of 3 numbers (i.e., a 3D point), found {}",
e.found
.map(|t| t.human_friendly_type())
.unwrap_or_else(|| val.human_friendly_type().to_owned())
),
source_ranges,
})
})
.map(|val| val.as_point3d().unwrap())
}
fn array_to_point2d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point2d, KclError> {
let KclValue::MixedArray { value: arr, meta } = val else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected an array of 2 numbers (i.e. a 2D point)".to_string(),
source_ranges,
}));
};
let len = arr.len();
if len != 2 {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected an array of 2 numbers (i.e. a 2D point) but found {len} items"),
source_ranges,
}));
};
// Gets an f64 from a KCL value.
let f = |k: &KclValue, component: char| {
use super::args::FromKclValue;
if let Some(value) = f64::from_kcl_val(k) {
Ok(value)
} else {
Err(KclError::Semantic(KclErrorDetails {
message: format!("{component} component of this point was not a number"),
source_ranges: meta.iter().map(|m| m.source_range).collect(),
}))
}
};
let x = f(&arr[0], 'x')?;
let y = f(&arr[1], 'y')?;
Ok(Point2d { x, y })
fn array_to_point2d(
val: &KclValue,
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<[TyF64; 2], KclError> {
val.coerce(&RuntimeType::point2d(), exec_state)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: format!(
"Expected an array of 2 numbers (i.e., a 2D point), found {}",
e.found
.map(|t| t.human_friendly_type())
.unwrap_or_else(|| val.human_friendly_type().to_owned())
),
source_ranges,
})
})
.map(|val| val.as_point2d().unwrap())
}
trait GeometryTrait: Clone {
@ -616,7 +614,11 @@ trait GeometryTrait: Clone {
fn id(&self) -> Uuid;
fn original_id(&self) -> Uuid;
fn set_id(&mut self, id: Uuid);
fn array_to_point3d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError>;
fn array_to_point3d(
val: &KclValue,
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<[TyF64; 3], KclError>;
async fn flush_batch(args: &Args, exec_state: &mut ExecState, set: &Self::Set) -> Result<(), KclError>;
}
@ -631,9 +633,14 @@ impl GeometryTrait for Sketch {
fn original_id(&self) -> Uuid {
self.original_id
}
fn array_to_point3d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError> {
let Point2d { x, y } = array_to_point2d(val, source_ranges)?;
Ok(Point3d { x, y, z: 0.0 })
fn array_to_point3d(
val: &KclValue,
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<[TyF64; 3], KclError> {
let [x, y] = array_to_point2d(val, source_ranges, exec_state)?;
let ty = x.ty.clone();
Ok([x, y, TyF64::new(0.0, ty)])
}
async fn flush_batch(_: &Args, _: &mut ExecState, _: &Self::Set) -> Result<(), KclError> {
@ -655,8 +662,12 @@ impl GeometryTrait for Solid {
self.sketch.original_id
}
fn array_to_point3d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError> {
array_to_point3d(val, source_ranges)
fn array_to_point3d(
val: &KclValue,
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<[TyF64; 3], KclError> {
array_to_point3d(val, source_ranges, exec_state)
}
async fn flush_batch(args: &Args, exec_state: &mut ExecState, solid_set: &Self::Set) -> Result<(), KclError> {
@ -669,30 +680,35 @@ mod tests {
use super::*;
use crate::execution::types::NumericType;
#[test]
fn test_array_to_point3d() {
#[tokio::test(flavor = "multi_thread")]
async fn test_array_to_point3d() {
let mut exec_state = ExecState::new(&ExecutorContext::new_mock().await);
let input = KclValue::MixedArray {
value: vec![
KclValue::Number {
value: 1.1,
meta: Default::default(),
ty: NumericType::Unknown,
ty: NumericType::mm(),
},
KclValue::Number {
value: 2.2,
meta: Default::default(),
ty: NumericType::Unknown,
ty: NumericType::mm(),
},
KclValue::Number {
value: 3.3,
meta: Default::default(),
ty: NumericType::Unknown,
ty: NumericType::mm(),
},
],
meta: Default::default(),
};
let expected = Point3d { x: 1.1, y: 2.2, z: 3.3 };
let actual = array_to_point3d(&input, Vec::new());
let expected = [
TyF64::new(1.1, NumericType::mm()),
TyF64::new(2.2, NumericType::mm()),
TyF64::new(3.3, NumericType::mm()),
];
let actual = array_to_point3d(&input, Vec::new(), &mut exec_state);
assert_eq!(actual.unwrap(), expected);
}
}
@ -701,10 +717,11 @@ mod tests {
pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
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 distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
let axis: [TyF64; 2] = args.get_kw_arg_typed("axis", &RuntimeType::point2d(), exec_state)?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let axis = untype_point(axis).0;
if axis == [0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails {
message:
@ -714,7 +731,8 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
}));
}
let sketches = inner_pattern_linear_2d(sketches, instances, distance, axis, use_original, exec_state, args).await?;
let sketches =
inner_pattern_linear_2d(sketches, instances, distance.n, axis, use_original, exec_state, args).await?;
Ok(sketches.into())
}
@ -780,10 +798,11 @@ async fn inner_pattern_linear_2d(
pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
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 distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
let axis: [TyF64; 3] = args.get_kw_arg_typed("axis", &RuntimeType::point3d(), exec_state)?;
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
let (axis, _) = untype_point_3d(axis);
if axis == [0.0, 0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails {
message:
@ -793,7 +812,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
}));
}
let solids = inner_pattern_linear_3d(solids, instances, distance, axis, use_original, exec_state, args).await?;
let solids = inner_pattern_linear_3d(solids, instances, distance.n, axis, use_original, exec_state, args).await?;
Ok(solids.into())
}
@ -828,11 +847,11 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
/// |> close(%)
/// |> extrude(length = 65)
///
/// const thing1 = startSketchOn(case, END)
/// const thing1 = startSketchOn(case, face = END)
/// |> circle(center = [-size / 2, -size / 2], radius = 25)
/// |> extrude(length = 50)
///
/// const thing2 = startSketchOn(case, END)
/// const thing2 = startSketchOn(case, face = END)
/// |> circle(center = [size / 2, -size / 2], radius = 25)
/// |> extrude(length = 50)
///
@ -856,7 +875,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
/// |> close(%)
/// |> extrude(length = 65)
///
/// const thing1 = startSketchOn(case, END)
/// const thing1 = startSketchOn(case, face = END)
/// |> circle(center =[-size / 2, -size / 2], radius = 25)
/// |> extrude(length = 50)
///
@ -1025,16 +1044,16 @@ impl CircularPattern {
pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
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 center: [TyF64; 2] = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?;
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::angle(), exec_state)?;
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(
sketches,
instances,
center,
arc_degrees,
untype_point(center).0,
arc_degrees.n,
rotate_duplicates,
use_original,
exec_state,
@ -1134,11 +1153,11 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
// 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")?;
let axis: [TyF64; 3] = args.get_kw_arg_typed("axis", &RuntimeType::point3d(), exec_state)?;
// The center about which to make the pattern. This is a 3D vector.
let center: [f64; 3] = args.get_kw_arg("center")?;
let center: [TyF64; 3] = args.get_kw_arg_typed("center", &RuntimeType::point3d(), exec_state)?;
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?;
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::angle(), exec_state)?;
// 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,
@ -1148,9 +1167,9 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
let solids = inner_pattern_circular_3d(
solids,
instances,
axis,
center,
arc_degrees,
untype_point_3d(axis).0,
untype_point_3d(center).0,
arc_degrees.n,
rotate_duplicates,
use_original,
exec_state,