Involute curve for sketch (#6258)

* WIP: Involute circles in KCL

* first pass involute end calculation

* fmt

* cleanup

* involute snapshot

* actually update markdown

* remove debug build stuff

* spacing

---------

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
This commit is contained in:
Ben Crabbe
2025-04-11 21:59:11 +01:00
committed by GitHub
parent 60d2be9ff2
commit 9e1f1152e3
6 changed files with 11511 additions and 0 deletions

View File

@ -78,6 +78,7 @@ layout: manual
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`intersect`](kcl/intersect)
* [`involuteCircular`](kcl/involuteCircular)
* [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY)
* [`legAngX`](kcl/legAngX)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -71,6 +71,7 @@ lazy_static! {
Box::new(crate::std::segment::AngleToMatchLengthY),
Box::new(crate::std::shapes::CircleThreePoint),
Box::new(crate::std::shapes::Polygon),
Box::new(crate::std::sketch::InvoluteCircular),
Box::new(crate::std::sketch::Line),
Box::new(crate::std::sketch::XLine),
Box::new(crate::std::sketch::YLine),

View File

@ -4,6 +4,7 @@ use anyhow::Result;
use indexmap::IndexMap;
use kcl_derive_docs::stdlib;
use kcmc::shared::Point2d as KPoint2d; // Point2d is already defined in this pkg, to impl ts_rs traits.
use kcmc::shared::Point3d as KPoint3d; // Point3d is already defined in this pkg, to impl ts_rs traits.
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, websocket::ModelingCmdReq, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
use kittycad_modeling_cmds::shared::PathSegment;
@ -94,6 +95,143 @@ pub enum StartOrEnd {
pub const NEW_TAG_KW: &str = "tag";
pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch =
args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
/*
*/
let start_radius = args.get_kw_arg("startRadius")?;
let end_radius = args.get_kw_arg("endRadius")?;
let angle = args.get_kw_arg("angle")?;
let reverse = args.get_kw_arg_opt("reverse")?;
let tag = args.get_kw_arg_opt("tag")?;
let new_sketch =
inner_involute_circular(sketch, start_radius, end_radius, angle, reverse, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
}
fn involute_curve(radius: f64, angle: f64) -> (f64, f64) {
(
radius * (angle.cos() + angle * angle.sin()),
radius * (angle.sin() - angle * angle.cos()),
)
}
/// Extend the current sketch with a new involute circular curve.
///
/// ```no_run
/// a = 10
/// b = 14
/// startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> involuteCircular(startRadius = a, endRadius = b, angle = 60)
/// |> involuteCircular(startRadius = a, endRadius = b, angle = 60, reverse = true)
///
/// ```
#[stdlib {
name = "involuteCircular",
keywords = true,
unlabeled_first = true,
args = {
sketch = { docs = "Which sketch should this path be added to?"},
start_radius = { docs = "The involute is described between two circles, start_radius is the radius of the inner circle."},
end_radius = { docs = "The involute is described between two circles, end_radius is the radius of the outer circle."},
angle = { docs = "The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve."},
reverse = { docs = "If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start. Defaults to false."},
tag = { docs = "Create a new tag which refers to this line"},
}
}]
#[allow(clippy::too_many_arguments)]
async fn inner_involute_circular(
sketch: Sketch,
start_radius: f64,
end_radius: f64,
angle: f64,
reverse: Option<bool>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let id = exec_state.next_uuid();
let angle = Angle::from_degrees(angle);
let segment = PathSegment::CircularInvolute {
start_radius: LengthUnit(start_radius),
end_radius: LengthUnit(end_radius),
angle,
reverse: reverse.unwrap_or_default(),
};
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment,
}),
)
.await?;
let from = sketch.current_pen_position()?;
let mut end: KPoint3d<f64> = Default::default(); // ADAM: TODO impl this below.
let theta = f64::sqrt(end_radius * end_radius - start_radius * start_radius) / start_radius;
let (x, y) = involute_curve(start_radius, theta);
end.x = x * angle.to_radians().cos() - y * angle.to_radians().sin();
end.y = x * angle.to_radians().sin() + y * angle.to_radians().cos();
end.x -= start_radius * angle.to_radians().cos();
end.y -= start_radius * angle.to_radians().sin();
if reverse.unwrap_or_default() {
end.x = -end.x;
}
end.x += from.x;
end.y += from.y;
// let path_json = path_to_json();
// let end = args
// .send_modeling_cmd(
// exec_state.next_uuid(),
// ModelingCmd::EngineUtilEvaluatePath(mcmd::EngineUtilEvaluatePath { path_json, t: 1.0 }),
// )
// .await?;
// let end = match end {
// kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Modeling {
// modeling_response: OkModelingCmdResponse::EngineUtilEvaluatePath(eval_path),
// } => eval_path.pos,
// other => {
// return Err(KclError::Engine(KclErrorDetails {
// source_ranges: vec![args.source_range],
// message: format!("Expected EngineUtilEvaluatePath response but found {other:?}"),
// }))
// }
// };
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
to: [end.x, end.y],
tag: tag.clone(),
units: sketch.units,
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
};
let mut new_sketch = sketch.clone();
if let Some(tag) = &tag {
new_sketch.add_tag(tag, &current_path, exec_state);
}
new_sketch.paths.push(current_path);
Ok(new_sketch)
}
/// Draw a line to a point.
pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch =

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB