diff --git a/docs/kcl-std/modules/std-sketch.md b/docs/kcl-std/modules/std-sketch.md index ea3e96e1c..61218bd5e 100644 --- a/docs/kcl-std/modules/std-sketch.md +++ b/docs/kcl-std/modules/std-sketch.md @@ -19,6 +19,7 @@ This module contains functions for creating and manipulating sketches, and makin * [`circle`](/docs/kcl-std/functions/std-sketch-circle) * [`circleThreePoint`](/docs/kcl-std/circleThreePoint) * [`close`](/docs/kcl-std/close) +* [`ellipse`](/doc/kcl-std/ellipse) * [`extrude`](/docs/kcl-std/extrude) * [`getCommonEdge`](/docs/kcl-std/getCommonEdge) * [`getNextAdjacentEdge`](/docs/kcl-std/getNextAdjacentEdge) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 1cae8e2bc..1c9e192f6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -535,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -963,7 +963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1746,7 +1746,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2083,8 +2083,6 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds" version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ba95c22493d79ec8a1faab963d8903f6de0e373efedf2bc3bb76a0ddbab036" dependencies = [ "anyhow", "chrono", @@ -2109,8 +2107,6 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds-macros" version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9bb1a594541b878adc1c8dcb821328774bf7aa09b65b104a206b1291a5235c" dependencies = [ "kittycad-modeling-cmds-macros-impl", "proc-macro2", @@ -2121,8 +2117,6 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds-macros-impl" version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb4ee23cc996aa2dca7584d410e8826e08161e1ac4335bb646d5ede33f37cb3" dependencies = [ "proc-macro2", "quote", @@ -3052,7 +3046,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3377,7 +3371,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3993,7 +3987,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4856,7 +4850,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index bee830c7b..d42f569bd 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -60,6 +60,6 @@ lossy_float_literal = "warn" result_large_err = "allow" # Example: how to point modeling-app at a different repo (e.g. a branch or a local clone) -#[patch.crates-io] -#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" } +[patch.crates-io] +kittycad-modeling-cmds = { path = "../../modeling-api/modeling-cmds" } #kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" } diff --git a/rust/kcl-lib/src/execution/geometry.rs b/rust/kcl-lib/src/execution/geometry.rs index 113cbe123..965ed3f53 100644 --- a/rust/kcl-lib/src/execution/geometry.rs +++ b/rust/kcl-lib/src/execution/geometry.rs @@ -661,8 +661,21 @@ impl SketchSurface { #[derive(Debug, Clone)] pub(crate) enum GetTangentialInfoFromPathsResult { PreviousPoint([f64; 2]), - Arc { center: [f64; 2], ccw: bool }, - Circle { center: [f64; 2], ccw: bool, radius: f64 }, + Arc { + center: [f64; 2], + ccw: bool, + }, + Circle { + center: [f64; 2], + ccw: bool, + radius: f64, + }, + Ellipse { + center: [f64; 2], + ccw: bool, + major_radius: f64, + _minor_radius: f64, + }, } impl GetTangentialInfoFromPathsResult { @@ -677,6 +690,12 @@ impl GetTangentialInfoFromPathsResult { GetTangentialInfoFromPathsResult::Circle { center, radius, ccw, .. } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }], + GetTangentialInfoFromPathsResult::Ellipse { + center, + major_radius, + ccw, + .. + } => [center[0] + major_radius, center[1] + if *ccw { -1.0 } else { 1.0 }], } } } @@ -1125,6 +1144,14 @@ pub enum Path { /// True if the arc is counterclockwise. ccw: bool, }, + Ellipse { + #[serde(flatten)] + base: BasePath, + center: [f64; 2], + major_radius: f64, + minor_radius: f64, + ccw: bool, + }, } /// What kind of path is this? @@ -1139,6 +1166,7 @@ enum PathType { Horizontal, AngledLineTo, Arc, + Ellipse, } impl From<&Path> for PathType { @@ -1154,6 +1182,7 @@ impl From<&Path> for PathType { Path::Base { .. } => Self::Base, Path::Arc { .. } => Self::Arc, Path::ArcThreePoint { .. } => Self::Arc, + Path::Ellipse { .. } => Self::Ellipse, } } } @@ -1171,6 +1200,7 @@ impl Path { Path::CircleThreePoint { base, .. } => base.geo_meta.id, Path::Arc { base, .. } => base.geo_meta.id, Path::ArcThreePoint { base, .. } => base.geo_meta.id, + Path::Ellipse { base, .. } => base.geo_meta.id, } } @@ -1186,6 +1216,7 @@ impl Path { Path::CircleThreePoint { base, .. } => base.geo_meta.id = id, Path::Arc { base, .. } => base.geo_meta.id = id, Path::ArcThreePoint { base, .. } => base.geo_meta.id = id, + Path::Ellipse { base, .. } => base.geo_meta.id = id, } } @@ -1201,6 +1232,7 @@ impl Path { Path::CircleThreePoint { base, .. } => base.tag.clone(), Path::Arc { base, .. } => base.tag.clone(), Path::ArcThreePoint { base, .. } => base.tag.clone(), + Path::Ellipse { base, .. } => base.tag.clone(), } } @@ -1216,6 +1248,7 @@ impl Path { Path::CircleThreePoint { base, .. } => base, Path::Arc { base, .. } => base, Path::ArcThreePoint { base, .. } => base, + Path::Ellipse { base, .. } => base, } } @@ -1291,6 +1324,10 @@ impl Path { // TODO: Call engine utils to figure this out. linear_distance(&self.get_base().from, &self.get_base().to) } + Self::Ellipse { .. } => { + //TODO: fix me + 10.0 + } }; TyF64::new(n, self.get_base().units.into()) } @@ -1307,6 +1344,7 @@ impl Path { Path::CircleThreePoint { base, .. } => Some(base), Path::Arc { base, .. } => Some(base), Path::ArcThreePoint { base, .. } => Some(base), + Path::Ellipse { base, .. } => Some(base), } } @@ -1342,6 +1380,18 @@ impl Path { radius: circle.radius, } } + Path::Ellipse { + center, + major_radius, + minor_radius, + ccw, + .. + } => GetTangentialInfoFromPathsResult::Ellipse { + center: *center, + major_radius: *major_radius, + _minor_radius: *minor_radius, + ccw: *ccw, + }, Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => { let base = self.get_base(); GetTangentialInfoFromPathsResult::PreviousPoint(base.from) diff --git a/rust/kcl-lib/src/std/extrude.rs b/rust/kcl-lib/src/std/extrude.rs index fb48a6313..3304d48e0 100644 --- a/rust/kcl-lib/src/std/extrude.rs +++ b/rust/kcl-lib/src/std/extrude.rs @@ -329,6 +329,7 @@ pub(crate) async fn do_post_extrude<'a>( Path::Arc { .. } | Path::TangentialArc { .. } | Path::TangentialArcTo { .. } + | Path::Ellipse { .. } | Path::Circle { .. } | Path::CircleThreePoint { .. } => { let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc { diff --git a/rust/kcl-lib/src/std/mod.rs b/rust/kcl-lib/src/std/mod.rs index 12ca4865f..e334ad263 100644 --- a/rust/kcl-lib/src/std/mod.rs +++ b/rust/kcl-lib/src/std/mod.rs @@ -58,7 +58,9 @@ lazy_static! { Box::new(crate::std::segment::SegAng), Box::new(crate::std::segment::TangentToEnd), Box::new(crate::std::shapes::CircleThreePoint), + Box::new(crate::std::shapes::Ellipse), Box::new(crate::std::shapes::Polygon), + Box::new(crate::std::sketch::EllipticalArc), Box::new(crate::std::sketch::InvoluteCircular), Box::new(crate::std::sketch::Line), Box::new(crate::std::sketch::XLine), diff --git a/rust/kcl-lib/src/std/shapes.rs b/rust/kcl-lib/src/std/shapes.rs index 071fca852..4acebdbd1 100644 --- a/rust/kcl-lib/src/std/shapes.rs +++ b/rust/kcl-lib/src/std/shapes.rs @@ -471,3 +471,117 @@ async fn inner_polygon( Ok(sketch) } + +/// Sketch an ellipse. +pub async fn ellipse(exec_state: &mut ExecState, args: Args) -> Result { + let sketch_or_surface = args.get_unlabeled_kw_arg("sketchOrSurface")?; + let center = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?; + let major_radius: TyF64 = args.get_kw_arg_typed("majorRadius", &RuntimeType::length(), exec_state)?; + let minor_radius: TyF64 = args.get_kw_arg_typed("minorRadius", &RuntimeType::length(), exec_state)?; + let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; + + let sketch = inner_ellipse( + sketch_or_surface, + center, + major_radius, + minor_radius, + tag, + exec_state, + args, + ) + .await?; + Ok(KclValue::Sketch { + value: Box::new(sketch), + }) +} + +/// Construct an ellipse derived from center and major/minor axes. +/// +/// ```no_run +/// exampleSketch = startSketchOn(XY) +/// |> ellipse(center = [10,10], majorRadius = 5, minorRadius = 2) +/// |> extrude(length = 5) +/// ``` +#[stdlib { + name = "ellipse", + unlabeled_first = true, + args = { + sketch_surface_or_group = { docs = "Plane or surface to sketch on" }, + center = {docs = "The center of the ellipse."}, + major_radius = {docs = "The length along the x axis."}, + minor_radius = {docs = "The length along the y axis."}, + tag = {docs = "Identifier for the circle to reference elsewhere."}, + }, + tags = ["sketch"] +}] +async fn inner_ellipse( + sketch_surface_or_group: SketchOrSurface, + center: [TyF64; 2], + major_radius: TyF64, + minor_radius: TyF64, + tag: Option, + exec_state: &mut ExecState, + args: Args, +) -> Result { + let sketch_surface = match sketch_surface_or_group { + SketchOrSurface::SketchSurface(surface) => surface, + SketchOrSurface::Sketch(group) => group.on, + }; + let (center_u, ty) = untype_point(center.clone()); + let units = ty.expect_length(); + + let from = [center_u[0] + major_radius.to_length_units(units), center_u[1]]; + let from_t = [TyF64::new(from[0], ty.clone()), TyF64::new(from[1], ty)]; + + let sketch = + crate::std::sketch::inner_start_profile(sketch_surface, from_t, None, exec_state, args.clone()).await?; + + let angle_start = Angle::zero(); + let angle_end = Angle::turn(); + + let id = exec_state.next_uuid(); + + args.batch_modeling_cmd( + id, + ModelingCmd::from(mcmd::ExtendPath { + path: sketch.id.into(), + segment: PathSegment::Ellipse { + center: KPoint2d::from(point_to_mm(center)).map(LengthUnit), + major_radius: LengthUnit(major_radius.to_mm()), + minor_radius: LengthUnit(minor_radius.to_mm()), + start_angle: Angle::from_degrees(angle_start.to_degrees()), + end_angle: Angle::from_degrees(angle_end.to_degrees()), + }, + }), + ) + .await?; + + let current_path = Path::Ellipse { + base: BasePath { + from, + to: from, + tag: tag.clone(), + units, + geo_meta: GeoMeta { + id, + metadata: args.source_range.into(), + }, + }, + major_radius: major_radius.to_length_units(units), + minor_radius: minor_radius.to_length_units(units), + center: center_u, + ccw: angle_start < angle_end, + }; + + 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); + + args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id })) + .await?; + + Ok(new_sketch) +} diff --git a/rust/kcl-lib/src/std/sketch.rs b/rust/kcl-lib/src/std/sketch.rs index 332077058..2208f3011 100644 --- a/rust/kcl-lib/src/std/sketch.rs +++ b/rust/kcl-lib/src/std/sketch.rs @@ -32,6 +32,8 @@ use crate::{ }, }; +use super::utils::untype_point; + /// A tag for a face. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] @@ -2410,6 +2412,148 @@ async fn inner_subtract_2d( Ok(sketch) } +/// Draw an elliptical arc. +pub async fn elliptical_arc(exec_state: &mut ExecState, args: Args) -> Result { + let sketch = + args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; + + let center: [TyF64; 2] = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?; + let angle_start: TyF64 = args.get_kw_arg_typed("angleStart", &RuntimeType::degrees(), exec_state)?; + let angle_end: TyF64 = args.get_kw_arg_typed("angleEnd", &RuntimeType::degrees(), exec_state)?; + let major_radius: TyF64 = args.get_kw_arg_typed("majorRadius", &RuntimeType::length(), exec_state)?; + let minor_radius: TyF64 = args.get_kw_arg_typed("minorRadius", &RuntimeType::length(), exec_state)?; + let end_absolute: Option<[TyF64; 2]> = + args.get_kw_arg_opt_typed("endAbsolute", &RuntimeType::point2d(), exec_state)?; + let interior_absolute: Option<[TyF64; 2]> = + args.get_kw_arg_opt_typed("interiorAbsolute", &RuntimeType::point2d(), exec_state)?; + let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; + let new_sketch = inner_elliptical_arc( + sketch, + center, + angle_start, + angle_end, + major_radius, + minor_radius, + interior_absolute, + end_absolute, + tag, + exec_state, + args, + ) + .await?; + Ok(KclValue::Sketch { + value: Box::new(new_sketch), + }) +} + +/// ```no_run +/// exampleSketch = startSketchOn(XZ) +/// |> startProfile(at = [0, 0]) +/// |> ellipticalArc( +/// endAbsolute = [10,0], +/// interiorAbsolute = [5,5] +/// ) +/// |> close() +/// example = extrude(exampleSketch, length = 10) +/// ``` +#[stdlib { + name = "ellipticalArc", + unlabeled_first = true, + args = { + sketch = { docs = "Which sketch should this path be added to?" }, + center = { docs = "The center of the ellipse.", include_in_snippet = true }, + angle_start = { docs = "Where along the circle should this arc start?", include_in_snippet = true }, + angle_end = { docs = "Where along the circle should this arc end?", include_in_snippet = true }, + major_radius = { docs = "The length of the ellipse in the x direction", include_in_snippet = true }, + minor_radius = { docs = "The length of the ellipse in the y direction", include_in_snippet = true }, + interior_absolute = { docs = "Any point between the arc's start and end? Requires `endAbsolute`. Incompatible with `angleStart` or `angleEnd`" }, + end_absolute = { docs = "Where should this arc end? Requires `interiorAbsolute`. Incompatible with `angleStart` or `angleEnd`" }, + tag = { docs = "Create a new tag which refers to this line"}, + }, + tags = ["sketch"] +}] +#[allow(clippy::too_many_arguments)] +pub(crate) async fn inner_elliptical_arc( + sketch: Sketch, + center: [TyF64; 2], + angle_start: TyF64, + angle_end: TyF64, + major_radius: TyF64, + minor_radius: TyF64, + _interior_absolute: Option<[TyF64; 2]>, + _end_absolute: Option<[TyF64; 2]>, + tag: Option, + exec_state: &mut ExecState, + args: Args, +) -> Result { + let from: Point2d = sketch.current_pen_position()?; + let id = exec_state.next_uuid(); + + let (center_u, _) = untype_point(center); + + let start_angle = Angle::from_degrees(angle_start.to_degrees()); + let end_angle = Angle::from_degrees(angle_end.to_degrees()); + let to = [ + center_u[0] + major_radius.to_length_units(from.units) * start_angle.to_radians().cos(), + center_u[1] + minor_radius.to_length_units(from.units) * end_angle.to_radians().sin(), + ]; + + // match (angle_start, angle_end, radius, interior_absolute, end_absolute) { + // (Some(angle_start), Some(angle_end), Some(radius), None, None) => { + // relative_arc(&args, id, exec_state, sketch, from, angle_start, angle_end, radius, tag).await + // } + // (None, None, None, Some(interior_absolute), Some(end_absolute)) => { + // absolute_arc(&args, id, exec_state, sketch, from, interior_absolute, end_absolute, tag).await + // } + // _ => { + // Err(KclError::Type(KclErrorDetails::new( + // "Invalid combination of arguments. Either provide (angleStart, angleEnd, radius) or (endAbsolute, interiorAbsolute)".to_owned(), + // vec![args.source_range], + // ))) + // } + // } + // + args.batch_modeling_cmd( + id, + ModelingCmd::from(mcmd::ExtendPath { + path: sketch.id.into(), + segment: PathSegment::Ellipse { + center: KPoint2d::from(untyped_point_to_mm(center_u, from.units)).map(LengthUnit), + major_radius: LengthUnit(from.units.adjust_to(major_radius.to_mm(), UnitLen::Mm).0), + minor_radius: LengthUnit(from.units.adjust_to(minor_radius.to_mm(), UnitLen::Mm).0), + start_angle, + end_angle, + }, + }), + ) + .await?; + + let current_path = Path::Ellipse { + ccw: start_angle < end_angle, + center: center_u, + major_radius: major_radius.to_mm(), + minor_radius: minor_radius.to_mm(), + base: BasePath { + from: from.ignore_units(), + to, + 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) +} + #[cfg(test)] mod tests { diff --git a/rust/kcl-wasm-lib/Cargo.toml b/rust/kcl-wasm-lib/Cargo.toml index 88be08db1..181a935da 100644 --- a/rust/kcl-wasm-lib/Cargo.toml +++ b/rust/kcl-wasm-lib/Cargo.toml @@ -53,5 +53,5 @@ wasm-bindgen-test = "0.3.50" typed-path = "0.11.0" # Local development only. Placeholder to speed up development cycle -#[package.metadata.wasm-pack.profile.release] -#wasm-opt = false +[package.metadata.wasm-pack.profile.release] +wasm-opt = false