kcl_lib/std/
args.rs

1use std::num::NonZeroU32;
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kcmc::{
6    websocket::{ModelingCmdReq, OkWebSocketResponseData},
7    ModelingCmd,
8};
9use kittycad_modeling_cmds as kcmc;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    errors::{KclError, KclErrorDetails},
15    execution::{
16        kcl_value::FunctionSource,
17        types::{NumericType, PrimitiveType, RuntimeType},
18        ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSurface,
19        Solid, TagIdentifier,
20    },
21    parsing::ast::types::TagNode,
22    source_range::SourceRange,
23    std::{
24        shapes::{PolygonType, SketchOrSurface},
25        sketch::FaceTag,
26        sweep::SweepPath,
27    },
28    ModuleId,
29};
30
31#[derive(Debug, Clone)]
32pub struct Arg {
33    /// The evaluated argument.
34    pub value: KclValue,
35    /// The source range of the unevaluated argument.
36    pub source_range: SourceRange,
37}
38
39impl Arg {
40    pub fn new(value: KclValue, source_range: SourceRange) -> Self {
41        Self { value, source_range }
42    }
43
44    pub fn synthetic(value: KclValue) -> Self {
45        Self {
46            value,
47            source_range: SourceRange::synthetic(),
48        }
49    }
50
51    pub fn source_ranges(&self) -> Vec<SourceRange> {
52        vec![self.source_range]
53    }
54}
55
56#[derive(Debug, Clone, Default)]
57pub struct KwArgs {
58    /// Unlabeled keyword args. Currently only the first arg can be unlabeled.
59    pub unlabeled: Option<Arg>,
60    /// Labeled args.
61    pub labeled: IndexMap<String, Arg>,
62}
63
64impl KwArgs {
65    /// How many arguments are there?
66    pub fn len(&self) -> usize {
67        self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
68    }
69    /// Are there no arguments?
70    pub fn is_empty(&self) -> bool {
71        self.labeled.len() == 0 && self.unlabeled.is_none()
72    }
73}
74
75#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
76#[ts(export)]
77#[serde(rename_all = "camelCase")]
78pub struct TyF64 {
79    pub n: f64,
80    pub ty: NumericType,
81}
82
83impl TyF64 {
84    pub fn new(n: f64, ty: NumericType) -> Self {
85        Self { n, ty }
86    }
87
88    pub fn count(n: f64) -> Self {
89        Self {
90            n,
91            ty: NumericType::count(),
92        }
93    }
94
95    pub fn map(mut self, n: f64) -> Self {
96        self.n = n;
97        self
98    }
99}
100
101#[derive(Debug, Clone)]
102pub struct Args {
103    /// Positional args.
104    pub args: Vec<Arg>,
105    /// Keyword arguments
106    pub kw_args: KwArgs,
107    pub source_range: SourceRange,
108    pub ctx: ExecutorContext,
109    /// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
110    /// Otherwise it's None.
111    pipe_value: Option<Arg>,
112}
113
114impl Args {
115    pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
116        Self {
117            args,
118            kw_args: Default::default(),
119            source_range,
120            ctx,
121            pipe_value,
122        }
123    }
124
125    /// Collect the given keyword arguments.
126    pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
127        Self {
128            args: Default::default(),
129            kw_args,
130            source_range,
131            ctx,
132            pipe_value,
133        }
134    }
135
136    /// Get a keyword argument. If not set, returns None.
137    pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Result<Option<T>, KclError>
138    where
139        T: FromKclValue<'a>,
140    {
141        let Some(arg) = self.kw_args.labeled.get(label) else {
142            return Ok(None);
143        };
144
145        T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| {
146            KclError::Type(KclErrorDetails {
147                source_ranges: vec![self.source_range],
148                message: format!(
149                    "The arg {label} was given, but it was the wrong type. It should be type {} but it was {}",
150                    tynm::type_name::<T>(),
151                    arg.value.human_friendly_type(),
152                ),
153            })
154        })
155    }
156
157    /// Get a keyword argument. If not set, returns Err.
158    pub(crate) fn get_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
159    where
160        T: FromKclValue<'a>,
161    {
162        self.get_kw_arg_opt(label)?.ok_or_else(|| {
163            KclError::Semantic(KclErrorDetails {
164                source_ranges: vec![self.source_range],
165                message: format!("This function requires a keyword argument '{label}'"),
166            })
167        })
168    }
169
170    pub(crate) fn get_kw_arg_typed<T>(
171        &self,
172        label: &str,
173        ty: &RuntimeType,
174        exec_state: &mut ExecState,
175    ) -> Result<T, KclError>
176    where
177        T: for<'a> FromKclValue<'a>,
178    {
179        let Some(arg) = self.kw_args.labeled.get(label) else {
180            return Err(KclError::Semantic(KclErrorDetails {
181                source_ranges: vec![self.source_range],
182                message: format!("This function requires a keyword argument '{label}'"),
183            }));
184        };
185
186        let arg = arg.value.coerce(ty, exec_state).ok_or_else(|| {
187            let actual_type_name = arg.value.human_friendly_type();
188            let msg_base = format!(
189                "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
190                ty.human_friendly_type(),
191            );
192            let suggestion = match (ty, actual_type_name) {
193                (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(
194                    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
195                ),
196                (RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => Some(
197                    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
198                ),
199                _ => None,
200            };
201            let message = match suggestion {
202                None => msg_base,
203                Some(sugg) => format!("{msg_base}. {sugg}"),
204            };
205            KclError::Semantic(KclErrorDetails {
206                source_ranges: arg.source_ranges(),
207                message,
208            })
209        })?;
210
211        // TODO unnecessary cloning
212        Ok(T::from_kcl_val(&arg).unwrap())
213    }
214
215    /// Get a labelled keyword arg, check it's an array, and return all items in the array
216    /// plus their source range.
217    pub(crate) fn kw_arg_array_and_source<'a, T>(&'a self, label: &str) -> Result<Vec<(T, SourceRange)>, KclError>
218    where
219        T: FromKclValue<'a>,
220    {
221        let Some(arg) = self.kw_args.labeled.get(label) else {
222            let err = KclError::Semantic(KclErrorDetails {
223                source_ranges: vec![self.source_range],
224                message: format!("This function requires a keyword argument '{label}'"),
225            });
226            return Err(err);
227        };
228        let Some(array) = arg.value.as_array() else {
229            let err = KclError::Semantic(KclErrorDetails {
230                source_ranges: vec![arg.source_range],
231                message: format!(
232                    "Expected an array of {} but found {}",
233                    tynm::type_name::<T>(),
234                    arg.value.human_friendly_type()
235                ),
236            });
237            return Err(err);
238        };
239        array
240            .iter()
241            .map(|item| {
242                let source = SourceRange::from(item);
243                let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
244                    KclError::Semantic(KclErrorDetails {
245                        source_ranges: arg.source_ranges(),
246                        message: format!(
247                            "Expected a {} but found {}",
248                            tynm::type_name::<T>(),
249                            arg.value.human_friendly_type()
250                        ),
251                    })
252                })?;
253                Ok((val, source))
254            })
255            .collect::<Result<Vec<_>, _>>()
256    }
257
258    /// Get the unlabeled keyword argument. If not set, returns None.
259    pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
260        self.kw_args
261            .unlabeled
262            .as_ref()
263            .or(self.args.first())
264            .or(self.pipe_value.as_ref())
265    }
266
267    /// Get the unlabeled keyword argument. If not set, returns Err.  If it
268    /// can't be converted to the given type, returns Err.
269    pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
270    where
271        T: FromKclValue<'a>,
272    {
273        let arg = self
274            .unlabeled_kw_arg_unconverted()
275            .ok_or(KclError::Semantic(KclErrorDetails {
276                source_ranges: vec![self.source_range],
277                message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
278            }))?;
279
280        T::from_kcl_val(&arg.value).ok_or_else(|| {
281            let expected_type_name = tynm::type_name::<T>();
282            let actual_type_name = arg.value.human_friendly_type();
283            let message = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}");
284            KclError::Semantic(KclErrorDetails {
285                source_ranges: arg.source_ranges(),
286                message,
287            })
288        })
289    }
290
291    /// Get the unlabeled keyword argument. If not set, returns Err. If it
292    /// can't be converted to the given type, returns Err.
293    pub(crate) fn get_unlabeled_kw_arg_typed<T>(
294        &self,
295        label: &str,
296        ty: &RuntimeType,
297        exec_state: &mut ExecState,
298    ) -> Result<T, KclError>
299    where
300        T: for<'a> FromKclValue<'a>,
301    {
302        let arg = self
303            .unlabeled_kw_arg_unconverted()
304            .ok_or(KclError::Semantic(KclErrorDetails {
305                source_ranges: vec![self.source_range],
306                message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
307            }))?;
308
309        let arg = arg.value.coerce(ty, exec_state).ok_or_else(|| {
310            let actual_type_name = arg.value.human_friendly_type();
311            let msg_base = format!(
312                "This function expected the input argument to be {} but it's actually of type {actual_type_name}",
313                ty.human_friendly_type(),
314            );
315            let suggestion = match (ty, actual_type_name) {
316                (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(
317                    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
318                ),
319                (RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => Some(
320                    "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
321                ),
322                _ => None,
323            };
324            let message = match suggestion {
325                None => msg_base,
326                Some(sugg) => format!("{msg_base}. {sugg}"),
327            };
328            KclError::Semantic(KclErrorDetails {
329                source_ranges: arg.source_ranges(),
330                message,
331            })
332        })?;
333
334        // TODO unnecessary cloning
335        Ok(T::from_kcl_val(&arg).unwrap())
336    }
337
338    // Add a modeling command to the batch but don't fire it right away.
339    pub(crate) async fn batch_modeling_cmd(
340        &self,
341        id: uuid::Uuid,
342        cmd: ModelingCmd,
343    ) -> Result<(), crate::errors::KclError> {
344        self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
345    }
346
347    // Add multiple modeling commands to the batch but don't fire them right away.
348    pub(crate) async fn batch_modeling_cmds(&self, cmds: &[ModelingCmdReq]) -> Result<(), crate::errors::KclError> {
349        self.ctx.engine.batch_modeling_cmds(self.source_range, cmds).await
350    }
351
352    // Add a modeling commandSolid> to the batch that gets executed at the end of the file.
353    // This is good for something like fillet or chamfer where the engine would
354    // eat the path id if we executed it right away.
355    pub(crate) async fn batch_end_cmd(&self, id: uuid::Uuid, cmd: ModelingCmd) -> Result<(), crate::errors::KclError> {
356        self.ctx.engine.batch_end_cmd(id, self.source_range, &cmd).await
357    }
358
359    /// Send the modeling cmd and wait for the response.
360    pub(crate) async fn send_modeling_cmd(
361        &self,
362        id: uuid::Uuid,
363        cmd: ModelingCmd,
364    ) -> Result<OkWebSocketResponseData, KclError> {
365        self.ctx.engine.send_modeling_cmd(id, self.source_range, &cmd).await
366    }
367
368    fn get_tag_info_from_memory<'a, 'e>(
369        &'a self,
370        exec_state: &'e mut ExecState,
371        tag: &'a TagIdentifier,
372    ) -> Result<&'e crate::execution::TagEngineInfo, KclError> {
373        if let (epoch, KclValue::TagIdentifier(t)) =
374            exec_state.stack().get_from_call_stack(&tag.value, self.source_range)?
375        {
376            let info = t.get_info(epoch).ok_or_else(|| {
377                KclError::Type(KclErrorDetails {
378                    message: format!("Tag `{}` does not have engine info", tag.value),
379                    source_ranges: vec![self.source_range],
380                })
381            })?;
382            Ok(info)
383        } else {
384            Err(KclError::Type(KclErrorDetails {
385                message: format!("Tag `{}` does not exist", tag.value),
386                source_ranges: vec![self.source_range],
387            }))
388        }
389    }
390
391    pub(crate) fn get_tag_engine_info<'a, 'e>(
392        &'a self,
393        exec_state: &'e mut ExecState,
394        tag: &'a TagIdentifier,
395    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
396    where
397        'e: 'a,
398    {
399        if let Some(info) = tag.get_cur_info() {
400            return Ok(info);
401        }
402
403        self.get_tag_info_from_memory(exec_state, tag)
404    }
405
406    fn get_tag_engine_info_check_surface<'a, 'e>(
407        &'a self,
408        exec_state: &'e mut ExecState,
409        tag: &'a TagIdentifier,
410    ) -> Result<&'a crate::execution::TagEngineInfo, KclError>
411    where
412        'e: 'a,
413    {
414        if let Some(info) = tag.get_cur_info() {
415            if info.surface.is_some() {
416                return Ok(info);
417            }
418        }
419
420        self.get_tag_info_from_memory(exec_state, tag)
421    }
422
423    /// Flush just the fillets and chamfers for this specific SolidSet.
424    #[allow(clippy::vec_box)]
425    pub(crate) async fn flush_batch_for_solids(
426        &self,
427        exec_state: &mut ExecState,
428        solids: &[Solid],
429    ) -> Result<(), KclError> {
430        // Make sure we don't traverse sketches more than once.
431        let mut traversed_sketches = Vec::new();
432
433        // Collect all the fillet/chamfer ids for the solids.
434        let mut ids = Vec::new();
435        for solid in solids {
436            // We need to traverse the solids that share the same sketch.
437            let sketch_id = solid.sketch.id;
438            if !traversed_sketches.contains(&sketch_id) {
439                // Find all the solids on the same shared sketch.
440                ids.extend(
441                    exec_state
442                        .stack()
443                        .walk_call_stack()
444                        .filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
445                        .flat_map(|v| match v {
446                            KclValue::Solid { value } => value.get_all_edge_cut_ids(),
447                            _ => unreachable!(),
448                        }),
449                );
450                traversed_sketches.push(sketch_id);
451            }
452
453            ids.extend(solid.get_all_edge_cut_ids());
454        }
455
456        // We can return early if there are no fillets or chamfers.
457        if ids.is_empty() {
458            return Ok(());
459        }
460
461        // We want to move these fillets and chamfers from batch_end to batch so they get executed
462        // before what ever we call next.
463        for id in ids {
464            // Pop it off the batch_end and add it to the batch.
465            let Some(item) = self.ctx.engine.batch_end().write().await.shift_remove(&id) else {
466                // It might be in the batch already.
467                continue;
468            };
469            // Add it to the batch.
470            self.ctx.engine.batch().write().await.push(item);
471        }
472
473        // Run flush.
474        // Yes, we do need to actually flush the batch here, or references will fail later.
475        self.ctx.engine.flush_batch(false, self.source_range).await?;
476
477        Ok(())
478    }
479
480    pub(crate) fn make_user_val_from_point(&self, p: [f64; 2]) -> Result<KclValue, KclError> {
481        let meta = Metadata {
482            source_range: self.source_range,
483        };
484        let x = KclValue::Number {
485            value: p[0],
486            meta: vec![meta],
487            ty: NumericType::Unknown,
488        };
489        let y = KclValue::Number {
490            value: p[1],
491            meta: vec![meta],
492            ty: NumericType::Unknown,
493        };
494        Ok(KclValue::MixedArray {
495            value: vec![x, y],
496            meta: vec![meta],
497        })
498    }
499
500    pub(crate) fn make_user_val_from_f64(&self, f: f64) -> KclValue {
501        KclValue::from_number(
502            f,
503            vec![Metadata {
504                source_range: self.source_range,
505            }],
506        )
507    }
508
509    pub(crate) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
510        KclValue::from_number_with_type(
511            f.n,
512            f.ty,
513            vec![Metadata {
514                source_range: self.source_range,
515            }],
516        )
517    }
518
519    pub(crate) fn make_user_val_from_f64_array(&self, f: Vec<f64>, ty: &NumericType) -> Result<KclValue, KclError> {
520        let array = f
521            .into_iter()
522            .map(|n| KclValue::Number {
523                value: n,
524                meta: vec![Metadata {
525                    source_range: self.source_range,
526                }],
527                ty: ty.clone(),
528            })
529            .collect::<Vec<_>>();
530        Ok(KclValue::MixedArray {
531            value: array,
532            meta: vec![Metadata {
533                source_range: self.source_range,
534            }],
535        })
536    }
537
538    pub(crate) fn get_number(&self) -> Result<f64, KclError> {
539        FromArgs::from_args(self, 0)
540    }
541
542    pub(crate) fn get_number_with_type(&self) -> Result<TyF64, KclError> {
543        FromArgs::from_args(self, 0)
544    }
545
546    pub(crate) fn get_number_array(&self) -> Result<Vec<f64>, KclError> {
547        let numbers = self
548            .args
549            .iter()
550            .map(|arg| {
551                let Some(num) = f64::from_kcl_val(&arg.value) else {
552                    return Err(KclError::Semantic(KclErrorDetails {
553                        source_ranges: arg.source_ranges(),
554                        message: format!("Expected a number but found {}", arg.value.human_friendly_type()),
555                    }));
556                };
557                Ok(num)
558            })
559            .collect::<Result<_, _>>()?;
560        Ok(numbers)
561    }
562
563    pub(crate) fn get_number_array_with_types(&self) -> Result<Vec<TyF64>, KclError> {
564        let numbers = self
565            .args
566            .iter()
567            .map(|arg| {
568                let Some(num) = <TyF64>::from_kcl_val(&arg.value) else {
569                    return Err(KclError::Semantic(KclErrorDetails {
570                        source_ranges: arg.source_ranges(),
571                        message: format!("Expected a number but found {}", arg.value.human_friendly_type()),
572                    }));
573                };
574                Ok(num)
575            })
576            .collect::<Result<_, _>>()?;
577        Ok(numbers)
578    }
579
580    pub(crate) fn get_hypotenuse_leg(&self) -> Result<(f64, f64, NumericType), KclError> {
581        let numbers = self.get_number_array_with_types()?;
582
583        if numbers.len() != 2 {
584            return Err(KclError::Type(KclErrorDetails {
585                message: format!("Expected a number array of length 2, found `{:?}`", numbers),
586                source_ranges: vec![self.source_range],
587            }));
588        }
589
590        let mut numbers = numbers.into_iter();
591        let a = numbers.next().unwrap();
592        let b = numbers.next().unwrap();
593        let ty = a.ty.combine_eq(&b.ty);
594        Ok((a.n, b.n, ty))
595    }
596
597    pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
598        let Some(arg0) = self.args.first() else {
599            return Err(KclError::Semantic(KclErrorDetails {
600                message: "Expected a sketch argument".to_owned(),
601                source_ranges: vec![self.source_range],
602            }));
603        };
604        let sarg = arg0
605            .value
606            .coerce(&RuntimeType::sketches(), exec_state)
607            .ok_or(KclError::Type(KclErrorDetails {
608                message: format!(
609                    "Expected an array of sketches, found {}",
610                    arg0.value.human_friendly_type()
611                ),
612                source_ranges: vec![self.source_range],
613            }))?;
614        let sketches = match sarg {
615            KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(),
616            _ => unreachable!(),
617        };
618
619        let Some(arg1) = self.args.get(1) else {
620            return Err(KclError::Semantic(KclErrorDetails {
621                message: "Expected a second sketch argument".to_owned(),
622                source_ranges: vec![self.source_range],
623            }));
624        };
625        let sarg = arg1
626            .value
627            .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
628            .ok_or(KclError::Type(KclErrorDetails {
629                message: format!("Expected a sketch, found {}", arg1.value.human_friendly_type()),
630                source_ranges: vec![self.source_range],
631            }))?;
632        let sketch = match sarg {
633            KclValue::Sketch { value } => *value,
634            _ => unreachable!(),
635        };
636
637        Ok((sketches, sketch))
638    }
639
640    pub(crate) fn get_sketch(&self, exec_state: &mut ExecState) -> Result<Sketch, KclError> {
641        let Some(arg0) = self.args.first() else {
642            return Err(KclError::Semantic(KclErrorDetails {
643                message: "Expected a sketch argument".to_owned(),
644                source_ranges: vec![self.source_range],
645            }));
646        };
647        let sarg = arg0
648            .value
649            .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
650            .ok_or(KclError::Type(KclErrorDetails {
651                message: format!("Expected a sketch, found {}", arg0.value.human_friendly_type()),
652                source_ranges: vec![self.source_range],
653            }))?;
654        match sarg {
655            KclValue::Sketch { value } => Ok(*value),
656            _ => unreachable!(),
657        }
658    }
659
660    pub(crate) fn get_data<'a, T>(&'a self) -> Result<T, KclError>
661    where
662        T: FromArgs<'a> + serde::de::DeserializeOwned,
663    {
664        FromArgs::from_args(self, 0)
665    }
666
667    pub(crate) fn get_import_data(&self) -> Result<(String, Option<crate::std::import::ImportFormat>), KclError> {
668        FromArgs::from_args(self, 0)
669    }
670
671    pub(crate) fn get_data_and_optional_tag<'a, T>(&'a self) -> Result<(T, Option<FaceTag>), KclError>
672    where
673        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
674    {
675        FromArgs::from_args(self, 0)
676    }
677
678    pub(crate) fn get_data_and_sketches<'a, T>(
679        &'a self,
680        exec_state: &mut ExecState,
681    ) -> Result<(T, Vec<Sketch>), KclError>
682    where
683        T: serde::de::DeserializeOwned + FromArgs<'a>,
684    {
685        let data: T = FromArgs::from_args(self, 0)?;
686        let Some(arg1) = self.args.get(1) else {
687            return Err(KclError::Semantic(KclErrorDetails {
688                message: "Expected one or more sketches for second argument".to_owned(),
689                source_ranges: vec![self.source_range],
690            }));
691        };
692        let sarg = arg1
693            .value
694            .coerce(&RuntimeType::sketches(), exec_state)
695            .ok_or(KclError::Type(KclErrorDetails {
696                message: format!(
697                    "Expected one or more sketches for second argument, found {}",
698                    arg1.value.human_friendly_type()
699                ),
700                source_ranges: vec![self.source_range],
701            }))?;
702        let sketches = match sarg {
703            KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(),
704            _ => unreachable!(),
705        };
706        Ok((data, sketches))
707    }
708
709    pub(crate) fn get_data_and_sketch_and_tag<'a, T>(
710        &'a self,
711        exec_state: &mut ExecState,
712    ) -> Result<(T, Sketch, Option<TagNode>), KclError>
713    where
714        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
715    {
716        let data: T = FromArgs::from_args(self, 0)?;
717        let Some(arg1) = self.args.get(1) else {
718            return Err(KclError::Semantic(KclErrorDetails {
719                message: "Expected a sketch for second argument".to_owned(),
720                source_ranges: vec![self.source_range],
721            }));
722        };
723        let sarg = arg1
724            .value
725            .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)
726            .ok_or(KclError::Type(KclErrorDetails {
727                message: format!(
728                    "Expected a sketch for second argument, found {}",
729                    arg1.value.human_friendly_type()
730                ),
731                source_ranges: vec![self.source_range],
732            }))?;
733        let sketch = match sarg {
734            KclValue::Sketch { value } => *value,
735            _ => unreachable!(),
736        };
737        let tag: Option<TagNode> = FromArgs::from_args(self, 2)?;
738        Ok((data, sketch, tag))
739    }
740
741    pub(crate) fn get_data_and_sketch_surface<'a, T>(&'a self) -> Result<(T, SketchSurface, Option<TagNode>), KclError>
742    where
743        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
744    {
745        FromArgs::from_args(self, 0)
746    }
747
748    pub(crate) fn get_data_and_solid<'a, T>(&'a self, exec_state: &mut ExecState) -> Result<(T, Box<Solid>), KclError>
749    where
750        T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
751    {
752        let data: T = FromArgs::from_args(self, 0)?;
753        let Some(arg1) = self.args.get(1) else {
754            return Err(KclError::Semantic(KclErrorDetails {
755                message: "Expected a solid for second argument".to_owned(),
756                source_ranges: vec![self.source_range],
757            }));
758        };
759        let sarg = arg1
760            .value
761            .coerce(&RuntimeType::Primitive(PrimitiveType::Solid), exec_state)
762            .ok_or(KclError::Type(KclErrorDetails {
763                message: format!(
764                    "Expected a solid for second argument, found {}",
765                    arg1.value.human_friendly_type()
766                ),
767                source_ranges: vec![self.source_range],
768            }))?;
769        let solid = match sarg {
770            KclValue::Solid { value } => value,
771            _ => unreachable!(),
772        };
773        Ok((data, solid))
774    }
775
776    pub(crate) fn get_tag_to_number_sketch(&self) -> Result<(TagIdentifier, f64, Sketch), KclError> {
777        FromArgs::from_args(self, 0)
778    }
779
780    pub(crate) async fn get_adjacent_face_to_tag(
781        &self,
782        exec_state: &mut ExecState,
783        tag: &TagIdentifier,
784        must_be_planar: bool,
785    ) -> Result<uuid::Uuid, KclError> {
786        if tag.value.is_empty() {
787            return Err(KclError::Type(KclErrorDetails {
788                message: "Expected a non-empty tag for the face".to_string(),
789                source_ranges: vec![self.source_range],
790            }));
791        }
792
793        let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?;
794
795        let surface = engine_info.surface.as_ref().ok_or_else(|| {
796            KclError::Type(KclErrorDetails {
797                message: format!("Tag `{}` does not have a surface", tag.value),
798                source_ranges: vec![self.source_range],
799            })
800        })?;
801
802        if let Some(face_from_surface) = match surface {
803            ExtrudeSurface::ExtrudePlane(extrude_plane) => {
804                if let Some(plane_tag) = &extrude_plane.tag {
805                    if plane_tag.name == tag.value {
806                        Some(Ok(extrude_plane.face_id))
807                    } else {
808                        None
809                    }
810                } else {
811                    None
812                }
813            }
814            // The must be planar check must be called before the arc check.
815            ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
816                message: format!("Tag `{}` is a non-planar surface", tag.value),
817                source_ranges: vec![self.source_range],
818            }))),
819            ExtrudeSurface::ExtrudeArc(extrude_arc) => {
820                if let Some(arc_tag) = &extrude_arc.tag {
821                    if arc_tag.name == tag.value {
822                        Some(Ok(extrude_arc.face_id))
823                    } else {
824                        None
825                    }
826                } else {
827                    None
828                }
829            }
830            ExtrudeSurface::Chamfer(chamfer) => {
831                if let Some(chamfer_tag) = &chamfer.tag {
832                    if chamfer_tag.name == tag.value {
833                        Some(Ok(chamfer.face_id))
834                    } else {
835                        None
836                    }
837                } else {
838                    None
839                }
840            }
841            // The must be planar check must be called before the fillet check.
842            ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
843                message: format!("Tag `{}` is a non-planar surface", tag.value),
844                source_ranges: vec![self.source_range],
845            }))),
846            ExtrudeSurface::Fillet(fillet) => {
847                if let Some(fillet_tag) = &fillet.tag {
848                    if fillet_tag.name == tag.value {
849                        Some(Ok(fillet.face_id))
850                    } else {
851                        None
852                    }
853                } else {
854                    None
855                }
856            }
857        } {
858            return face_from_surface;
859        }
860
861        // If we still haven't found the face, return an error.
862        Err(KclError::Type(KclErrorDetails {
863            message: format!("Expected a face with the tag `{}`", tag.value),
864            source_ranges: vec![self.source_range],
865        }))
866    }
867
868    pub(crate) fn get_polygon_args(
869        &self,
870    ) -> Result<
871        (
872            crate::std::shapes::PolygonData,
873            crate::std::shapes::SketchOrSurface,
874            Option<TagNode>,
875        ),
876        KclError,
877    > {
878        FromArgs::from_args(self, 0)
879    }
880}
881
882/// Types which impl this trait can be read out of the `Args` passed into a KCL function.
883pub trait FromArgs<'a>: Sized {
884    /// Get this type from the args passed into a KCL function, at the given index in the argument list.
885    fn from_args(args: &'a Args, index: usize) -> Result<Self, KclError>;
886}
887
888/// Types which impl this trait can be extracted from a `KclValue`.
889pub trait FromKclValue<'a>: Sized {
890    /// Try to convert a KclValue into this type.
891    fn from_kcl_val(arg: &'a KclValue) -> Option<Self>;
892}
893
894impl<'a> FromArgs<'a> for Vec<KclValue> {
895    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
896        let Some(arg) = args.args.get(i) else {
897            return Err(KclError::Semantic(KclErrorDetails {
898                message: format!("Expected an argument at index {i}"),
899                source_ranges: vec![args.source_range],
900            }));
901        };
902        let KclValue::MixedArray { value: array, meta: _ } = &arg.value else {
903            let message = format!("Expected an array but found {}", arg.value.human_friendly_type());
904            return Err(KclError::Type(KclErrorDetails {
905                source_ranges: arg.source_ranges(),
906                message,
907            }));
908        };
909        Ok(array.to_owned())
910    }
911}
912
913impl<'a, T> FromArgs<'a> for T
914where
915    T: FromKclValue<'a> + Sized,
916{
917    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
918        let Some(arg) = args.args.get(i) else {
919            return Err(KclError::Semantic(KclErrorDetails {
920                message: format!("Expected an argument at index {i}"),
921                source_ranges: vec![args.source_range],
922            }));
923        };
924        let Some(val) = T::from_kcl_val(&arg.value) else {
925            return Err(KclError::Semantic(KclErrorDetails {
926                message: format!(
927                    "Argument at index {i} was supposed to be type {} but found {}",
928                    tynm::type_name::<T>(),
929                    arg.value.human_friendly_type(),
930                ),
931                source_ranges: arg.source_ranges(),
932            }));
933        };
934        Ok(val)
935    }
936}
937
938impl<'a, T> FromArgs<'a> for Option<T>
939where
940    T: FromKclValue<'a> + Sized,
941{
942    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
943        let Some(arg) = args.args.get(i) else { return Ok(None) };
944        if crate::parsing::ast::types::KclNone::from_kcl_val(&arg.value).is_some() {
945            return Ok(None);
946        }
947        let Some(val) = T::from_kcl_val(&arg.value) else {
948            return Err(KclError::Semantic(KclErrorDetails {
949                message: format!(
950                    "Argument at index {i} was supposed to be type Option<{}> but found {}",
951                    tynm::type_name::<T>(),
952                    arg.value.human_friendly_type()
953                ),
954                source_ranges: arg.source_ranges(),
955            }));
956        };
957        Ok(Some(val))
958    }
959}
960
961impl<'a, A, B> FromArgs<'a> for (A, B)
962where
963    A: FromArgs<'a>,
964    B: FromArgs<'a>,
965{
966    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
967        let a = A::from_args(args, i)?;
968        let b = B::from_args(args, i + 1)?;
969        Ok((a, b))
970    }
971}
972
973impl<'a, A, B, C> FromArgs<'a> for (A, B, C)
974where
975    A: FromArgs<'a>,
976    B: FromArgs<'a>,
977    C: FromArgs<'a>,
978{
979    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
980        let a = A::from_args(args, i)?;
981        let b = B::from_args(args, i + 1)?;
982        let c = C::from_args(args, i + 2)?;
983        Ok((a, b, c))
984    }
985}
986impl<'a, A, B, C, D> FromArgs<'a> for (A, B, C, D)
987where
988    A: FromArgs<'a>,
989    B: FromArgs<'a>,
990    C: FromArgs<'a>,
991    D: FromArgs<'a>,
992{
993    fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
994        let a = A::from_args(args, i)?;
995        let b = B::from_args(args, i + 1)?;
996        let c = C::from_args(args, i + 2)?;
997        let d = D::from_args(args, i + 3)?;
998        Ok((a, b, c, d))
999    }
1000}
1001
1002impl<'a> FromKclValue<'a> for [f64; 2] {
1003    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1004        match arg {
1005            KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
1006                if value.len() != 2 {
1007                    return None;
1008                }
1009                let v0 = value.first()?;
1010                let v1 = value.get(1)?;
1011                let array = [v0.as_f64()?, v1.as_f64()?];
1012                Some(array)
1013            }
1014            _ => None,
1015        }
1016    }
1017}
1018
1019impl<'a> FromKclValue<'a> for [usize; 3] {
1020    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1021        match arg {
1022            KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
1023                if value.len() != 3 {
1024                    return None;
1025                }
1026                let v0 = value.first()?;
1027                let v1 = value.get(1)?;
1028                let v2 = value.get(2)?;
1029                let array = [v0.as_usize()?, v1.as_usize()?, v2.as_usize()?];
1030                Some(array)
1031            }
1032            _ => None,
1033        }
1034    }
1035}
1036
1037impl<'a> FromKclValue<'a> for [f64; 3] {
1038    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1039        match arg {
1040            KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
1041                if value.len() != 3 {
1042                    return None;
1043                }
1044                let v0 = value.first()?;
1045                let v1 = value.get(1)?;
1046                let v2 = value.get(2)?;
1047                let array = [v0.as_f64()?, v1.as_f64()?, v2.as_f64()?];
1048                Some(array)
1049            }
1050            _ => None,
1051        }
1052    }
1053}
1054
1055impl<'a> FromKclValue<'a> for TagNode {
1056    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1057        arg.get_tag_declarator().ok()
1058    }
1059}
1060
1061impl<'a> FromKclValue<'a> for TagIdentifier {
1062    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1063        arg.get_tag_identifier().ok()
1064    }
1065}
1066
1067impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
1068    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1069        match arg {
1070            KclValue::HomArray { value, .. } => {
1071                let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
1072                Some(tags)
1073            }
1074            KclValue::MixedArray { value, .. } => {
1075                let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
1076                Some(tags)
1077            }
1078            _ => None,
1079        }
1080    }
1081}
1082
1083impl<'a> FromKclValue<'a> for KclValue {
1084    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1085        Some(arg.clone())
1086    }
1087}
1088
1089macro_rules! let_field_of {
1090    // Optional field
1091    ($obj:ident, $field:ident?) => {
1092        let $field = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val);
1093    };
1094    // Optional field but with a different string used as the key
1095    ($obj:ident, $field:ident? $key:literal) => {
1096        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val);
1097    };
1098    // Mandatory field, but with a different string used as the key.
1099    ($obj:ident, $field:ident $key:literal) => {
1100        let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val)?;
1101    };
1102    // Mandatory field, optionally with a type annotation
1103    ($obj:ident, $field:ident $(, $annotation:ty)?) => {
1104        let $field $(: $annotation)? = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val)?;
1105    };
1106}
1107
1108impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat {
1109    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1110        let obj = arg.as_object()?;
1111        let_field_of!(obj, typ "format");
1112        match typ {
1113            "fbx" => Some(Self::Fbx {}),
1114            "gltf" => Some(Self::Gltf {}),
1115            "sldprt" => Some(Self::Sldprt {}),
1116            "step" => Some(Self::Step {}),
1117            "stl" => {
1118                let_field_of!(obj, coords?);
1119                let_field_of!(obj, units);
1120                Some(Self::Stl { coords, units })
1121            }
1122            "obj" => {
1123                let_field_of!(obj, coords?);
1124                let_field_of!(obj, units);
1125                Some(Self::Obj { coords, units })
1126            }
1127            "ply" => {
1128                let_field_of!(obj, coords?);
1129                let_field_of!(obj, units);
1130                Some(Self::Ply { coords, units })
1131            }
1132            _ => None,
1133        }
1134    }
1135}
1136
1137impl<'a> FromKclValue<'a> for super::sketch::AngledLineThatIntersectsData {
1138    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1139        let obj = arg.as_object()?;
1140        let_field_of!(obj, angle);
1141        let_field_of!(obj, intersect_tag "intersectTag");
1142        let_field_of!(obj, offset?);
1143        Some(Self {
1144            angle,
1145            intersect_tag,
1146            offset,
1147        })
1148    }
1149}
1150
1151impl<'a> FromKclValue<'a> for super::shapes::PolygonData {
1152    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1153        let obj = arg.as_object()?;
1154        let_field_of!(obj, radius);
1155        let_field_of!(obj, num_sides "numSides");
1156        let_field_of!(obj, center);
1157        let_field_of!(obj, inscribed);
1158        let polygon_type = if inscribed {
1159            PolygonType::Inscribed
1160        } else {
1161            PolygonType::Circumscribed
1162        };
1163        Some(Self {
1164            radius,
1165            num_sides,
1166            center,
1167            polygon_type,
1168            inscribed,
1169        })
1170    }
1171}
1172
1173impl<'a> FromKclValue<'a> for crate::std::polar::PolarCoordsData {
1174    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1175        let obj = arg.as_object()?;
1176        let_field_of!(obj, angle);
1177        let_field_of!(obj, length);
1178        Some(Self { angle, length })
1179    }
1180}
1181
1182impl<'a> FromKclValue<'a> for crate::execution::Plane {
1183    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1184        arg.as_plane().cloned()
1185    }
1186}
1187
1188impl<'a> FromKclValue<'a> for crate::execution::PlaneType {
1189    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1190        let plane_type = match arg.as_str()? {
1191            "XY" | "xy" => Self::XY,
1192            "XZ" | "xz" => Self::XZ,
1193            "YZ" | "yz" => Self::YZ,
1194            "Custom" => Self::Custom,
1195            _ => return None,
1196        };
1197        Some(plane_type)
1198    }
1199}
1200
1201impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::units::UnitLength {
1202    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1203        let s = arg.as_str()?;
1204        s.parse().ok()
1205    }
1206}
1207
1208impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::System {
1209    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1210        let obj = arg.as_object()?;
1211        let_field_of!(obj, forward);
1212        let_field_of!(obj, up);
1213        Some(Self { forward, up })
1214    }
1215}
1216
1217impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::AxisDirectionPair {
1218    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1219        let obj = arg.as_object()?;
1220        let_field_of!(obj, axis);
1221        let_field_of!(obj, direction);
1222        Some(Self { axis, direction })
1223    }
1224}
1225
1226impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Axis {
1227    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1228        let s = arg.as_str()?;
1229        match s {
1230            "y" => Some(Self::Y),
1231            "z" => Some(Self::Z),
1232            _ => None,
1233        }
1234    }
1235}
1236
1237impl<'a> FromKclValue<'a> for PolygonType {
1238    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1239        let s = arg.as_str()?;
1240        match s {
1241            "inscribed" => Some(Self::Inscribed),
1242            _ => Some(Self::Circumscribed),
1243        }
1244    }
1245}
1246
1247impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
1248    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1249        let s = arg.as_str()?;
1250        match s {
1251            "positive" => Some(Self::Positive),
1252            "negative" => Some(Self::Negative),
1253            _ => None,
1254        }
1255    }
1256}
1257
1258impl<'a> FromKclValue<'a> for super::sketch::BezierData {
1259    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1260        let obj = arg.as_object()?;
1261        let_field_of!(obj, to);
1262        let_field_of!(obj, control1);
1263        let_field_of!(obj, control2);
1264        Some(Self { to, control1, control2 })
1265    }
1266}
1267
1268impl<'a> FromKclValue<'a> for FaceTag {
1269    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1270        let case1 = || match arg.as_str() {
1271            Some("start" | "START") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::Start)),
1272            Some("end" | "END") => Some(Self::StartOrEnd(super::sketch::StartOrEnd::End)),
1273            _ => None,
1274        };
1275        let case2 = || {
1276            let tag = TagIdentifier::from_kcl_val(arg)?;
1277            Some(Self::Tag(Box::new(tag)))
1278        };
1279        case1().or_else(case2)
1280    }
1281}
1282
1283impl<'a> FromKclValue<'a> for super::sketch::AngledLineToData {
1284    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1285        // Deserialize from an {angle, to} object.
1286        let case1 = || {
1287            let obj = arg.as_object()?;
1288            let_field_of!(obj, to);
1289            let_field_of!(obj, angle);
1290            Some(Self { angle, to })
1291        };
1292        // Deserialize from an [angle, to] array.
1293        let case2 = || {
1294            let [angle, to] = arg.as_point2d()?;
1295            Some(Self { angle, to })
1296        };
1297        case1().or_else(case2)
1298    }
1299}
1300
1301impl<'a> FromKclValue<'a> for super::sketch::ArcData {
1302    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1303        let obj = arg.as_object()?;
1304        let_field_of!(obj, radius);
1305        let case1 = || {
1306            let angle_start = obj.get("angleStart")?.as_f64()?;
1307            let angle_end = obj.get("angleEnd")?.as_f64()?;
1308            Some(Self::AnglesAndRadius {
1309                angle_start,
1310                angle_end,
1311                radius,
1312            })
1313        };
1314        let case2 = || {
1315            let obj = arg.as_object()?;
1316            let_field_of!(obj, to);
1317            let_field_of!(obj, center);
1318            Some(Self::CenterToRadius { center, to, radius })
1319        };
1320        case1().or_else(case2)
1321    }
1322}
1323
1324impl<'a> FromKclValue<'a> for super::sketch::ArcToData {
1325    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1326        let obj = arg.as_object()?;
1327        let_field_of!(obj, end);
1328        let_field_of!(obj, interior);
1329        Some(Self { end, interior })
1330    }
1331}
1332
1333impl<'a> FromKclValue<'a> for super::sketch::TangentialArcData {
1334    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1335        let obj = arg.as_object()?;
1336        let_field_of!(obj, radius);
1337        let_field_of!(obj, offset);
1338        Some(Self::RadiusAndOffset { radius, offset })
1339    }
1340}
1341
1342impl<'a> FromKclValue<'a> for crate::execution::Point3d {
1343    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1344        // Case 1: object with x/y/z fields
1345        if let Some(obj) = arg.as_object() {
1346            let_field_of!(obj, x);
1347            let_field_of!(obj, y);
1348            let_field_of!(obj, z);
1349            return Some(Self { x, y, z });
1350        }
1351        // Case 2: Array of 3 numbers.
1352        let [x, y, z]: [f64; 3] = FromKclValue::from_kcl_val(arg)?;
1353        Some(Self { x, y, z })
1354    }
1355}
1356
1357impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
1358    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1359        // Case 0: actual plane
1360        if let KclValue::Plane { value } = arg {
1361            return Some(Self::Plane {
1362                origin: value.origin,
1363                x_axis: value.x_axis,
1364                y_axis: value.y_axis,
1365                z_axis: value.z_axis,
1366            });
1367        }
1368        // Case 1: predefined plane
1369        if let Some(s) = arg.as_str() {
1370            return match s {
1371                "XY" | "xy" => Some(Self::XY),
1372                "-XY" | "-xy" => Some(Self::NegXY),
1373                "XZ" | "xz" => Some(Self::XZ),
1374                "-XZ" | "-xz" => Some(Self::NegXZ),
1375                "YZ" | "yz" => Some(Self::YZ),
1376                "-YZ" | "-yz" => Some(Self::NegYZ),
1377                _ => None,
1378            };
1379        }
1380        // Case 2: custom plane
1381        let obj = arg.as_object()?;
1382        let_field_of!(obj, plane, &KclObjectFields);
1383        let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
1384        let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
1385        let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
1386        let z_axis = plane.get("zAxis").and_then(FromKclValue::from_kcl_val)?;
1387        Some(Self::Plane {
1388            origin,
1389            x_axis,
1390            y_axis,
1391            z_axis,
1392        })
1393    }
1394}
1395
1396impl<'a> FromKclValue<'a> for crate::execution::ExtrudePlane {
1397    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1398        let obj = arg.as_object()?;
1399        let_field_of!(obj, face_id "faceId");
1400        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1401        let_field_of!(obj, geo_meta "geoMeta");
1402        Some(Self { face_id, tag, geo_meta })
1403    }
1404}
1405
1406impl<'a> FromKclValue<'a> for crate::execution::ExtrudeArc {
1407    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1408        let obj = arg.as_object()?;
1409        let_field_of!(obj, face_id "faceId");
1410        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1411        let_field_of!(obj, geo_meta "geoMeta");
1412        Some(Self { face_id, tag, geo_meta })
1413    }
1414}
1415
1416impl<'a> FromKclValue<'a> for crate::execution::GeoMeta {
1417    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1418        let obj = arg.as_object()?;
1419        let_field_of!(obj, id);
1420        let_field_of!(obj, source_range "sourceRange");
1421        Some(Self {
1422            id,
1423            metadata: Metadata { source_range },
1424        })
1425    }
1426}
1427
1428impl<'a> FromKclValue<'a> for crate::execution::ChamferSurface {
1429    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1430        let obj = arg.as_object()?;
1431        let_field_of!(obj, face_id "faceId");
1432        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1433        let_field_of!(obj, geo_meta "geoMeta");
1434        Some(Self { face_id, tag, geo_meta })
1435    }
1436}
1437
1438impl<'a> FromKclValue<'a> for crate::execution::FilletSurface {
1439    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1440        let obj = arg.as_object()?;
1441        let_field_of!(obj, face_id "faceId");
1442        let tag = FromKclValue::from_kcl_val(obj.get("tag")?);
1443        let_field_of!(obj, geo_meta "geoMeta");
1444        Some(Self { face_id, tag, geo_meta })
1445    }
1446}
1447
1448impl<'a> FromKclValue<'a> for ExtrudeSurface {
1449    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1450        let case1 = crate::execution::ExtrudePlane::from_kcl_val;
1451        let case2 = crate::execution::ExtrudeArc::from_kcl_val;
1452        let case3 = crate::execution::ChamferSurface::from_kcl_val;
1453        let case4 = crate::execution::FilletSurface::from_kcl_val;
1454        case1(arg)
1455            .map(Self::ExtrudePlane)
1456            .or_else(|| case2(arg).map(Self::ExtrudeArc))
1457            .or_else(|| case3(arg).map(Self::Chamfer))
1458            .or_else(|| case4(arg).map(Self::Fillet))
1459    }
1460}
1461
1462impl<'a> FromKclValue<'a> for crate::execution::EdgeCut {
1463    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1464        let obj = arg.as_object()?;
1465        let_field_of!(obj, typ "type");
1466        let tag = Box::new(obj.get("tag").and_then(FromKclValue::from_kcl_val));
1467        let_field_of!(obj, edge_id "edgeId");
1468        let_field_of!(obj, id);
1469        match typ {
1470            "fillet" => {
1471                let_field_of!(obj, radius);
1472                Some(Self::Fillet {
1473                    edge_id,
1474                    tag,
1475                    id,
1476                    radius,
1477                })
1478            }
1479            "chamfer" => {
1480                let_field_of!(obj, length);
1481                Some(Self::Chamfer {
1482                    id,
1483                    length,
1484                    edge_id,
1485                    tag,
1486                })
1487            }
1488            _ => None,
1489        }
1490    }
1491}
1492
1493macro_rules! impl_from_kcl_for_vec {
1494    ($typ:path) => {
1495        impl<'a> FromKclValue<'a> for Vec<$typ> {
1496            fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1497                arg.as_array()?
1498                    .iter()
1499                    .map(|value| FromKclValue::from_kcl_val(value))
1500                    .collect::<Option<_>>()
1501            }
1502        }
1503    };
1504}
1505
1506impl_from_kcl_for_vec!(FaceTag);
1507impl_from_kcl_for_vec!(crate::execution::EdgeCut);
1508impl_from_kcl_for_vec!(crate::execution::Metadata);
1509impl_from_kcl_for_vec!(super::fillet::EdgeReference);
1510impl_from_kcl_for_vec!(ExtrudeSurface);
1511
1512impl<'a> FromKclValue<'a> for SourceRange {
1513    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1514        let KclValue::MixedArray { value, meta: _ } = arg else {
1515            return None;
1516        };
1517        if value.len() != 3 {
1518            return None;
1519        }
1520        let v0 = value.first()?;
1521        let v1 = value.get(1)?;
1522        let v2 = value.get(2)?;
1523        Some(SourceRange::new(
1524            v0.as_usize()?,
1525            v1.as_usize()?,
1526            ModuleId::from_usize(v2.as_usize()?),
1527        ))
1528    }
1529}
1530
1531impl<'a> FromKclValue<'a> for crate::execution::Metadata {
1532    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1533        FromKclValue::from_kcl_val(arg).map(|sr| Self { source_range: sr })
1534    }
1535}
1536
1537impl<'a> FromKclValue<'a> for crate::execution::Solid {
1538    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1539        arg.as_solid().cloned()
1540    }
1541}
1542
1543impl<'a> FromKclValue<'a> for crate::execution::SolidOrSketchOrImportedGeometry {
1544    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1545        match arg {
1546            KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
1547            KclValue::Sketch { value } => Some(Self::SketchSet(vec![(**value).clone()])),
1548            KclValue::HomArray { value, .. } => {
1549                let mut solids = vec![];
1550                let mut sketches = vec![];
1551                for item in value {
1552                    match item {
1553                        KclValue::Solid { value } => solids.push((**value).clone()),
1554                        KclValue::Sketch { value } => sketches.push((**value).clone()),
1555                        _ => return None,
1556                    }
1557                }
1558                if !solids.is_empty() {
1559                    Some(Self::SolidSet(solids))
1560                } else {
1561                    Some(Self::SketchSet(sketches))
1562                }
1563            }
1564            KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
1565            _ => None,
1566        }
1567    }
1568}
1569
1570impl<'a> FromKclValue<'a> for super::sketch::SketchData {
1571    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1572        // Order is critical since PlaneData is a subset of Plane.
1573        let case1 = crate::execution::Plane::from_kcl_val;
1574        let case2 = super::sketch::PlaneData::from_kcl_val;
1575        let case3 = crate::execution::Solid::from_kcl_val;
1576        let case4 = <Vec<Solid>>::from_kcl_val;
1577        case1(arg)
1578            .map(Box::new)
1579            .map(Self::Plane)
1580            .or_else(|| case2(arg).map(Self::PlaneOrientation))
1581            .or_else(|| case3(arg).map(Box::new).map(Self::Solid))
1582            .or_else(|| case4(arg).map(|v| Box::new(v[0].clone())).map(Self::Solid))
1583    }
1584}
1585
1586impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin2d {
1587    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1588        // Case 1: predefined planes.
1589        if let Some(s) = arg.as_str() {
1590            return match s {
1591                "X" | "x" => Some(Self::X),
1592                "Y" | "y" => Some(Self::Y),
1593                "-X" | "-x" => Some(Self::NegX),
1594                "-Y" | "-y" => Some(Self::NegY),
1595                _ => None,
1596            };
1597        }
1598        // Case 2: custom planes.
1599        let obj = arg.as_object()?;
1600        let_field_of!(obj, custom, &KclObjectFields);
1601        let_field_of!(custom, origin);
1602        let_field_of!(custom, axis);
1603        Some(Self::Custom { axis, origin })
1604    }
1605}
1606
1607impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin3d {
1608    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1609        // Case 1: predefined planes.
1610        if let Some(s) = arg.as_str() {
1611            return match s {
1612                "X" | "x" => Some(Self::X),
1613                "Y" | "y" => Some(Self::Y),
1614                "Z" | "z" => Some(Self::Z),
1615                "-X" | "-x" => Some(Self::NegX),
1616                "-Y" | "-y" => Some(Self::NegY),
1617                "-Z" | "-z" => Some(Self::NegZ),
1618                _ => None,
1619            };
1620        }
1621        // Case 2: custom planes.
1622        let obj = arg.as_object()?;
1623        let_field_of!(obj, custom, &KclObjectFields);
1624        let_field_of!(custom, origin);
1625        let_field_of!(custom, axis);
1626        Some(Self::Custom { axis, origin })
1627    }
1628}
1629
1630impl<'a> FromKclValue<'a> for super::fillet::EdgeReference {
1631    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1632        let id = arg.as_uuid().map(Self::Uuid);
1633        let tag = || TagIdentifier::from_kcl_val(arg).map(Box::new).map(Self::Tag);
1634        id.or_else(tag)
1635    }
1636}
1637
1638impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrEdgeReference {
1639    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1640        let case1 = super::axis_or_reference::AxisAndOrigin2d::from_kcl_val;
1641        let case2 = super::fillet::EdgeReference::from_kcl_val;
1642        case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
1643    }
1644}
1645
1646impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrEdgeReference {
1647    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1648        let case1 = super::axis_or_reference::AxisAndOrigin3d::from_kcl_val;
1649        let case2 = super::fillet::EdgeReference::from_kcl_val;
1650        case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
1651    }
1652}
1653
1654impl<'a> FromKclValue<'a> for super::mirror::Mirror2dData {
1655    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1656        let obj = arg.as_object()?;
1657        let_field_of!(obj, axis);
1658        Some(Self { axis })
1659    }
1660}
1661
1662impl<'a> FromKclValue<'a> for super::sketch::AngledLineData {
1663    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1664        let case1 = |arg: &KclValue| {
1665            let obj = arg.as_object()?;
1666            let_field_of!(obj, angle);
1667            let_field_of!(obj, length);
1668            Some(Self::AngleAndLengthNamed { angle, length })
1669        };
1670        let case2 = |arg: &KclValue| {
1671            let array = arg.as_array()?;
1672            let ang = array.first()?.as_f64()?;
1673            let len = array.get(1)?.as_f64()?;
1674            Some(Self::AngleAndLengthPair([ang, len]))
1675        };
1676        case1(arg).or_else(|| case2(arg))
1677    }
1678}
1679
1680impl<'a> FromKclValue<'a> for i64 {
1681    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1682        match arg {
1683            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
1684            _ => None,
1685        }
1686    }
1687}
1688
1689impl<'a> FromKclValue<'a> for &'a str {
1690    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1691        let KclValue::String { value, meta: _ } = arg else {
1692            return None;
1693        };
1694        Some(value)
1695    }
1696}
1697
1698impl<'a> FromKclValue<'a> for &'a KclObjectFields {
1699    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1700        let KclValue::Object { value, meta: _ } = arg else {
1701            return None;
1702        };
1703        Some(value)
1704    }
1705}
1706
1707impl<'a> FromKclValue<'a> for uuid::Uuid {
1708    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1709        let KclValue::Uuid { value, meta: _ } = arg else {
1710            return None;
1711        };
1712        Some(*value)
1713    }
1714}
1715
1716impl<'a> FromKclValue<'a> for u32 {
1717    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1718        match arg {
1719            KclValue::Number { value, .. } => crate::try_f64_to_u32(*value),
1720            _ => None,
1721        }
1722    }
1723}
1724
1725impl<'a> FromKclValue<'a> for NonZeroU32 {
1726    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1727        u32::from_kcl_val(arg).and_then(|x| x.try_into().ok())
1728    }
1729}
1730
1731impl<'a> FromKclValue<'a> for u64 {
1732    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1733        match arg {
1734            KclValue::Number { value, .. } => crate::try_f64_to_u64(*value),
1735            _ => None,
1736        }
1737    }
1738}
1739impl<'a> FromKclValue<'a> for f64 {
1740    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1741        match arg {
1742            KclValue::Number { value, .. } => Some(*value),
1743            _ => None,
1744        }
1745    }
1746}
1747impl<'a> FromKclValue<'a> for TyF64 {
1748    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1749        match arg {
1750            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())),
1751            _ => None,
1752        }
1753    }
1754}
1755
1756impl<'a> FromKclValue<'a> for Sketch {
1757    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1758        let KclValue::Sketch { value } = arg else {
1759            return None;
1760        };
1761        Some(value.as_ref().to_owned())
1762    }
1763}
1764
1765impl<'a> FromKclValue<'a> for Helix {
1766    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1767        let KclValue::Helix { value } = arg else {
1768            return None;
1769        };
1770        Some(value.as_ref().to_owned())
1771    }
1772}
1773
1774impl<'a> FromKclValue<'a> for SweepPath {
1775    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1776        let case1 = Sketch::from_kcl_val;
1777        let case2 = <Vec<Sketch>>::from_kcl_val;
1778        let case3 = Helix::from_kcl_val;
1779        case1(arg)
1780            .map(Self::Sketch)
1781            .or_else(|| case2(arg).map(|arg0: Vec<Sketch>| Self::Sketch(arg0[0].clone())))
1782            .or_else(|| case3(arg).map(|arg0: Helix| Self::Helix(Box::new(arg0))))
1783    }
1784}
1785impl<'a> FromKclValue<'a> for String {
1786    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1787        let KclValue::String { value, meta: _ } = arg else {
1788            return None;
1789        };
1790        Some(value.to_owned())
1791    }
1792}
1793impl<'a> FromKclValue<'a> for crate::parsing::ast::types::KclNone {
1794    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1795        let KclValue::KclNone { value, meta: _ } = arg else {
1796            return None;
1797        };
1798        Some(value.to_owned())
1799    }
1800}
1801impl<'a> FromKclValue<'a> for bool {
1802    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1803        let KclValue::Bool { value, meta: _ } = arg else {
1804            return None;
1805        };
1806        Some(*value)
1807    }
1808}
1809
1810impl<'a> FromKclValue<'a> for Box<Solid> {
1811    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1812        let KclValue::Solid { value } = arg else {
1813            return None;
1814        };
1815        Some(value.to_owned())
1816    }
1817}
1818
1819impl<'a> FromKclValue<'a> for Vec<Solid> {
1820    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1821        let KclValue::HomArray { value, .. } = arg else {
1822            return None;
1823        };
1824        value.iter().map(Solid::from_kcl_val).collect()
1825    }
1826}
1827
1828impl<'a> FromKclValue<'a> for Vec<Sketch> {
1829    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1830        let KclValue::HomArray { value, .. } = arg else {
1831            return None;
1832        };
1833        value.iter().map(Sketch::from_kcl_val).collect()
1834    }
1835}
1836
1837impl<'a> FromKclValue<'a> for &'a FunctionSource {
1838    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1839        arg.get_function()
1840    }
1841}
1842
1843impl<'a> FromKclValue<'a> for SketchOrSurface {
1844    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1845        match arg {
1846            KclValue::Sketch { value: sg } => Some(Self::Sketch(sg.to_owned())),
1847            KclValue::Plane { value } => Some(Self::SketchSurface(SketchSurface::Plane(value.clone()))),
1848            KclValue::Face { value } => Some(Self::SketchSurface(SketchSurface::Face(value.clone()))),
1849            _ => None,
1850        }
1851    }
1852}
1853impl<'a> FromKclValue<'a> for SketchSurface {
1854    fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
1855        match arg {
1856            KclValue::Plane { value } => Some(Self::Plane(value.clone())),
1857            KclValue::Face { value } => Some(Self::Face(value.clone())),
1858            _ => None,
1859        }
1860    }
1861}
1862
1863impl From<Args> for Metadata {
1864    fn from(value: Args) -> Self {
1865        Self {
1866            source_range: value.source_range,
1867        }
1868    }
1869}
1870
1871impl From<Args> for Vec<Metadata> {
1872    fn from(value: Args) -> Self {
1873        vec![Metadata {
1874            source_range: value.source_range,
1875        }]
1876    }
1877}