KCL: rectangle function (#7616)

* KCL test for rectangle

* Rectangle function

* Rectangle helper tests

* Rectangle helper

* Fix clippy lints

* Update docs

* fmt

* Fix bug

* fmt

* Fix doc comments

* Update generated docs

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
This commit is contained in:
Adam Chalmers
2025-07-01 13:26:04 -05:00
committed by GitHub
parent 7f9851ae28
commit 051bb0589e
29 changed files with 2182 additions and 1 deletions

View File

@ -38,6 +38,119 @@ pub enum SketchOrSurface {
Sketch(Box<Sketch>),
}
/// Sketch a rectangle.
pub async fn rectangle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch_or_surface =
args.get_unlabeled_kw_arg("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
let center = args.get_kw_arg_opt("center", &RuntimeType::point2d(), exec_state)?;
let corner = args.get_kw_arg_opt("corner", &RuntimeType::point2d(), exec_state)?;
let width: TyF64 = args.get_kw_arg("width", &RuntimeType::length(), exec_state)?;
let height: TyF64 = args.get_kw_arg("height", &RuntimeType::length(), exec_state)?;
inner_rectangle(sketch_or_surface, center, corner, width, height, exec_state, args)
.await
.map(Box::new)
.map(|value| KclValue::Sketch { value })
}
async fn inner_rectangle(
sketch_or_surface: SketchOrSurface,
center: Option<[TyF64; 2]>,
corner: Option<[TyF64; 2]>,
width: TyF64,
height: TyF64,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let sketch_surface = match sketch_or_surface {
SketchOrSurface::SketchSurface(surface) => surface,
SketchOrSurface::Sketch(s) => s.on,
};
// Find the corner in the negative quadrant
let (ty, corner) = match (center, corner) {
(Some(center), None) => (
center[0].ty,
[center[0].n - width.n / 2.0, center[1].n - height.n / 2.0],
),
(None, Some(corner)) => (corner[0].ty, [corner[0].n, corner[1].n]),
(None, None) => {
return Err(KclError::new_semantic(KclErrorDetails::new(
"You must supply either `corner` or `center` arguments, but not both".to_string(),
vec![args.source_range],
)));
}
(Some(_), Some(_)) => {
return Err(KclError::new_semantic(KclErrorDetails::new(
"You must supply either `corner` or `center` arguments, but not both".to_string(),
vec![args.source_range],
)));
}
};
let units = ty.expect_length();
let corner_t = [TyF64::new(corner[0], ty), TyF64::new(corner[1], ty)];
// Start the sketch then draw the 4 lines.
let sketch =
crate::std::sketch::inner_start_profile(sketch_surface, corner_t, None, exec_state, args.clone()).await?;
let sketch_id = sketch.id;
let deltas = [[width.n, 0.0], [0.0, height.n], [-width.n, 0.0], [0.0, -height.n]];
let ids = [
exec_state.next_uuid(),
exec_state.next_uuid(),
exec_state.next_uuid(),
exec_state.next_uuid(),
];
for (id, delta) in ids.iter().copied().zip(deltas) {
exec_state
.batch_modeling_cmd(
ModelingCmdMeta::from_args_id(&args, id),
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Line {
end: KPoint2d::from(untyped_point_to_mm(delta, units))
.with_z(0.0)
.map(LengthUnit),
relative: true,
},
}),
)
.await?;
}
exec_state
.batch_modeling_cmd(
ModelingCmdMeta::from_args_id(&args, sketch_id),
ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }),
)
.await?;
// Update the sketch in KCL memory.
let mut new_sketch = sketch.clone();
fn add(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
[a[0] + b[0], a[1] + b[1]]
}
let a = (corner, add(corner, deltas[0]));
let b = (a.1, add(a.1, deltas[1]));
let c = (b.1, add(b.1, deltas[2]));
let d = (c.1, add(c.1, deltas[3]));
for (id, (from, to)) in ids.into_iter().zip([a, b, c, d]) {
let current_path = Path::ToPoint {
base: BasePath {
from,
to,
tag: None,
units,
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
};
new_sketch.paths.push(current_path);
}
Ok(new_sketch)
}
/// Sketch a circle.
pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch_or_surface =