added Ellipse and EllipticalArc to kcl

This commit is contained in:
benjamaan476
2025-05-28 14:26:05 +01:00
parent 783b6ed76c
commit 8cf67a29bf
9 changed files with 325 additions and 19 deletions

View File

@ -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)

20
rust/Cargo.lock generated
View File

@ -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]]

View File

@ -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" }

View File

@ -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)

View File

@ -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 {

View File

@ -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),

View File

@ -471,3 +471,117 @@ async fn inner_polygon(
Ok(sketch)
}
/// Sketch an ellipse.
pub async fn ellipse(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
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<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
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, &current_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)
}

View File

@ -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<KclValue, KclError> {
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<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
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, &current_path, exec_state);
}
new_sketch.paths.push(current_path);
Ok(new_sketch)
}
#[cfg(test)]
mod tests {

View File

@ -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