kcl_lib/std/
args.rs

1use std::num::NonZeroU32;
2
3use anyhow::Result;
4use kittycad_modeling_cmds::units::{UnitAngle, UnitLength};
5use serde::Serialize;
6
7use super::fillet::EdgeReference;
8pub use crate::execution::fn_call::Args;
9use crate::{
10    CompilationError, MetaSettings, ModuleId, SourceRange,
11    errors::{KclError, KclErrorDetails},
12    execution::{
13        ExecState, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Plane, PlaneInfo, Sketch, SketchSurface,
14        Solid, TagIdentifier, annotations,
15        kcl_value::FunctionSource,
16        types::{NumericSuffixTypeConvertError, NumericType, PrimitiveType, RuntimeType, UnitType},
17    },
18    front::Number,
19    parsing::ast::types::TagNode,
20    std::{
21        shapes::{PolygonType, SketchOrSurface},
22        sketch::FaceTag,
23        sweep::SweepPath,
24    },
25};
26
27const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
28    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
29
30#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
31#[ts(export)]
32#[serde(rename_all = "camelCase")]
33pub struct TyF64 {
34    pub n: f64,
35    pub ty: NumericType,
36}
37
38impl TyF64 {
39    pub const fn new(n: f64, ty: NumericType) -> Self {
40        Self { n, ty }
41    }
42
43    pub fn from_number(n: Number, settings: &MetaSettings) -> Self {
44        Self {
45            n: n.value,
46            ty: NumericType::from_parsed(n.units, settings),
47        }
48    }
49
50    pub fn to_mm(&self) -> f64 {
51        self.to_length_units(UnitLength::Millimeters)
52    }
53
54    pub fn to_length_units(&self, units: UnitLength) -> f64 {
55        let len = match &self.ty {
56            NumericType::Default { len, .. } => *len,
57            NumericType::Known(UnitType::Length(len)) => *len,
58            t => unreachable!("expected length, found {t:?}"),
59        };
60
61        crate::execution::types::adjust_length(len, self.n, units).0
62    }
63
64    pub fn to_degrees(&self, exec_state: &mut ExecState, source_range: SourceRange) -> f64 {
65        let angle = match self.ty {
66            NumericType::Default { angle, .. } => {
67                if self.n != 0.0 {
68                    exec_state.warn(
69                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
70                        annotations::WARN_ANGLE_UNITS,
71                    );
72                }
73                angle
74            }
75            NumericType::Known(UnitType::Angle(angle)) => angle,
76            _ => unreachable!(),
77        };
78
79        crate::execution::types::adjust_angle(angle, self.n, UnitAngle::Degrees).0
80    }
81
82    pub fn to_radians(&self, exec_state: &mut ExecState, source_range: SourceRange) -> f64 {
83        let angle = match self.ty {
84            NumericType::Default { angle, .. } => {
85                if self.n != 0.0 {
86                    exec_state.warn(
87                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
88                        annotations::WARN_ANGLE_UNITS,
89                    );
90                }
91                angle
92            }
93            NumericType::Known(UnitType::Angle(angle)) => angle,
94            _ => unreachable!(),
95        };
96
97        crate::execution::types::adjust_angle(angle, self.n, UnitAngle::Radians).0
98    }
99    pub fn count(n: f64) -> Self {
100        Self {
101            n,
102            ty: NumericType::count(),
103        }
104    }
105
106    pub fn map_value(mut self, n: f64) -> Self {
107        self.n = n;
108        self
109    }
110
111    // This can't be a TryFrom impl because `Point2d` is defined in another
112    // crate.
113    pub fn to_point2d(value: &[TyF64; 2]) -> Result<crate::front::Point2d<Number>, NumericSuffixTypeConvertError> {
114        Ok(crate::front::Point2d {
115            x: Number {
116                value: value[0].n,
117                units: value[0].ty.try_into()?,
118            },
119            y: Number {
120                value: value[1].n,
121                units: value[1].ty.try_into()?,
122            },
123        })
124    }
125}
126
127impl Args {
128    pub(crate) fn get_kw_arg_opt<T>(
129        &self,
130        label: &str,
131        ty: &RuntimeType,
132        exec_state: &mut ExecState,
133    ) -> Result<Option<T>, KclError>
134    where
135        T: for<'a> FromKclValue<'a>,
136    {
137        match self.labeled.get(label) {
138            None => return Ok(None),
139            Some(a) => {
140                if let KclValue::KclNone { .. } = &a.value {
141                    return Ok(None);
142                }
143            }
144        }
145
146        self.get_kw_arg(label, ty, exec_state).map(Some)
147    }
148
149    pub(crate) fn get_kw_arg<T>(&self, label: &str, ty: &RuntimeType, exec_state: &mut ExecState) -> Result<T, KclError>
150    where
151        T: for<'a> FromKclValue<'a>,
152    {
153        let Some(arg) = self.labeled.get(label) else {
154            return Err(KclError::new_semantic(KclErrorDetails::new(
155                if let Some(ref fname) = self.fn_name {
156                    format!("The `{fname}` function requires a keyword argument `{label}`")
157                } else {
158                    format!("This function requires a keyword argument `{label}`")
159                },
160                vec![self.source_range],
161            )));
162        };
163
164        let arg = arg.value.coerce(ty, true, exec_state).map_err(|_| {
165            let actual_type = arg.value.principal_type();
166            let actual_type_name = actual_type
167                .as_ref()
168                .map(|t| t.to_string())
169                .unwrap_or_else(|| arg.value.human_friendly_type());
170            let msg_base = if let Some(ref fname) = self.fn_name {
171                format!("The `{fname}` function expected its `{label}` argument to be {} but it's actually of type {actual_type_name}", ty.human_friendly_type())
172            } else {
173                format!("This function expected its `{label}` argument to be {} but it's actually of type {actual_type_name}", ty.human_friendly_type())
174            };
175            let suggestion = match (ty, actual_type) {
176                (RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
177                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
178                }
179                (RuntimeType::Array(t, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
180                    if **t == RuntimeType::Primitive(PrimitiveType::Solid) =>
181                {
182                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
183                }
184                _ => None,
185            };
186            let mut message = match suggestion {
187                None => msg_base,
188                Some(sugg) => format!("{msg_base}. {sugg}"),
189            };
190            if message.contains("one or more Solids or ImportedGeometry but it's actually of type Sketch") {
191                message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
192            }
193            KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
194        })?;
195
196        T::from_kcl_val(&arg).ok_or_else(|| {
197            KclError::new_internal(KclErrorDetails::new(
198                format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
199                vec![self.source_range],
200           ))
201        })
202    }
203
204    /// Get a labelled keyword arg, check it's an array, and return all items in the array
205    /// plus their source range.
206    pub(crate) fn kw_arg_edge_array_and_source(
207        &self,
208        label: &str,
209    ) -> Result<Vec<(EdgeReference, SourceRange)>, KclError> {
210        let Some(arg) = self.labeled.get(label) else {
211            let err = KclError::new_semantic(KclErrorDetails::new(
212                if let Some(ref fname) = self.fn_name {
213                    format!("The `{fname}` function requires a keyword argument '{label}'")
214                } else {
215                    format!("This function requires a keyword argument '{label}'")
216                },
217                vec![self.source_range],
218            ));
219            return Err(err);
220        };
221        arg.value
222            .clone()
223            .into_array()
224            .iter()
225            .map(|item| {
226                let source = SourceRange::from(item);
227                let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
228                    KclError::new_semantic(KclErrorDetails::new(
229                        format!("Expected an Edge but found {}", arg.value.human_friendly_type()),
230                        arg.source_ranges(),
231                    ))
232                })?;
233                Ok((val, source))
234            })
235            .collect::<Result<Vec<_>, _>>()
236    }
237
238    pub(crate) fn get_unlabeled_kw_arg_array_and_type(
239        &self,
240        label: &str,
241        exec_state: &mut ExecState,
242    ) -> Result<(Vec<KclValue>, RuntimeType), KclError> {
243        let value = self.get_unlabeled_kw_arg(label, &RuntimeType::any_array(), exec_state)?;
244        Ok(match value {
245            KclValue::HomArray { value, ty } => (value, ty),
246            KclValue::Tuple { value, .. } => (value, RuntimeType::any()),
247            val => (vec![val], RuntimeType::any()),
248        })
249    }
250
251    /// Get the unlabeled keyword argument. If not set, returns Err. If it
252    /// can't be converted to the given type, returns Err.
253    pub(crate) fn get_unlabeled_kw_arg<T>(
254        &self,
255        label: &str,
256        ty: &RuntimeType,
257        exec_state: &mut ExecState,
258    ) -> Result<T, KclError>
259    where
260        T: for<'a> FromKclValue<'a>,
261    {
262        let arg = self
263            .unlabeled_kw_arg_unconverted()
264            .ok_or(KclError::new_semantic(KclErrorDetails::new(
265                if let Some(ref fname) = self.fn_name {
266                    format!(
267                        "The `{fname}` function requires a value for the special unlabeled first parameter, '{label}'"
268                    )
269                } else {
270                    format!("This function requires a value for the special unlabeled first parameter, '{label}'")
271                },
272                vec![self.source_range],
273            )))?;
274
275        let arg = arg.value.coerce(ty, true, exec_state).map_err(|_| {
276            let actual_type = arg.value.principal_type();
277            let actual_type_name = actual_type
278                .as_ref()
279                .map(|t| t.to_string())
280                .unwrap_or_else(|| arg.value.human_friendly_type());
281            let msg_base = if let Some(ref fname) = self.fn_name {
282                format!(
283                    "The `{fname}` function expected the input argument to be {} but it's actually of type {actual_type_name}",
284                    ty.human_friendly_type(),
285                )
286            } else {
287                format!(
288                    "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
289                    ty.human_friendly_type(),
290                )
291            };
292            let suggestion = match (ty, actual_type) {
293                (RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
294                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
295                }
296                (RuntimeType::Array(ty, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
297                    if **ty == RuntimeType::Primitive(PrimitiveType::Solid) =>
298                {
299                    Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
300                }
301                _ => None,
302            };
303            let mut message = match suggestion {
304                None => msg_base,
305                Some(sugg) => format!("{msg_base}. {sugg}"),
306            };
307
308            if message.contains("one or more Solids or ImportedGeometry but it's actually of type Sketch") {
309                message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
310            }
311            KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
312        })?;
313
314        T::from_kcl_val(&arg).ok_or_else(|| {
315            KclError::new_internal(KclErrorDetails::new(
316                format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
317                vec![self.source_range],
318           ))
319        })
320    }
321
322    // TODO: Move this to the modeling module.
323    fn get_tag_info_from_memory<'a, 'e>(
324        &'a self,
325        exec_state: &'e mut ExecState,
326        tag: &'a TagIdentifier,
327    ) -> Result<&'e crate::execution::TagEngineInfo, KclError> {
328        if let (epoch, KclValue::TagIdentifier(t)) =
329            exec_state.stack().get_from_call_stack(&tag.value, self.source_range)?
330        {
331            let info = t.get_info(epoch).ok_or_else(|| {
332                KclError::new_type(KclErrorDetails::new(
333                    format!("Tag `{}` does not have engine info", tag.value),
334                    vec![self.source_range],
335                ))
336            })?;
337            Ok(info)
338        } else {
339            Err(KclError::new_type(KclErrorDetails::new(
340                format!("Tag `{}` does not exist", tag.value),
341                vec![self.source_range],
342            )))
343        }
344    }
345
346    // TODO: Move this to the modeling module.
347    pub(crate) fn get_tag_engine_info<'a, 'e>(
348        &'a self,
349        exec_state: &'e mut ExecState,
350        tag: &'a TagIdentifier,
351    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
352    where
353        'e: 'a,
354    {
355        if let Some(info) = tag.get_cur_info() {
356            return Ok(info);
357        }
358
359        self.get_tag_info_from_memory(exec_state, tag)
360    }
361
362    // TODO: Move this to the modeling module.
363    fn get_tag_engine_info_check_surface<'a, 'e>(
364        &'a self,
365        exec_state: &'e mut ExecState,
366        tag: &'a TagIdentifier,
367    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
368    where
369        'e: 'a,
370    {
371        if let Some(info) = tag.get_cur_info()
372            && info.surface.is_some()
373        {
374            return Ok(info);
375        }
376
377        self.get_tag_info_from_memory(exec_state, tag)
378    }
379
380    pub(crate) fn make_kcl_val_from_point(&self, p: [f64; 2], ty: NumericType) -> Result<KclValue, KclError> {
381        let meta = Metadata {
382            source_range: self.source_range,
383        };
384        let x = KclValue::Number {
385            value: p[0],
386            meta: vec![meta],
387            ty,
388        };
389        let y = KclValue::Number {
390            value: p[1],
391            meta: vec![meta],
392            ty,
393        };
394        let ty = RuntimeType::Primitive(PrimitiveType::Number(ty));
395
396        Ok(KclValue::HomArray { value: vec![x, y], ty })
397    }
398
399    pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
400        KclValue::from_number_with_type(
401            f.n,
402            f.ty,
403            vec![Metadata {
404                source_range: self.source_range,
405            }],
406        )
407    }
408
409    // TODO: Move this to the modeling module.
410    pub(crate) async fn get_adjacent_face_to_tag(
411        &self,
412        exec_state: &mut ExecState,
413        tag: &TagIdentifier,
414        must_be_planar: bool,
415    ) -> Result<uuid::Uuid, KclError> {
416        if tag.value.is_empty() {
417            return Err(KclError::new_type(KclErrorDetails::new(
418                "Expected a non-empty tag for the face".to_string(),
419                vec![self.source_range],
420            )));
421        }
422
423        let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?;
424
425        let surface = engine_info.surface.as_ref().ok_or_else(|| {
426            KclError::new_type(KclErrorDetails::new(
427                format!("Tag `{}` does not have a surface", tag.value),
428                vec![self.source_range],
429            ))
430        })?;
431
432        if let Some(face_from_surface) = match surface {
433            ExtrudeSurface::ExtrudePlane(extrude_plane) => {
434                if let Some(plane_tag) = &extrude_plane.tag {
435                    if plane_tag.name == tag.value {
436                        Some(Ok(extrude_plane.face_id))
437                    } else {
438                        None
439                    }
440                } else {
441                    None
442                }
443            }
444            // The must be planar check must be called before the arc check.
445            ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
446                format!("Tag `{}` is a non-planar surface", tag.value),
447                vec![self.source_range],
448            )))),
449            ExtrudeSurface::ExtrudeArc(extrude_arc) => {
450                if let Some(arc_tag) = &extrude_arc.tag {
451                    if arc_tag.name == tag.value {
452                        Some(Ok(extrude_arc.face_id))
453                    } else {
454                        None
455                    }
456                } else {
457                    None
458                }
459            }
460            ExtrudeSurface::Chamfer(chamfer) => {
461                if let Some(chamfer_tag) = &chamfer.tag {
462                    if chamfer_tag.name == tag.value {
463                        Some(Ok(chamfer.face_id))
464                    } else {
465                        None
466                    }
467                } else {
468                    None
469                }
470            }
471            // The must be planar check must be called before the fillet check.
472            ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
473                format!("Tag `{}` is a non-planar surface", tag.value),
474                vec![self.source_range],
475            )))),
476            ExtrudeSurface::Fillet(fillet) => {
477                if let Some(fillet_tag) = &fillet.tag {
478                    if fillet_tag.name == tag.value {
479                        Some(Ok(fillet.face_id))
480                    } else {
481                        None
482                    }
483                } else {
484                    None
485                }
486            }
487        } {
488            return face_from_surface;
489        }
490
491        // If we still haven't found the face, return an error.
492        Err(KclError::new_type(KclErrorDetails::new(
493            format!("Expected a face with the tag `{}`", tag.value),
494            vec![self.source_range],
495        )))
496    }
497}
498
499/// Types which impl this trait can be extracted from a `KclValue`.
500pub trait FromKclValue<'a>: Sized {
501    /// Try to convert a KclValue into this type.
502    fn from_kcl_val(arg: &'a KclValue) -> Option<Self>;
503}
504
505impl<'a> FromKclValue<'a> for TagNode {
506    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
507        arg.get_tag_declarator().ok()
508    }
509}
510
511impl<'a> FromKclValue<'a> for TagIdentifier {
512    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
513        arg.get_tag_identifier().ok()
514    }
515}
516
517impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
518    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
519        let tags = arg
520            .clone()
521            .into_array()
522            .iter()
523            .map(|v| v.get_tag_identifier().unwrap())
524            .collect();
525        Some(tags)
526    }
527}
528
529impl<'a> FromKclValue<'a> for Vec<KclValue> {
530    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
531        Some(arg.clone().into_array())
532    }
533}
534
535impl<'a> FromKclValue<'a> for KclValue {
536    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
537        Some(arg.clone())
538    }
539}
540
541macro_rules! let_field_of {
542    // Optional field
543    ($obj:ident, $field:ident?) => {
544        let $field = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val);
545    };
546    // Optional field but with a different string used as the key
547    ($obj:ident, $field:ident? $key:literal) => {
548        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val);
549    };
550    // Mandatory field, but with a different string used as the key.
551    ($obj:ident, $field:ident $key:literal) => {
552        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val)?;
553    };
554    // Mandatory field, optionally with a type annotation
555    ($obj:ident, $field:ident $(, $annotation:ty)?) => {
556        let $field $(: $annotation)? = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val)?;
557    };
558}
559
560impl<'a> FromKclValue<'a> for crate::execution::Plane {
561    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
562        arg.as_plane().cloned()
563    }
564}
565
566impl<'a> FromKclValue<'a> for crate::execution::PlaneType {
567    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
568        let plane_type = match arg.as_str()? {
569            "XY" | "xy" => Self::XY,
570            "XZ" | "xz" => Self::XZ,
571            "YZ" | "yz" => Self::YZ,
572            "Custom" => Self::Custom,
573            _ => return None,
574        };
575        Some(plane_type)
576    }
577}
578
579impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::units::UnitLength {
580    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
581        let s = arg.as_str()?;
582        s.parse().ok()
583    }
584}
585
586impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::System {
587    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
588        let obj = arg.as_object()?;
589        let_field_of!(obj, forward);
590        let_field_of!(obj, up);
591        Some(Self { forward, up })
592    }
593}
594
595impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::AxisDirectionPair {
596    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
597        let obj = arg.as_object()?;
598        let_field_of!(obj, axis);
599        let_field_of!(obj, direction);
600        Some(Self { axis, direction })
601    }
602}
603
604impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Axis {
605    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
606        let s = arg.as_str()?;
607        match s {
608            "y" => Some(Self::Y),
609            "z" => Some(Self::Z),
610            _ => None,
611        }
612    }
613}
614
615impl<'a> FromKclValue<'a> for PolygonType {
616    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
617        let s = arg.as_str()?;
618        match s {
619            "inscribed" => Some(Self::Inscribed),
620            _ => Some(Self::Circumscribed),
621        }
622    }
623}
624
625impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
626    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
627        let s = arg.as_str()?;
628        match s {
629            "positive" => Some(Self::Positive),
630            "negative" => Some(Self::Negative),
631            _ => None,
632        }
633    }
634}
635
636impl<'a> FromKclValue<'a> for crate::execution::Geometry {
637    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
638        match arg {
639            KclValue::Sketch { value } => Some(Self::Sketch(*value.to_owned())),
640            KclValue::Solid { value } => Some(Self::Solid(*value.to_owned())),
641            _ => None,
642        }
643    }
644}
645
646impl<'a> FromKclValue<'a> for crate::execution::GeometryWithImportedGeometry {
647    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
648        match arg {
649            KclValue::Sketch { value } => Some(Self::Sketch(*value.to_owned())),
650            KclValue::Solid { value } => Some(Self::Solid(*value.to_owned())),
651            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
652            _ => None,
653        }
654    }
655}
656
657impl<'a> FromKclValue<'a> for FaceTag {
658    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
659        let case1 = || match arg.as_str() {
660            Some("start" | "START") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::Start)),
661            Some("end" | "END") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::End)),
662            _ => None,
663        };
664        let case2 = || {
665            let tag = TagIdentifier::from_kcl_val(arg)?;
666            Some(Self::Tag(Box::new(tag)))
667        };
668        case1().or_else(case2)
669    }
670}
671
672impl<'a> FromKclValue<'a> for super::sketch::TangentialArcData {
673    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
674        let obj = arg.as_object()?;
675        let_field_of!(obj, radius);
676        let_field_of!(obj, offset);
677        Some(Self::RadiusAndOffset { radius, offset })
678    }
679}
680
681impl<'a> FromKclValue<'a> for crate::execution::Point3d {
682    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
683        // Case 1: object with x/y/z fields
684        if let Some(obj) = arg.as_object() {
685            let_field_of!(obj, x, TyF64);
686            let_field_of!(obj, y, TyF64);
687            let_field_of!(obj, z, TyF64);
688            // TODO here and below we could use coercing combination.
689            let (a, ty) = NumericType::combine_eq_array(&[x, y, z]);
690            return Some(Self {
691                x: a[0],
692                y: a[1],
693                z: a[2],
694                units: ty.as_length(),
695            });
696        }
697        // Case 2: Array of 3 numbers.
698        let [x, y, z]: [TyF64; 3] = FromKclValue::from_kcl_val(arg)?;
699        let (a, ty) = NumericType::combine_eq_array(&[x, y, z]);
700        Some(Self {
701            x: a[0],
702            y: a[1],
703            z: a[2],
704            units: ty.as_length(),
705        })
706    }
707}
708
709impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
710    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
711        // Case 0: actual plane
712        if let KclValue::Plane { value } = arg {
713            return Some(Self::Plane(PlaneInfo {
714                origin: value.info.origin,
715                x_axis: value.info.x_axis,
716                y_axis: value.info.y_axis,
717                z_axis: value.info.z_axis,
718            }));
719        }
720        // Case 1: predefined plane
721        if let Some(s) = arg.as_str() {
722            return match s {
723                "XY" | "xy" => Some(Self::XY),
724                "-XY" | "-xy" => Some(Self::NegXY),
725                "XZ" | "xz" => Some(Self::XZ),
726                "-XZ" | "-xz" => Some(Self::NegXZ),
727                "YZ" | "yz" => Some(Self::YZ),
728                "-YZ" | "-yz" => Some(Self::NegYZ),
729                _ => None,
730            };
731        }
732        // Case 2: custom plane
733        let obj = arg.as_object()?;
734        let_field_of!(obj, plane, &KclObjectFields);
735        let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
736        let x_axis: crate::execution::Point3d = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
737        let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
738        let z_axis = x_axis.axes_cross_product(&y_axis);
739        Some(Self::Plane(PlaneInfo {
740            origin,
741            x_axis,
742            y_axis,
743            z_axis,
744        }))
745    }
746}
747
748impl<'a> FromKclValue<'a> for crate::execution::ExtrudePlane {
749    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
750        let obj = arg.as_object()?;
751        let_field_of!(obj, face_id "faceId");
752        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
753        let_field_of!(obj, geo_meta "geoMeta");
754        Some(Self { face_id, tag, geo_meta })
755    }
756}
757
758impl<'a> FromKclValue<'a> for crate::execution::ExtrudeArc {
759    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
760        let obj = arg.as_object()?;
761        let_field_of!(obj, face_id "faceId");
762        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
763        let_field_of!(obj, geo_meta "geoMeta");
764        Some(Self { face_id, tag, geo_meta })
765    }
766}
767
768impl<'a> FromKclValue<'a> for crate::execution::GeoMeta {
769    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
770        let obj = arg.as_object()?;
771        let_field_of!(obj, id);
772        let_field_of!(obj, source_range "sourceRange");
773        Some(Self {
774            id,
775            metadata: Metadata { source_range },
776        })
777    }
778}
779
780impl<'a> FromKclValue<'a> for crate::execution::ChamferSurface {
781    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
782        let obj = arg.as_object()?;
783        let_field_of!(obj, face_id "faceId");
784        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
785        let_field_of!(obj, geo_meta "geoMeta");
786        Some(Self { face_id, tag, geo_meta })
787    }
788}
789
790impl<'a> FromKclValue<'a> for crate::execution::FilletSurface {
791    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
792        let obj = arg.as_object()?;
793        let_field_of!(obj, face_id "faceId");
794        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
795        let_field_of!(obj, geo_meta "geoMeta");
796        Some(Self { face_id, tag, geo_meta })
797    }
798}
799
800impl<'a> FromKclValue<'a> for ExtrudeSurface {
801    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
802        let case1 = crate::execution::ExtrudePlane::from_kcl_val;
803        let case2 = crate::execution::ExtrudeArc::from_kcl_val;
804        let case3 = crate::execution::ChamferSurface::from_kcl_val;
805        let case4 = crate::execution::FilletSurface::from_kcl_val;
806        case1(arg)
807            .map(Self::ExtrudePlane)
808            .or_else(|| case2(arg).map(Self::ExtrudeArc))
809            .or_else(|| case3(arg).map(Self::Chamfer))
810            .or_else(|| case4(arg).map(Self::Fillet))
811    }
812}
813
814impl<'a> FromKclValue<'a> for crate::execution::EdgeCut {
815    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
816        let obj = arg.as_object()?;
817        let_field_of!(obj, typ "type");
818        let tag = Box::new(obj.get("tag").and_then(FromKclValue::from_kcl_val));
819        let_field_of!(obj, edge_id "edgeId");
820        let_field_of!(obj, id);
821        match typ {
822            "fillet" => {
823                let_field_of!(obj, radius);
824                Some(Self::Fillet {
825                    edge_id,
826                    tag,
827                    id,
828                    radius,
829                })
830            }
831            "chamfer" => {
832                let_field_of!(obj, length);
833                Some(Self::Chamfer {
834                    id,
835                    length,
836                    edge_id,
837                    tag,
838                })
839            }
840            _ => None,
841        }
842    }
843}
844
845macro_rules! impl_from_kcl_for_vec {
846    ($typ:path) => {
847        impl<'a> FromKclValue<'a> for Vec<$typ> {
848            fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
849                arg.clone()
850                    .into_array()
851                    .iter()
852                    .map(|value| FromKclValue::from_kcl_val(value))
853                    .collect::<Option<_>>()
854            }
855        }
856    };
857}
858
859impl_from_kcl_for_vec!(FaceTag);
860impl_from_kcl_for_vec!(crate::execution::EdgeCut);
861impl_from_kcl_for_vec!(crate::execution::Metadata);
862impl_from_kcl_for_vec!(super::fillet::EdgeReference);
863impl_from_kcl_for_vec!(ExtrudeSurface);
864impl_from_kcl_for_vec!(TyF64);
865impl_from_kcl_for_vec!(Solid);
866impl_from_kcl_for_vec!(Sketch);
867
868impl<'a> FromKclValue<'a> for SourceRange {
869    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
870        let value = match arg {
871            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
872            _ => {
873                return None;
874            }
875        };
876        if value.len() != 3 {
877            return None;
878        }
879        let v0 = value.first()?;
880        let v1 = value.get(1)?;
881        let v2 = value.get(2)?;
882        Some(SourceRange::new(
883            v0.as_usize()?,
884            v1.as_usize()?,
885            ModuleId::from_usize(v2.as_usize()?),
886        ))
887    }
888}
889
890impl<'a> FromKclValue<'a> for crate::execution::Metadata {
891    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
892        FromKclValue::from_kcl_val(arg).map(|sr| Self { source_range: sr })
893    }
894}
895
896impl<'a> FromKclValue<'a> for crate::execution::Solid {
897    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
898        arg.as_solid().cloned()
899    }
900}
901
902impl<'a> FromKclValue<'a> for crate::execution::SolidOrSketchOrImportedGeometry {
903    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
904        match arg {
905            KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
906            KclValue::Sketch { value } => Some(Self::SketchSet(vec![(**value).clone()])),
907            KclValue::HomArray { value, .. } => {
908                let mut solids = vec![];
909                let mut sketches = vec![];
910                for item in value {
911                    match item {
912                        KclValue::Solid { value } => solids.push((**value).clone()),
913                        KclValue::Sketch { value } => sketches.push((**value).clone()),
914                        _ => return None,
915                    }
916                }
917                if !solids.is_empty() {
918                    Some(Self::SolidSet(solids))
919                } else {
920                    Some(Self::SketchSet(sketches))
921                }
922            }
923            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
924            _ => None,
925        }
926    }
927}
928
929impl<'a> FromKclValue<'a> for crate::execution::SolidOrImportedGeometry {
930    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
931        match arg {
932            KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
933            KclValue::HomArray { value, .. } => {
934                let mut solids = vec![];
935                for item in value {
936                    match item {
937                        KclValue::Solid { value } => solids.push((**value).clone()),
938                        _ => return None,
939                    }
940                }
941                Some(Self::SolidSet(solids))
942            }
943            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
944            _ => None,
945        }
946    }
947}
948
949impl<'a> FromKclValue<'a> for super::sketch::SketchData {
950    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
951        // Order is critical since PlaneData is a subset of Plane.
952        let case1 = crate::execution::Plane::from_kcl_val;
953        let case2 = super::sketch::PlaneData::from_kcl_val;
954        let case3 = crate::execution::Solid::from_kcl_val;
955        let case4 = <Vec<Solid>>::from_kcl_val;
956        case1(arg)
957            .map(Box::new)
958            .map(Self::Plane)
959            .or_else(|| case2(arg).map(Self::PlaneOrientation))
960            .or_else(|| case3(arg).map(Box::new).map(Self::Solid))
961            .or_else(|| case4(arg).map(|v| Box::new(v[0].clone())).map(Self::Solid))
962    }
963}
964
965impl<'a> FromKclValue<'a> for super::fillet::EdgeReference {
966    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
967        let id = arg.as_uuid().map(Self::Uuid);
968        let tag = || TagIdentifier::from_kcl_val(arg).map(Box::new).map(Self::Tag);
969        id.or_else(tag)
970    }
971}
972
973impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrEdgeReference {
974    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
975        let case1 = |arg: &KclValue| {
976            let obj = arg.as_object()?;
977            let_field_of!(obj, direction);
978            let_field_of!(obj, origin);
979            Some(Self::Axis { direction, origin })
980        };
981        let case2 = super::fillet::EdgeReference::from_kcl_val;
982        case1(arg).or_else(|| case2(arg).map(Self::Edge))
983    }
984}
985
986impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrEdgeReference {
987    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
988        let case1 = |arg: &KclValue| {
989            let obj = arg.as_object()?;
990            let_field_of!(obj, direction);
991            let_field_of!(obj, origin);
992            Some(Self::Axis { direction, origin })
993        };
994        let case2 = super::fillet::EdgeReference::from_kcl_val;
995        case1(arg).or_else(|| case2(arg).map(Self::Edge))
996    }
997}
998
999impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrPoint2d {
1000    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1001        let case1 = |arg: &KclValue| {
1002            let obj = arg.as_object()?;
1003            let_field_of!(obj, direction);
1004            let_field_of!(obj, origin);
1005            Some(Self::Axis { direction, origin })
1006        };
1007        let case2 = <[TyF64; 2]>::from_kcl_val;
1008        case1(arg).or_else(|| case2(arg).map(Self::Point))
1009    }
1010}
1011
1012impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrPoint3d {
1013    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1014        let case1 = |arg: &KclValue| {
1015            let obj = arg.as_object()?;
1016            let_field_of!(obj, direction);
1017            let_field_of!(obj, origin);
1018            Some(Self::Axis { direction, origin })
1019        };
1020        let case2 = <[TyF64; 3]>::from_kcl_val;
1021        case1(arg).or_else(|| case2(arg).map(Self::Point))
1022    }
1023}
1024
1025impl<'a> FromKclValue<'a> for super::axis_or_reference::Point3dAxis3dOrGeometryReference {
1026    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1027        let case1 = |arg: &KclValue| {
1028            let obj = arg.as_object()?;
1029            let_field_of!(obj, direction);
1030            let_field_of!(obj, origin);
1031            Some(Self::Axis { direction, origin })
1032        };
1033        let case2 = <[TyF64; 3]>::from_kcl_val;
1034        let case3 = super::fillet::EdgeReference::from_kcl_val;
1035        let case4 = FaceTag::from_kcl_val;
1036        let case5 = Box::<Solid>::from_kcl_val;
1037        let case6 = TagIdentifier::from_kcl_val;
1038        let case7 = Box::<Plane>::from_kcl_val;
1039        let case8 = Box::<Sketch>::from_kcl_val;
1040
1041        case1(arg)
1042            .or_else(|| case2(arg).map(Self::Point))
1043            .or_else(|| case3(arg).map(Self::Edge))
1044            .or_else(|| case4(arg).map(Self::Face))
1045            .or_else(|| case5(arg).map(Self::Solid))
1046            .or_else(|| case6(arg).map(Self::TaggedEdgeOrFace))
1047            .or_else(|| case7(arg).map(Self::Plane))
1048            .or_else(|| case8(arg).map(Self::Sketch))
1049    }
1050}
1051
1052impl<'a> FromKclValue<'a> for i64 {
1053    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1054        match arg {
1055            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
1056            _ => None,
1057        }
1058    }
1059}
1060
1061impl<'a> FromKclValue<'a> for &'a str {
1062    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1063        let KclValue::String { value, meta: _ } = arg else {
1064            return None;
1065        };
1066        Some(value)
1067    }
1068}
1069
1070impl<'a> FromKclValue<'a> for &'a KclObjectFields {
1071    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1072        let KclValue::Object { value, .. } = arg else {
1073            return None;
1074        };
1075        Some(value)
1076    }
1077}
1078
1079impl<'a> FromKclValue<'a> for uuid::Uuid {
1080    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1081        let KclValue::Uuid { value, meta: _ } = arg else {
1082            return None;
1083        };
1084        Some(*value)
1085    }
1086}
1087
1088impl<'a> FromKclValue<'a> for u32 {
1089    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1090        match arg {
1091            KclValue::Number { value, .. } => crate::try_f64_to_u32(*value),
1092            _ => None,
1093        }
1094    }
1095}
1096
1097impl<'a> FromKclValue<'a> for NonZeroU32 {
1098    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1099        u32::from_kcl_val(arg).and_then(|x| x.try_into().ok())
1100    }
1101}
1102
1103impl<'a> FromKclValue<'a> for u64 {
1104    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1105        match arg {
1106            KclValue::Number { value, .. } => crate::try_f64_to_u64(*value),
1107            _ => None,
1108        }
1109    }
1110}
1111
1112impl<'a> FromKclValue<'a> for TyF64 {
1113    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1114        match arg {
1115            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
1116            _ => None,
1117        }
1118    }
1119}
1120
1121impl<'a> FromKclValue<'a> for [TyF64; 2] {
1122    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1123        match arg {
1124            KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
1125                if value.len() != 2 {
1126                    return None;
1127                }
1128                let v0 = value.first()?;
1129                let v1 = value.get(1)?;
1130                let array = [v0.as_ty_f64()?, v1.as_ty_f64()?];
1131                Some(array)
1132            }
1133            _ => None,
1134        }
1135    }
1136}
1137
1138impl<'a> FromKclValue<'a> for [TyF64; 3] {
1139    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1140        match arg {
1141            KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
1142                if value.len() != 3 {
1143                    return None;
1144                }
1145                let v0 = value.first()?;
1146                let v1 = value.get(1)?;
1147                let v2 = value.get(2)?;
1148                let array = [v0.as_ty_f64()?, v1.as_ty_f64()?, v2.as_ty_f64()?];
1149                Some(array)
1150            }
1151            _ => None,
1152        }
1153    }
1154}
1155
1156impl<'a> FromKclValue<'a> for [TyF64; 6] {
1157    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1158        match arg {
1159            KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
1160                if value.len() != 6 {
1161                    return None;
1162                }
1163                let v0 = value.first()?;
1164                let v1 = value.get(1)?;
1165                let v2 = value.get(2)?;
1166                let v3 = value.get(3)?;
1167                let v4 = value.get(4)?;
1168                let v5 = value.get(5)?;
1169                let array = [
1170                    v0.as_ty_f64()?,
1171                    v1.as_ty_f64()?,
1172                    v2.as_ty_f64()?,
1173                    v3.as_ty_f64()?,
1174                    v4.as_ty_f64()?,
1175                    v5.as_ty_f64()?,
1176                ];
1177                Some(array)
1178            }
1179            _ => None,
1180        }
1181    }
1182}
1183
1184impl<'a> FromKclValue<'a> for Sketch {
1185    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1186        let KclValue::Sketch { value } = arg else {
1187            return None;
1188        };
1189        Some(value.as_ref().to_owned())
1190    }
1191}
1192
1193impl<'a> FromKclValue<'a> for Helix {
1194    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1195        let KclValue::Helix { value } = arg else {
1196            return None;
1197        };
1198        Some(value.as_ref().to_owned())
1199    }
1200}
1201
1202impl<'a> FromKclValue<'a> for SweepPath {
1203    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1204        let case1 = Sketch::from_kcl_val;
1205        let case2 = <Vec<Sketch>>::from_kcl_val;
1206        let case3 = Helix::from_kcl_val;
1207        case1(arg)
1208            .map(Self::Sketch)
1209            .or_else(|| case2(arg).map(|arg0: Vec<Sketch>| Self::Sketch(arg0[0].clone())))
1210            .or_else(|| case3(arg).map(|arg0: Helix| Self::Helix(Box::new(arg0))))
1211    }
1212}
1213impl<'a> FromKclValue<'a> for String {
1214    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1215        let KclValue::String { value, meta: _ } = arg else {
1216            return None;
1217        };
1218        Some(value.to_owned())
1219    }
1220}
1221impl<'a> FromKclValue<'a> for crate::parsing::ast::types::KclNone {
1222    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1223        let KclValue::KclNone { value, meta: _ } = arg else {
1224            return None;
1225        };
1226        Some(value.to_owned())
1227    }
1228}
1229impl<'a> FromKclValue<'a> for bool {
1230    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1231        let KclValue::Bool { value, meta: _ } = arg else {
1232            return None;
1233        };
1234        Some(*value)
1235    }
1236}
1237
1238impl<'a> FromKclValue<'a> for Box<Solid> {
1239    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1240        let KclValue::Solid { value } = arg else {
1241            return None;
1242        };
1243        Some(value.to_owned())
1244    }
1245}
1246
1247impl<'a> FromKclValue<'a> for Box<Plane> {
1248    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1249        let KclValue::Plane { value } = arg else {
1250            return None;
1251        };
1252        Some(value.to_owned())
1253    }
1254}
1255
1256impl<'a> FromKclValue<'a> for Box<Sketch> {
1257    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1258        let KclValue::Sketch { value } = arg else {
1259            return None;
1260        };
1261        Some(value.to_owned())
1262    }
1263}
1264
1265impl<'a> FromKclValue<'a> for FunctionSource {
1266    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1267        arg.as_function().cloned()
1268    }
1269}
1270
1271impl<'a> FromKclValue<'a> for SketchOrSurface {
1272    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1273        match arg {
1274            KclValue::Sketch { value: sg } => Some(Self::Sketch(sg.to_owned())),
1275            KclValue::Plane { value } => Some(Self::SketchSurface(SketchSurface::Plane(value.clone()))),
1276            KclValue::Face { value } => Some(Self::SketchSurface(SketchSurface::Face(value.clone()))),
1277            _ => None,
1278        }
1279    }
1280}
1281impl<'a> FromKclValue<'a> for SketchSurface {
1282    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1283        match arg {
1284            KclValue::Plane { value } => Some(Self::Plane(value.clone())),
1285            KclValue::Face { value } => Some(Self::Face(value.clone())),
1286            _ => None,
1287        }
1288    }
1289}
1290
1291impl From<Args> for Metadata {
1292    fn from(value: Args) -> Self {
1293        Self {
1294            source_range: value.source_range,
1295        }
1296    }
1297}
1298
1299impl From<Args> for Vec<Metadata> {
1300    fn from(value: Args) -> Self {
1301        vec![Metadata {
1302            source_range: value.source_range,
1303        }]
1304    }
1305}