kcl_lib/std/
extrude.rs

1//! Functions related to extruding.
2
3use std::collections::HashMap;
4
5use anyhow::Result;
6use kcmc::shared::Point3d as KPoint3d; // Point3d is already defined in this pkg, to impl ts_rs traits.
7use kcmc::{
8    ModelingCmd, each_cmd as mcmd,
9    length_unit::LengthUnit,
10    ok_response::OkModelingCmdResponse,
11    output::ExtrusionFaceInfo,
12    shared::{ExtrudeReference, ExtrusionFaceCapType, Opposite},
13    websocket::{ModelingCmdReq, OkWebSocketResponseData},
14};
15use kittycad_modeling_cmds::{
16    self as kcmc,
17    shared::{Angle, BodyType, ExtrudeMethod, Point2d},
18};
19use uuid::Uuid;
20
21use super::{DEFAULT_TOLERANCE_MM, args::TyF64, utils::point_to_mm};
22use crate::{
23    errors::{KclError, KclErrorDetails},
24    execution::{
25        ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Path, ProfileClosed, Sketch,
26        SketchSurface, Solid,
27        types::{PrimitiveType, RuntimeType},
28    },
29    parsing::ast::types::TagNode,
30    std::{Args, axis_or_reference::Point3dAxis3dOrGeometryReference},
31};
32
33/// Extrudes by a given amount.
34pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
35    let sketches = args.get_unlabeled_kw_arg("sketches", &RuntimeType::sketches(), exec_state)?;
36    let length: Option<TyF64> = args.get_kw_arg_opt("length", &RuntimeType::length(), exec_state)?;
37    let to = args.get_kw_arg_opt(
38        "to",
39        &RuntimeType::Union(vec![
40            RuntimeType::point3d(),
41            RuntimeType::Primitive(PrimitiveType::Axis3d),
42            RuntimeType::Primitive(PrimitiveType::Edge),
43            RuntimeType::plane(),
44            RuntimeType::Primitive(PrimitiveType::Face),
45            RuntimeType::sketch(),
46            RuntimeType::Primitive(PrimitiveType::Solid),
47            RuntimeType::tagged_edge(),
48            RuntimeType::tagged_face(),
49        ]),
50        exec_state,
51    )?;
52    let symmetric = args.get_kw_arg_opt("symmetric", &RuntimeType::bool(), exec_state)?;
53    let bidirectional_length: Option<TyF64> =
54        args.get_kw_arg_opt("bidirectionalLength", &RuntimeType::length(), exec_state)?;
55    let tag_start = args.get_kw_arg_opt("tagStart", &RuntimeType::tag_decl(), exec_state)?;
56    let tag_end = args.get_kw_arg_opt("tagEnd", &RuntimeType::tag_decl(), exec_state)?;
57    let twist_angle: Option<TyF64> = args.get_kw_arg_opt("twistAngle", &RuntimeType::degrees(), exec_state)?;
58    let twist_angle_step: Option<TyF64> = args.get_kw_arg_opt("twistAngleStep", &RuntimeType::degrees(), exec_state)?;
59    let twist_center: Option<[TyF64; 2]> = args.get_kw_arg_opt("twistCenter", &RuntimeType::point2d(), exec_state)?;
60    let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
61    let method: Option<String> = args.get_kw_arg_opt("method", &RuntimeType::string(), exec_state)?;
62    let body_type: Option<BodyType> = args.get_kw_arg_opt("bodyType", &RuntimeType::string(), exec_state)?;
63
64    let result = inner_extrude(
65        sketches,
66        length,
67        to,
68        symmetric,
69        bidirectional_length,
70        tag_start,
71        tag_end,
72        twist_angle,
73        twist_angle_step,
74        twist_center,
75        tolerance,
76        method,
77        body_type,
78        exec_state,
79        args,
80    )
81    .await?;
82
83    Ok(result.into())
84}
85
86#[allow(clippy::too_many_arguments)]
87async fn inner_extrude(
88    sketches: Vec<Sketch>,
89    length: Option<TyF64>,
90    to: Option<Point3dAxis3dOrGeometryReference>,
91    symmetric: Option<bool>,
92    bidirectional_length: Option<TyF64>,
93    tag_start: Option<TagNode>,
94    tag_end: Option<TagNode>,
95    twist_angle: Option<TyF64>,
96    twist_angle_step: Option<TyF64>,
97    twist_center: Option<[TyF64; 2]>,
98    tolerance: Option<TyF64>,
99    method: Option<String>,
100    body_type: Option<BodyType>,
101    exec_state: &mut ExecState,
102    args: Args,
103) -> Result<Vec<Solid>, KclError> {
104    let body_type = body_type.unwrap_or_default();
105
106    // Extrude the element(s).
107    let mut solids = Vec::new();
108    let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM));
109
110    let extrude_method = match method.as_deref() {
111        Some("new" | "NEW") => ExtrudeMethod::New,
112        Some("merge" | "MERGE") => ExtrudeMethod::Merge,
113        None => ExtrudeMethod::default(),
114        Some(other) => {
115            return Err(KclError::new_semantic(KclErrorDetails::new(
116                format!("Unknown merge method {other}, try using `MERGE` or `NEW`"),
117                vec![args.source_range],
118            )));
119        }
120    };
121
122    if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
123        return Err(KclError::new_semantic(KclErrorDetails::new(
124            "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
125                .to_owned(),
126            vec![args.source_range],
127        )));
128    }
129
130    if (length.is_some() || twist_angle.is_some()) && to.is_some() {
131        return Err(KclError::new_semantic(KclErrorDetails::new(
132            "You cannot give `length` or `twist` params with the `to` param, you have to choose one or the other"
133                .to_owned(),
134            vec![args.source_range],
135        )));
136    }
137
138    let bidirection = bidirectional_length.map(|l| LengthUnit(l.to_mm()));
139
140    let opposite = match (symmetric, bidirection) {
141        (Some(true), _) => Opposite::Symmetric,
142        (None, None) => Opposite::None,
143        (Some(false), None) => Opposite::None,
144        (None, Some(length)) => Opposite::Other(length),
145        (Some(false), Some(length)) => Opposite::Other(length),
146    };
147
148    for sketch in &sketches {
149        let id = exec_state.next_uuid();
150        let cmd = match (&twist_angle, &twist_angle_step, &twist_center, length.clone(), &to) {
151            (Some(angle), angle_step, center, Some(length), None) => {
152                let center = center.clone().map(point_to_mm).map(Point2d::from).unwrap_or_default();
153                let total_rotation_angle = Angle::from_degrees(angle.to_degrees(exec_state, args.source_range));
154                let angle_step_size = Angle::from_degrees(
155                    angle_step
156                        .clone()
157                        .map(|a| a.to_degrees(exec_state, args.source_range))
158                        .unwrap_or(15.0),
159                );
160                ModelingCmd::from(mcmd::TwistExtrude {
161                    target: sketch.id.into(),
162                    distance: LengthUnit(length.to_mm()),
163                    faces: Default::default(),
164                    center_2d: center,
165                    total_rotation_angle,
166                    angle_step_size,
167                    tolerance,
168                    body_type,
169                })
170            }
171            (None, None, None, Some(length), None) => ModelingCmd::from(mcmd::Extrude {
172                target: sketch.id.into(),
173                distance: LengthUnit(length.to_mm()),
174                faces: Default::default(),
175                opposite: opposite.clone(),
176                extrude_method,
177                body_type,
178            }),
179            (None, None, None, None, Some(to)) => match to {
180                Point3dAxis3dOrGeometryReference::Point(point) => ModelingCmd::from(mcmd::ExtrudeToReference {
181                    target: sketch.id.into(),
182                    reference: ExtrudeReference::Point {
183                        point: KPoint3d {
184                            x: LengthUnit(point[0].to_mm()),
185                            y: LengthUnit(point[1].to_mm()),
186                            z: LengthUnit(point[2].to_mm()),
187                        },
188                    },
189                    faces: Default::default(),
190                    extrude_method,
191                    body_type,
192                }),
193                Point3dAxis3dOrGeometryReference::Axis { direction, origin } => {
194                    ModelingCmd::from(mcmd::ExtrudeToReference {
195                        target: sketch.id.into(),
196                        reference: ExtrudeReference::Axis {
197                            axis: KPoint3d {
198                                x: direction[0].to_mm(),
199                                y: direction[1].to_mm(),
200                                z: direction[2].to_mm(),
201                            },
202                            point: KPoint3d {
203                                x: LengthUnit(origin[0].to_mm()),
204                                y: LengthUnit(origin[1].to_mm()),
205                                z: LengthUnit(origin[2].to_mm()),
206                            },
207                        },
208                        faces: Default::default(),
209                        extrude_method,
210                        body_type,
211                    })
212                }
213                Point3dAxis3dOrGeometryReference::Plane(plane) => {
214                    let plane_id = if plane.value == crate::exec::PlaneType::Uninit {
215                        if plane.info.origin.units.is_none() {
216                            return Err(KclError::new_semantic(KclErrorDetails::new(
217                                "Origin of plane has unknown units".to_string(),
218                                vec![args.source_range],
219                            )));
220                        }
221                        let sketch_plane = crate::std::sketch::make_sketch_plane_from_orientation(
222                            plane.clone().info.into_plane_data(),
223                            exec_state,
224                            &args,
225                        )
226                        .await?;
227                        sketch_plane.id
228                    } else {
229                        plane.id
230                    };
231                    ModelingCmd::from(mcmd::ExtrudeToReference {
232                        target: sketch.id.into(),
233                        reference: ExtrudeReference::EntityReference { entity_id: plane_id },
234                        faces: Default::default(),
235                        extrude_method,
236                        body_type,
237                    })
238                }
239                Point3dAxis3dOrGeometryReference::Edge(edge_ref) => {
240                    let edge_id = edge_ref.get_engine_id(exec_state, &args)?;
241                    ModelingCmd::from(mcmd::ExtrudeToReference {
242                        target: sketch.id.into(),
243                        reference: ExtrudeReference::EntityReference { entity_id: edge_id },
244                        faces: Default::default(),
245                        extrude_method,
246                        body_type,
247                    })
248                }
249                Point3dAxis3dOrGeometryReference::Face(face_tag) => {
250                    let face_id = face_tag.get_face_id_from_tag(exec_state, &args, false).await?;
251                    ModelingCmd::from(mcmd::ExtrudeToReference {
252                        target: sketch.id.into(),
253                        reference: ExtrudeReference::EntityReference { entity_id: face_id },
254                        faces: Default::default(),
255                        extrude_method,
256                        body_type,
257                    })
258                }
259                Point3dAxis3dOrGeometryReference::Sketch(sketch_ref) => ModelingCmd::from(mcmd::ExtrudeToReference {
260                    target: sketch.id.into(),
261                    reference: ExtrudeReference::EntityReference {
262                        entity_id: sketch_ref.id,
263                    },
264                    faces: Default::default(),
265                    extrude_method,
266                    body_type,
267                }),
268                Point3dAxis3dOrGeometryReference::Solid(solid) => ModelingCmd::from(mcmd::ExtrudeToReference {
269                    target: sketch.id.into(),
270                    reference: ExtrudeReference::EntityReference { entity_id: solid.id },
271                    faces: Default::default(),
272                    extrude_method,
273                    body_type,
274                }),
275                Point3dAxis3dOrGeometryReference::TaggedEdgeOrFace(tag) => {
276                    let tagged_edge_or_face = args.get_tag_engine_info(exec_state, tag)?;
277                    let tagged_edge_or_face_id = tagged_edge_or_face.id;
278                    ModelingCmd::from(mcmd::ExtrudeToReference {
279                        target: sketch.id.into(),
280                        reference: ExtrudeReference::EntityReference {
281                            entity_id: tagged_edge_or_face_id,
282                        },
283                        faces: Default::default(),
284                        extrude_method,
285                        body_type,
286                    })
287                }
288            },
289            (Some(_), _, _, None, None) => {
290                return Err(KclError::new_semantic(KclErrorDetails::new(
291                    "The `length` parameter must be provided when using twist angle for extrusion.".to_owned(),
292                    vec![args.source_range],
293                )));
294            }
295            (_, _, _, None, None) => {
296                return Err(KclError::new_semantic(KclErrorDetails::new(
297                    "Either `length` or `to` parameter must be provided for extrusion.".to_owned(),
298                    vec![args.source_range],
299                )));
300            }
301            (_, _, _, Some(_), Some(_)) => {
302                return Err(KclError::new_semantic(KclErrorDetails::new(
303                    "You cannot give both `length` and `to` params, you have to choose one or the other".to_owned(),
304                    vec![args.source_range],
305                )));
306            }
307            (_, _, _, _, _) => {
308                return Err(KclError::new_semantic(KclErrorDetails::new(
309                    "Invalid combination of parameters for extrusion.".to_owned(),
310                    vec![args.source_range],
311                )));
312            }
313        };
314        let cmds = sketch.build_sketch_mode_cmds(exec_state, ModelingCmdReq { cmd_id: id.into(), cmd });
315        exec_state
316            .batch_modeling_cmds(ModelingCmdMeta::from_args_id(exec_state, &args, id), &cmds)
317            .await?;
318
319        solids.push(
320            do_post_extrude(
321                sketch,
322                id.into(),
323                false,
324                &NamedCapTags {
325                    start: tag_start.as_ref(),
326                    end: tag_end.as_ref(),
327                },
328                extrude_method,
329                exec_state,
330                &args,
331                None,
332                None,
333                body_type,
334            )
335            .await?,
336        );
337    }
338
339    Ok(solids)
340}
341
342#[derive(Debug, Default)]
343pub(crate) struct NamedCapTags<'a> {
344    pub start: Option<&'a TagNode>,
345    pub end: Option<&'a TagNode>,
346}
347
348#[allow(clippy::too_many_arguments)]
349pub(crate) async fn do_post_extrude<'a>(
350    sketch: &Sketch,
351    solid_id: ArtifactId,
352    sectional: bool,
353    named_cap_tags: &'a NamedCapTags<'a>,
354    extrude_method: ExtrudeMethod,
355    exec_state: &mut ExecState,
356    args: &Args,
357    edge_id: Option<Uuid>,
358    clone_id_map: Option<&HashMap<Uuid, Uuid>>, // old sketch id -> new sketch id
359    body_type: BodyType,
360) -> Result<Solid, KclError> {
361    // Bring the object to the front of the scene.
362    // See: https://github.com/KittyCAD/modeling-app/issues/806
363    exec_state
364        .batch_modeling_cmd(
365            ModelingCmdMeta::from_args(exec_state, args),
366            ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
367        )
368        .await?;
369
370    let any_edge_id = if let Some(edge_id) = sketch.mirror {
371        edge_id
372    } else if let Some(id) = edge_id {
373        id
374    } else {
375        // The "get extrusion face info" API call requires *any* edge on the sketch being extruded.
376        // So, let's just use the first one.
377        let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else {
378            return Err(KclError::new_type(KclErrorDetails::new(
379                "Expected a non-empty sketch".to_owned(),
380                vec![args.source_range],
381            )));
382        };
383        any_edge_id
384    };
385
386    // If the sketch is a clone, we will use the original info to get the extrusion face info.
387    let mut extrusion_info_edge_id = any_edge_id;
388    if sketch.clone.is_some() && clone_id_map.is_some() {
389        extrusion_info_edge_id = if let Some(clone_map) = clone_id_map {
390            if let Some(new_edge_id) = clone_map.get(&extrusion_info_edge_id) {
391                *new_edge_id
392            } else {
393                extrusion_info_edge_id
394            }
395        } else {
396            any_edge_id
397        };
398    }
399
400    let mut sketch = sketch.clone();
401    match body_type {
402        BodyType::Solid => {
403            sketch.is_closed = ProfileClosed::Explicitly;
404        }
405        BodyType::Surface => {}
406    }
407
408    // If we were sketching on a face, we need the original face id.
409    if let SketchSurface::Face(ref face) = sketch.on {
410        // If we are creating a new body we need to preserve its new id.
411        if extrude_method != ExtrudeMethod::New {
412            sketch.id = face.solid.sketch.id;
413        }
414    }
415
416    // Similarly, if the sketch is a clone, we need to use the original sketch id to get the extrusion face info.
417    let sketch_id = if let Some(cloned_from) = sketch.clone
418        && clone_id_map.is_some()
419    {
420        cloned_from
421    } else {
422        sketch.id
423    };
424
425    let solid3d_info = exec_state
426        .send_modeling_cmd(
427            ModelingCmdMeta::from_args(exec_state, args),
428            ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
429                edge_id: extrusion_info_edge_id,
430                object_id: sketch_id,
431            }),
432        )
433        .await?;
434
435    let face_infos = if let OkWebSocketResponseData::Modeling {
436        modeling_response: OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(data),
437    } = solid3d_info
438    {
439        data.faces
440    } else {
441        vec![]
442    };
443
444    // Only do this if we need the artifact graph.
445    #[cfg(feature = "artifact-graph")]
446    {
447        // Getting the ids of a sectional sweep does not work well and we cannot guarantee that
448        // any of these call will not just fail.
449        if !sectional {
450            exec_state
451                .batch_modeling_cmd(
452                    ModelingCmdMeta::from_args(exec_state, args),
453                    ModelingCmd::from(mcmd::Solid3dGetAdjacencyInfo {
454                        object_id: sketch.id,
455                        edge_id: any_edge_id,
456                    }),
457                )
458                .await?;
459        }
460    }
461
462    let Faces {
463        sides: mut face_id_map,
464        start_cap_id,
465        end_cap_id,
466    } = analyze_faces(exec_state, args, face_infos).await;
467
468    // If this is a clone, we will use the clone_id_map to map the face info from the original sketch to the clone sketch.
469    if sketch.clone.is_some()
470        && let Some(clone_id_map) = clone_id_map
471    {
472        face_id_map = face_id_map
473            .into_iter()
474            .filter_map(|(k, v)| {
475                let fe_key = clone_id_map.get(&k)?;
476                let fe_value = clone_id_map.get(&(v?)).copied();
477                Some((*fe_key, fe_value))
478            })
479            .collect::<HashMap<Uuid, Option<Uuid>>>();
480    }
481
482    // Iterate over the sketch.value array and add face_id to GeoMeta
483    let no_engine_commands = args.ctx.no_engine_commands().await;
484    let mut new_value: Vec<ExtrudeSurface> = Vec::with_capacity(sketch.paths.len() + sketch.inner_paths.len() + 2);
485    let outer_surfaces = sketch.paths.iter().flat_map(|path| {
486        if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
487            surface_of(path, *actual_face_id)
488        } else if no_engine_commands {
489            crate::log::logln!(
490                "No face ID found for path ID {:?}, but in no-engine-commands mode, so faking it",
491                path.get_base().geo_meta.id
492            );
493            // Only pre-populate the extrude surface if we are in mock mode.
494            fake_extrude_surface(exec_state, path)
495        } else if sketch.clone.is_some()
496            && let Some(clone_map) = clone_id_map
497        {
498            let new_path = clone_map.get(&(path.get_base().geo_meta.id));
499
500            if let Some(new_path) = new_path {
501                match face_id_map.get(new_path) {
502                    Some(Some(actual_face_id)) => clone_surface_of(path, *new_path, *actual_face_id),
503                    _ => {
504                        let actual_face_id = face_id_map.iter().find_map(|(key, value)| {
505                            if let Some(value) = value {
506                                if value == new_path { Some(key) } else { None }
507                            } else {
508                                None
509                            }
510                        });
511                        match actual_face_id {
512                            Some(actual_face_id) => clone_surface_of(path, *new_path, *actual_face_id),
513                            None => {
514                                crate::log::logln!("No face ID found for clone path ID {:?}, so skipping it", new_path);
515                                None
516                            }
517                        }
518                    }
519                }
520            } else {
521                None
522            }
523        } else {
524            crate::log::logln!(
525                "No face ID found for path ID {:?}, and not in no-engine-commands mode, so skipping it",
526                path.get_base().geo_meta.id
527            );
528            None
529        }
530    });
531
532    new_value.extend(outer_surfaces);
533    let inner_surfaces = sketch.inner_paths.iter().flat_map(|path| {
534        if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
535            surface_of(path, *actual_face_id)
536        } else if no_engine_commands {
537            // Only pre-populate the extrude surface if we are in mock mode.
538            fake_extrude_surface(exec_state, path)
539        } else {
540            None
541        }
542    });
543    new_value.extend(inner_surfaces);
544
545    // Add the tags for the start or end caps.
546    if let Some(tag_start) = named_cap_tags.start {
547        let Some(start_cap_id) = start_cap_id else {
548            return Err(KclError::new_type(KclErrorDetails::new(
549                format!(
550                    "Expected a start cap ID for tag `{}` for extrusion of sketch {:?}",
551                    tag_start.name, sketch.id
552                ),
553                vec![args.source_range],
554            )));
555        };
556
557        new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
558            face_id: start_cap_id,
559            tag: Some(tag_start.clone()),
560            geo_meta: GeoMeta {
561                id: start_cap_id,
562                metadata: args.source_range.into(),
563            },
564        }));
565    }
566    if let Some(tag_end) = named_cap_tags.end {
567        let Some(end_cap_id) = end_cap_id else {
568            return Err(KclError::new_type(KclErrorDetails::new(
569                format!(
570                    "Expected an end cap ID for tag `{}` for extrusion of sketch {:?}",
571                    tag_end.name, sketch.id
572                ),
573                vec![args.source_range],
574            )));
575        };
576
577        new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
578            face_id: end_cap_id,
579            tag: Some(tag_end.clone()),
580            geo_meta: GeoMeta {
581                id: end_cap_id,
582                metadata: args.source_range.into(),
583            },
584        }));
585    }
586
587    Ok(Solid {
588        // Ok so you would think that the id would be the id of the solid,
589        // that we passed in to the function, but it's actually the id of the
590        // sketch.
591        //
592        // Why? Because when you extrude a sketch, the engine lets the solid absorb the
593        // sketch's ID. So the solid should take over the sketch's ID.
594        id: sketch.id,
595        artifact_id: solid_id,
596        value: new_value,
597        meta: sketch.meta.clone(),
598        units: sketch.units,
599        sectional,
600        sketch,
601        start_cap_id,
602        end_cap_id,
603        edge_cuts: vec![],
604    })
605}
606
607#[derive(Default)]
608struct Faces {
609    /// Maps curve ID to face ID for each side.
610    sides: HashMap<Uuid, Option<Uuid>>,
611    /// Top face ID.
612    end_cap_id: Option<Uuid>,
613    /// Bottom face ID.
614    start_cap_id: Option<Uuid>,
615}
616
617async fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
618    let mut faces = Faces {
619        sides: HashMap::with_capacity(face_infos.len()),
620        ..Default::default()
621    };
622    if args.ctx.no_engine_commands().await {
623        // Create fake IDs for start and end caps, to make extrudes mock-execute safe
624        faces.start_cap_id = Some(exec_state.next_uuid());
625        faces.end_cap_id = Some(exec_state.next_uuid());
626    }
627    for face_info in face_infos {
628        match face_info.cap {
629            ExtrusionFaceCapType::Bottom => faces.start_cap_id = face_info.face_id,
630            ExtrusionFaceCapType::Top => faces.end_cap_id = face_info.face_id,
631            ExtrusionFaceCapType::Both => {
632                faces.end_cap_id = face_info.face_id;
633                faces.start_cap_id = face_info.face_id;
634            }
635            ExtrusionFaceCapType::None => {
636                if let Some(curve_id) = face_info.curve_id {
637                    faces.sides.insert(curve_id, face_info.face_id);
638                }
639            }
640        }
641    }
642    faces
643}
644fn surface_of(path: &Path, actual_face_id: Uuid) -> Option<ExtrudeSurface> {
645    match path {
646        Path::Arc { .. }
647        | Path::TangentialArc { .. }
648        | Path::TangentialArcTo { .. }
649        // TODO: (bc) fix me
650        | Path::Ellipse { .. }
651        | Path::Conic {.. }
652        | Path::Circle { .. }
653        | Path::CircleThreePoint { .. } => {
654            let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
655                face_id: actual_face_id,
656                tag: path.get_base().tag.clone(),
657                geo_meta: GeoMeta {
658                    id: path.get_base().geo_meta.id,
659                    metadata: path.get_base().geo_meta.metadata,
660                },
661            });
662            Some(extrude_surface)
663        }
664        Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
665            let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
666                face_id: actual_face_id,
667                tag: path.get_base().tag.clone(),
668                geo_meta: GeoMeta {
669                    id: path.get_base().geo_meta.id,
670                    metadata: path.get_base().geo_meta.metadata,
671                },
672            });
673            Some(extrude_surface)
674        }
675        Path::ArcThreePoint { .. } => {
676            let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
677                face_id: actual_face_id,
678                tag: path.get_base().tag.clone(),
679                geo_meta: GeoMeta {
680                    id: path.get_base().geo_meta.id,
681                    metadata: path.get_base().geo_meta.metadata,
682                },
683            });
684            Some(extrude_surface)
685        }
686    }
687}
688
689fn clone_surface_of(path: &Path, clone_path_id: Uuid, actual_face_id: Uuid) -> Option<ExtrudeSurface> {
690    match path {
691        Path::Arc { .. }
692        | Path::TangentialArc { .. }
693        | Path::TangentialArcTo { .. }
694        // TODO: (gserena) fix me
695        | Path::Ellipse { .. }
696        | Path::Conic {.. }
697        | Path::Circle { .. }
698        | Path::CircleThreePoint { .. } => {
699            let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
700                face_id: actual_face_id,
701                tag: path.get_base().tag.clone(),
702                geo_meta: GeoMeta {
703                    id: clone_path_id,
704                    metadata: path.get_base().geo_meta.metadata,
705                },
706            });
707            Some(extrude_surface)
708        }
709        Path::Base { .. } | Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } => {
710            let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
711                face_id: actual_face_id,
712                tag: path.get_base().tag.clone(),
713                geo_meta: GeoMeta {
714                    id: clone_path_id,
715                    metadata: path.get_base().geo_meta.metadata,
716                },
717            });
718            Some(extrude_surface)
719        }
720        Path::ArcThreePoint { .. } => {
721            let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
722                face_id: actual_face_id,
723                tag: path.get_base().tag.clone(),
724                geo_meta: GeoMeta {
725                    id: clone_path_id,
726                    metadata: path.get_base().geo_meta.metadata,
727                },
728            });
729            Some(extrude_surface)
730        }
731    }
732}
733
734/// Create a fake extrude surface to report for mock execution, when there's no engine response.
735fn fake_extrude_surface(exec_state: &mut ExecState, path: &Path) -> Option<ExtrudeSurface> {
736    let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
737        // pushing this values with a fake face_id to make extrudes mock-execute safe
738        face_id: exec_state.next_uuid(),
739        tag: path.get_base().tag.clone(),
740        geo_meta: GeoMeta {
741            id: path.get_base().geo_meta.id,
742            metadata: path.get_base().geo_meta.metadata,
743        },
744    });
745    Some(extrude_surface)
746}