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:
@ -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),
|
||||
|
@ -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, ¤t_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 =
|
||||
|
Reference in New Issue
Block a user