kcl_lib/std/
mirror.rs

1//! Standard library mirror.
2
3use anyhow::Result;
4use kcmc::{each_cmd as mcmd, ModelingCmd};
5use kittycad_modeling_cmds::{
6    self as kcmc, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, output::EntityGetAllChildUuids,
7    shared::Point3d, websocket::OkWebSocketResponseData,
8};
9
10use crate::{
11    errors::{KclError, KclErrorDetails},
12    execution::{
13        types::{PrimitiveType, RuntimeType},
14        ExecState, KclValue, Sketch,
15    },
16    std::{axis_or_reference::Axis2dOrEdgeReference, Args},
17};
18
19/// Mirror a sketch.
20pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
21    let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
22    let axis = args.get_kw_arg_typed(
23        "axis",
24        &RuntimeType::Union(vec![
25            RuntimeType::Primitive(PrimitiveType::Edge),
26            RuntimeType::Primitive(PrimitiveType::Axis2d),
27        ]),
28        exec_state,
29    )?;
30
31    let sketches = inner_mirror_2d(sketches, axis, exec_state, args).await?;
32    Ok(sketches.into())
33}
34
35/// Mirror a sketch.
36async fn inner_mirror_2d(
37    sketches: Vec<Sketch>,
38    axis: Axis2dOrEdgeReference,
39    exec_state: &mut ExecState,
40    args: Args,
41) -> Result<Vec<Sketch>, KclError> {
42    let mut starting_sketches = sketches.clone();
43
44    // Update all to have a mirror.
45    starting_sketches.iter_mut().for_each(|sketch| {
46        sketch.mirror = Some(exec_state.next_uuid());
47    });
48
49    if args.ctx.no_engine_commands().await {
50        return Ok(starting_sketches);
51    }
52
53    match axis {
54        Axis2dOrEdgeReference::Axis { direction, origin } => {
55            args.batch_modeling_cmd(
56                exec_state.next_uuid(),
57                ModelingCmd::from(mcmd::EntityMirror {
58                    ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
59                    axis: Point3d {
60                        x: direction[0].to_mm(),
61                        y: direction[1].to_mm(),
62                        z: 0.0,
63                    },
64                    point: Point3d {
65                        x: LengthUnit(origin[0].to_mm()),
66                        y: LengthUnit(origin[1].to_mm()),
67                        z: LengthUnit(0.0),
68                    },
69                }),
70            )
71            .await?;
72        }
73        Axis2dOrEdgeReference::Edge(edge) => {
74            let edge_id = edge.get_engine_id(exec_state, &args)?;
75
76            args.batch_modeling_cmd(
77                exec_state.next_uuid(),
78                ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
79                    ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
80                    edge_id,
81                }),
82            )
83            .await?;
84        }
85    };
86
87    // After the mirror, get the first child uuid for the path.
88    // The "get extrusion face info" API call requires *any* edge on the sketch being extruded.
89    // But if you mirror2d a sketch these IDs might change so we need to get the children versus
90    // using the IDs we already have.
91    // We only do this with mirrors because otherwise it is a waste of a websocket call.
92    for sketch in &mut starting_sketches {
93        let response = args
94            .send_modeling_cmd(
95                exec_state.next_uuid(),
96                ModelingCmd::from(mcmd::EntityGetAllChildUuids { entity_id: sketch.id }),
97            )
98            .await?;
99        let OkWebSocketResponseData::Modeling {
100            modeling_response:
101                OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids { entity_ids: child_ids }),
102        } = response
103        else {
104            return Err(KclError::Internal(KclErrorDetails::new(
105                "Expected a successful response from EntityGetAllChildUuids".to_string(),
106                vec![args.source_range],
107            )));
108        };
109
110        if child_ids.len() >= 2 {
111            // The first child is the original sketch, the second is the mirrored sketch.
112            let child_id = child_ids[1];
113            sketch.mirror = Some(child_id);
114        } else {
115            return Err(KclError::Type(KclErrorDetails::new(
116                "Expected child uuids to be >= 2".to_string(),
117                vec![args.source_range],
118            )));
119        }
120    }
121
122    Ok(starting_sketches)
123}