From 64c4d8eb243b24affba66dbcefbd7276dc6993ff Mon Sep 17 00:00:00 2001 From: benjamaan476 Date: Thu, 5 Jun 2025 12:25:14 +0100 Subject: [PATCH] use coeffs in conic fn --- rust/kcl-lib/src/std/args.rs | 28 ++++++ rust/kcl-lib/src/std/sketch.rs | 167 ++++++++++++++++++--------------- rust/kcl-lib/src/std/utils.rs | 9 ++ 3 files changed, 128 insertions(+), 76 deletions(-) diff --git a/rust/kcl-lib/src/std/args.rs b/rust/kcl-lib/src/std/args.rs index 52e595e51..483b220d2 100644 --- a/rust/kcl-lib/src/std/args.rs +++ b/rust/kcl-lib/src/std/args.rs @@ -1323,6 +1323,34 @@ impl<'a> FromKclValue<'a> for [TyF64; 3] { } } +impl<'a> FromKclValue<'a> for [TyF64; 6] { + fn from_kcl_val(arg: &'a KclValue) -> Option { + match arg { + KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => { + if value.len() != 6 { + return None; + } + let v0 = value.first()?; + let v1 = value.get(1)?; + let v2 = value.get(2)?; + let v3 = value.get(3)?; + let v4 = value.get(4)?; + let v5 = value.get(5)?; + let array = [ + v0.as_ty_f64()?, + v1.as_ty_f64()?, + v2.as_ty_f64()?, + v3.as_ty_f64()?, + v4.as_ty_f64()?, + v5.as_ty_f64()?, + ]; + Some(array) + } + _ => None, + } + } +} + impl<'a> FromKclValue<'a> for Sketch { fn from_kcl_val(arg: &'a KclValue) -> Option { let KclValue::Sketch { value } = arg else { diff --git a/rust/kcl-lib/src/std/sketch.rs b/rust/kcl-lib/src/std/sketch.rs index 73892a8e1..1e084ed64 100644 --- a/rust/kcl-lib/src/std/sketch.rs +++ b/rust/kcl-lib/src/std/sketch.rs @@ -32,6 +32,7 @@ use crate::{ }, }; +use super::utils::untype_array; use super::utils::untype_point; /// A tag for a face. @@ -2850,11 +2851,9 @@ pub(crate) async fn inner_hyperbolic( pub async fn parabolic_point(exec_state: &mut ExecState, args: Args) -> Result { let x: Option = args.get_kw_arg_opt_typed("x", &RuntimeType::length(), exec_state)?; let y: Option = args.get_kw_arg_opt_typed("y", &RuntimeType::length(), exec_state)?; - let coefficient_a: TyF64 = args.get_kw_arg_typed("a", &RuntimeType::count(), exec_state)?; - let coefficient_b: TyF64 = args.get_kw_arg_typed("b", &RuntimeType::count(), exec_state)?; - let coefficient_c: TyF64 = args.get_kw_arg_typed("c", &RuntimeType::count(), exec_state)?; + let coefficients: [TyF64; 3] = args.get_kw_arg_typed("coefficients", &RuntimeType::any_array(), exec_state)?; - let parabolic_point = inner_parabolic_point(x, y, coefficient_a, coefficient_b, coefficient_c, &args).await?; + let parabolic_point = inner_parabolic_point(x, y, &coefficients, &args).await?; args.make_kcl_val_from_point(parabolic_point, exec_state.length_unit().into()) } @@ -2869,9 +2868,7 @@ pub async fn parabolic_point(exec_state: &mut ExecState, args: Args) -> Result Result, y: Option, - a: TyF64, - b: TyF64, - c: TyF64, + coefficients: &[TyF64; 3], args: &Args, ) -> Result<[f64; 2], KclError> { - let a = a.n; - let b = b.n; - let c = c.n; + let a = coefficients[0].n; + let b = coefficients[1].n; + let c = coefficients[2].n; if let Some(x) = x { Ok((x.n, a * x.n.powf(2.0) + b * x.n + c).into()) } else if let Some(y) = y { @@ -2906,25 +2901,13 @@ pub async fn parabolic(exec_state: &mut ExecState, args: Args) -> Result = args.get_kw_arg_opt_typed("a", &RuntimeType::count(), exec_state)?; - let coefficient_b: Option = args.get_kw_arg_opt_typed("b", &RuntimeType::count(), exec_state)?; - let coefficient_c: Option = args.get_kw_arg_opt_typed("c", &RuntimeType::count(), exec_state)?; + let coefficients: Option<[TyF64; 3]> = + args.get_kw_arg_opt_typed("coefficients", &RuntimeType::any_array(), exec_state)?; let interior: Option<[TyF64; 2]> = args.get_kw_arg_opt_typed("interior", &RuntimeType::point2d(), exec_state)?; let end: [TyF64; 2] = args.get_kw_arg_typed("end", &RuntimeType::point2d(), exec_state)?; let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; - let new_sketch = inner_parabolic( - sketch, - coefficient_a, - coefficient_b, - coefficient_c, - interior, - end, - tag, - exec_state, - args, - ) - .await?; + let new_sketch = inner_parabolic(sketch, coefficients, interior, end, tag, exec_state, args).await?; Ok(KclValue::Sketch { value: Box::new(new_sketch), }) @@ -2951,10 +2934,8 @@ fn parabolic_tangent(point: Point2d, a: f64, b: f64) -> [f64; 2] { unlabeled_first = true, args = { sketch = { docs = "Which sketch should this path be added to?" }, - a = { docs = "The coefficient a of the parabolic equation y = ax^2 + bx + c." }, - b = { docs = "The coefficient b of the parabolic equation y = ax^2 + bx + c." }, - c = { docs = "The coefficient c of the parabolic equation y = ax^2 + bx + c." }, - interior = { docs = "Any point between the arc's start and end?" }, + coefficients = { docs = "The coefficienta [a, b, c] of the parabolic equation y = ax^2 + bx + c. Incompatible with `interior`." }, + interior = { docs = "Any point between the arc's start and end?. Incompatible with `coefficients." }, end = { docs = "Where should this arc end?" }, tag = { docs = "Create a new tag which refers to this line"}, }, @@ -2962,9 +2943,7 @@ fn parabolic_tangent(point: Point2d, a: f64, b: f64) -> [f64; 2] { }] pub(crate) async fn inner_parabolic( sketch: Sketch, - a: Option, - b: Option, - c: Option, + coefficients: Option<[TyF64; 3]>, interior: Option<[TyF64; 2]>, end: [TyF64; 2], tag: Option, @@ -2974,9 +2953,7 @@ pub(crate) async fn inner_parabolic( let from = sketch.current_pen_position()?; let id = exec_state.next_uuid(); - if (a.is_some() && b.is_some() && c.is_some() && interior.is_some()) - || (a.is_none() && b.is_none() && c.is_none() && interior.is_none()) - { + if (coefficients.is_some() && interior.is_some()) || (coefficients.is_none() && interior.is_none()) { return Err(KclError::Type(KclErrorDetails::new( "Invalid combination of arguments. Either provide (a, b, c) or (interior)".to_owned(), vec![args.source_range], @@ -2993,9 +2970,7 @@ pub(crate) async fn inner_parabolic( inner_parabolic_point( Some(TyF64::count(0.5 * (from.x + end[0]))), None, - a.clone().unwrap(), - b.clone().unwrap(), - c.clone().unwrap(), + coefficients.as_ref().unwrap(), &args, ) .await?, @@ -3008,26 +2983,25 @@ pub(crate) async fn inner_parabolic( units: from.units, }; - let (a, b, _c) = match (a, b, c) { - (Some(a), Some(b), Some(c)) => (a.n, b.n, c.n), - _ => { - // Any three points is enough to uniquely define a parabola - let denom = (from.x - interior[0]) * (from.x - end_point.x) * (interior[0] - end_point.x); - let a = (end_point.x * (interior[1] - from.y) - + interior[0] * (from.y - end_point.y) - + from.x * (end_point.y - interior[1])) - / denom; - let b = (end_point.x.powf(2.0) * (from.y - interior[1]) - + interior[0].powf(2.0) * (end_point.y - from.y) - + from.x.powf(2.0) * (interior[1] - end_point.y)) - / denom; - let c = (interior[0] * end_point.x * (interior[0] - end_point.x) * from.y - + end_point.x * from.x * (end_point.x - from.x) * interior[1] - + from.x * interior[0] * (from.x - interior[0]) * end_point.y) - / denom; + let (a, b, _c) = if let Some([a, b, c]) = coefficients { + (a.n, b.n, c.n) + } else { + // Any three points is enough to uniquely define a parabola + let denom = (from.x - interior[0]) * (from.x - end_point.x) * (interior[0] - end_point.x); + let a = (end_point.x * (interior[1] - from.y) + + interior[0] * (from.y - end_point.y) + + from.x * (end_point.y - interior[1])) + / denom; + let b = (end_point.x.powf(2.0) * (from.y - interior[1]) + + interior[0].powf(2.0) * (end_point.y - from.y) + + from.x.powf(2.0) * (interior[1] - end_point.y)) + / denom; + let c = (interior[0] * end_point.x * (interior[0] - end_point.x) * from.y + + end_point.x * from.x * (end_point.x - from.x) * interior[1] + + from.x * interior[0] * (from.x - interior[0]) * end_point.y) + / denom; - (a, b, c) - } + (a, b, c) }; let start_tangent = parabolic_tangent(from, a, b); @@ -3071,6 +3045,16 @@ pub(crate) async fn inner_parabolic( Ok(new_sketch) } +fn conic_tangent(coefficients: [f64; 6], point: [f64; 2]) -> [f64; 2] { + let [a, b, c, d, e, _] = coefficients; + + ( + c * point[0] + 2.0 * b * point[1] + e, + -(2.0 * a * point[0] + c * point[1] + d), + ) + .into() +} + /// Draw a conic section pub async fn conic(exec_state: &mut ExecState, args: Args) -> Result { let sketch = @@ -3078,12 +3062,26 @@ pub async fn conic(exec_state: &mut ExecState, args: Args) -> Result = args.get_kw_arg_opt_typed("startTangent", &RuntimeType::point2d(), exec_state)?; - let end_tangent: [TyF64; 2] = args.get_kw_arg_typed("endTangent", &RuntimeType::point2d(), exec_state)?; + let end_tangent: Option<[TyF64; 2]> = + args.get_kw_arg_opt_typed("endTangent", &RuntimeType::point2d(), exec_state)?; let end: [TyF64; 2] = args.get_kw_arg_typed("end", &RuntimeType::point2d(), exec_state)?; let interior: [TyF64; 2] = args.get_kw_arg_typed("interior", &RuntimeType::point2d(), exec_state)?; + let coefficients: Option<[TyF64; 6]> = + args.get_kw_arg_opt_typed("coefficients", &RuntimeType::any_array(), exec_state)?; let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; - let new_sketch = inner_conic(sketch, start_tangent, end, end_tangent, interior, tag, exec_state, args).await?; + let new_sketch = inner_conic( + sketch, + start_tangent, + end, + end_tangent, + interior, + coefficients, + tag, + exec_state, + args, + ) + .await?; Ok(KclValue::Sketch { value: Box::new(new_sketch), }) @@ -3108,7 +3106,8 @@ pub async fn conic(exec_state: &mut ExecState, args: Args) -> Result, end: [TyF64; 2], - end_tangent: [TyF64; 2], + end_tangent: Option<[TyF64; 2]>, interior: [TyF64; 2], + coefficients: Option<[TyF64; 6]>, tag: Option, exec_state: &mut ExecState, args: Args, ) -> Result { let from: Point2d = sketch.current_pen_position()?; let id = exec_state.next_uuid(); - let (end_tangent, _) = untype_point(end_tangent); - let (end, _) = untype_point(end); + + if (coefficients.is_some() && (start_tangent.is_some() || end_tangent.is_some())) + || (coefficients.is_none() && (start_tangent.is_none() && end_tangent.is_none())) + { + return Err(KclError::Type(KclErrorDetails::new( + "Invalid combination of arguments. Either provide coefficients or interior".to_owned(), + vec![args.source_range], + ))); + } + + let (end, _) = untype_array(end); let (interior, _) = untype_point(interior); - let (start_tangent, _) = if let Some(start_tangent) = start_tangent { - untype_point(start_tangent) + let (start_tangent, end_tangent) = if let Some(coeffs) = coefficients { + let (coeffs, _) = untype_array(coeffs); + (conic_tangent(coeffs, [from.x, from.y]), conic_tangent(coeffs, end)) } else { - let previous_point = sketch - .get_tangential_info_from_paths() - .tan_previous_point(from.ignore_units()); - let from = from.ignore_units(); - ( - [from[0] - previous_point[0], from[1] - previous_point[1]], - NumericType::Any, - ) + let start = if let Some(start_tangent) = start_tangent { + let (start, _) = untype_point(start_tangent); + start + } else { + let previous_point = sketch + .get_tangential_info_from_paths() + .tan_previous_point(from.ignore_units()); + let from = from.ignore_units(); + [from[0] - previous_point[0], from[1] - previous_point[1]] + }; + + let (end_tan, _) = untype_point(end_tangent.unwrap()); + (start, end_tan) }; args.batch_modeling_cmd( diff --git a/rust/kcl-lib/src/std/utils.rs b/rust/kcl-lib/src/std/utils.rs index 3e45ff528..46c4b3e73 100644 --- a/rust/kcl-lib/src/std/utils.rs +++ b/rust/kcl-lib/src/std/utils.rs @@ -10,6 +10,15 @@ pub(crate) fn untype_point(p: [TyF64; 2]) -> ([f64; 2], NumericType) { ([x, y], ty) } +pub(crate) fn untype_array(p: [TyF64; N]) -> ([f64; N], NumericType) { + let (vec, ty) = NumericType::combine_eq_array(&p); + ( + vec.try_into() + .unwrap_or_else(|v: Vec| panic!("Expected a Vec of length {} but it was {}", N, v.len())), + ty, + ) +} + pub(crate) fn point_to_mm(p: [TyF64; 2]) -> [f64; 2] { [p[0].to_mm(), p[1].to_mm()] }