KCL: Absolute point bezier curves (#7172)

Previously KCL bezier curves could only use relative control points. Now you can use absolute control points too. 

Here's an example of the new arguments:

```kcl
startSketchOn(XY)
  |> startProfile(at = [300, 300])
  |> bezierCurve(control1Absolute = [600, 300], control2Absolute = [-300, -100], endAbsolute = [600, 300])
  |> close()
  |> extrude(length = 10)
```

Closes https://github.com/KittyCAD/modeling-app/issues/7083
This commit is contained in:
Adam Chalmers
2025-05-22 12:06:06 -05:00
committed by GitHub
parent 2c7701e2d4
commit 04a2c184d7
4 changed files with 4960 additions and 45 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -2154,12 +2154,27 @@ async fn inner_tangential_arc_to_point(
pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch = let sketch =
args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
let end: [TyF64; 2] = args.get_kw_arg_typed("end", &RuntimeType::point2d(), exec_state)?; let control1 = args.get_kw_arg_opt_typed("control1", &RuntimeType::point2d(), exec_state)?;
let control1: [TyF64; 2] = args.get_kw_arg_typed("control1", &RuntimeType::point2d(), exec_state)?; let control2 = args.get_kw_arg_opt_typed("control2", &RuntimeType::point2d(), exec_state)?;
let control2: [TyF64; 2] = args.get_kw_arg_typed("control2", &RuntimeType::point2d(), exec_state)?; let end = args.get_kw_arg_opt_typed("end", &RuntimeType::point2d(), exec_state)?;
let control1_absolute = args.get_kw_arg_opt_typed("control1Absolute", &RuntimeType::point2d(), exec_state)?;
let control2_absolute = args.get_kw_arg_opt_typed("control2Absolute", &RuntimeType::point2d(), exec_state)?;
let end_absolute = args.get_kw_arg_opt_typed("endAbsolute", &RuntimeType::point2d(), exec_state)?;
let tag = args.get_kw_arg_opt("tag")?; let tag = args.get_kw_arg_opt("tag")?;
let new_sketch = inner_bezier_curve(sketch, control1, control2, end, tag, exec_state, args).await?; let new_sketch = inner_bezier_curve(
sketch,
control1,
control2,
end,
control1_absolute,
control2_absolute,
end_absolute,
tag,
exec_state,
args,
)
.await?;
Ok(KclValue::Sketch { Ok(KclValue::Sketch {
value: Box::new(new_sketch), value: Box::new(new_sketch),
}) })
@ -2170,6 +2185,7 @@ pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclV
/// shape. /// shape.
/// ///
/// ```no_run /// ```no_run
/// // Example using relative control points.
/// exampleSketch = startSketchOn(XZ) /// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0]) /// |> startProfile(at = [0, 0])
/// |> line(end = [0, 10]) /// |> line(end = [0, 10])
@ -2183,51 +2199,101 @@ pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclV
/// ///
/// example = extrude(exampleSketch, length = 10) /// example = extrude(exampleSketch, length = 10)
/// ``` /// ```
/// ```no_run
/// // Example using absolute control points.
/// startSketchOn(XY)
/// |> startProfile(at = [300, 300])
/// |> bezierCurve(control1Absolute = [600, 300], control2Absolute = [-300, -100], endAbsolute = [600, 600])
/// |> close()
/// |> extrude(length = 10)
/// ```
#[stdlib { #[stdlib {
name = "bezierCurve", name = "bezierCurve",
unlabeled_first = true, unlabeled_first = true,
args = { args = {
sketch = { docs = "Which sketch should this path be added to?"}, sketch = { docs = "Which sketch should this path be added to?"},
end = { docs = "How far away (along the X and Y axes) should this line go?" },
control1 = { docs = "First control point for the cubic" }, control1 = { docs = "First control point for the cubic" },
control2 = { docs = "Second control point for the cubic" }, control2 = { docs = "Second control point for the cubic" },
end = { docs = "How far away (along the X and Y axes) should this line go?" },
control1_absolute = { docs = "First control point for the cubic. Absolute point." },
control2_absolute = { docs = "Second control point for the cubic. Absolute point." },
end_absolute = { docs = "Coordinate on the plane at which this line should end." },
tag = { docs = "Create a new tag which refers to this line"}, tag = { docs = "Create a new tag which refers to this line"},
}, },
tags = ["sketch"] tags = ["sketch"]
}] }]
#[allow(clippy::too_many_arguments)]
async fn inner_bezier_curve( async fn inner_bezier_curve(
sketch: Sketch, sketch: Sketch,
control1: [TyF64; 2], control1: Option<[TyF64; 2]>,
control2: [TyF64; 2], control2: Option<[TyF64; 2]>,
end: [TyF64; 2], end: Option<[TyF64; 2]>,
control1_absolute: Option<[TyF64; 2]>,
control2_absolute: Option<[TyF64; 2]>,
end_absolute: Option<[TyF64; 2]>,
tag: Option<TagNode>, tag: Option<TagNode>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let relative = true;
let delta = end.clone();
let to = [
from.x + end[0].to_length_units(from.units),
from.y + end[1].to_length_units(from.units),
];
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( let to = match (
id, control1,
ModelingCmd::from(mcmd::ExtendPath { control2,
path: sketch.id.into(), end,
segment: PathSegment::Bezier { control1_absolute,
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit), control2_absolute,
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit), end_absolute,
end: KPoint2d::from(point_to_mm(delta)).with_z(0.0).map(LengthUnit), ) {
relative, // Relative
}, (Some(control1), Some(control2), Some(end), None, None, None) => {
}), let delta = end.clone();
) let to = [
.await?; from.x + end[0].to_length_units(from.units),
from.y + end[1].to_length_units(from.units),
];
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Bezier {
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit),
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(point_to_mm(delta)).with_z(0.0).map(LengthUnit),
relative: true,
},
}),
)
.await?;
to
}
// Absolute
(None, None, None, Some(control1), Some(control2), Some(end)) => {
let to = [end[0].to_length_units(from.units), end[1].to_length_units(from.units)];
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::Bezier {
control1: KPoint2d::from(point_to_mm(control1)).with_z(0.0).map(LengthUnit),
control2: KPoint2d::from(point_to_mm(control2)).with_z(0.0).map(LengthUnit),
end: KPoint2d::from(point_to_mm(end)).with_z(0.0).map(LengthUnit),
relative: false,
},
}),
)
.await?;
to
}
_ => {
return Err(KclError::Semantic(KclErrorDetails::new(
"You must either give `control1`, `control2` and `end`, or `control1Absolute`, `control2Absolute` and `endAbsolute`.".to_owned(),
vec![args.source_range],
)));
}
};
let current_path = Path::ToPoint { let current_path = Path::ToPoint {
base: BasePath { base: BasePath {

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB