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