kcl_lib/std/
mirror.rs

1//! Standard library mirror.
2
3use anyhow::Result;
4use kcmc::{ModelingCmd, each_cmd as mcmd};
5use kittycad_modeling_cmds::{
6    self as kcmc, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, shared::Point3d,
7    websocket::OkWebSocketResponseData,
8};
9
10use crate::{
11    errors::{KclError, KclErrorDetails},
12    execution::{
13        ExecState, KclValue, Sketch,
14        types::{PrimitiveType, RuntimeType},
15    },
16    std::{Args, axis_or_reference::Axis2dOrEdgeReference},
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("sketches", &RuntimeType::sketches(), exec_state)?;
22    let axis = args.get_kw_arg(
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    if args.ctx.no_engine_commands().await {
45        return Ok(starting_sketches);
46    }
47
48    match axis {
49        Axis2dOrEdgeReference::Axis { direction, origin } => {
50            let resp = exec_state
51                .send_modeling_cmd(
52                    (&args).into(),
53                    ModelingCmd::from(mcmd::EntityMirror {
54                        ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
55                        axis: Point3d {
56                            x: direction[0].to_mm(),
57                            y: direction[1].to_mm(),
58                            z: 0.0,
59                        },
60                        point: Point3d {
61                            x: LengthUnit(origin[0].to_mm()),
62                            y: LengthUnit(origin[1].to_mm()),
63                            z: LengthUnit(0.0),
64                        },
65                    }),
66                )
67                .await?;
68
69            if let OkWebSocketResponseData::Modeling {
70                modeling_response: OkModelingCmdResponse::EntityMirror(mirror_info),
71            } = &resp
72            {
73                let face_edge_info = &mirror_info.entity_face_edge_ids;
74
75                starting_sketches
76                    .iter_mut()
77                    .zip(face_edge_info.iter())
78                    .try_for_each(|(sketch, info)| {
79                        sketch.id = info.object_id;
80                        let first_edge = info.edges.first().copied();
81                        match first_edge {
82                            Some(edge) => sketch.mirror = Some(edge),
83                            None => {
84                                return Err(KclError::new_engine(KclErrorDetails::new(
85                                    "No edges found in mirror info".to_string(),
86                                    vec![args.source_range],
87                                )));
88                            }
89                        }
90                        Ok(())
91                    })?;
92            } else {
93                return Err(KclError::new_engine(KclErrorDetails::new(
94                    format!("EntityMirror response was not as expected: {resp:?}"),
95                    vec![args.source_range],
96                )));
97            };
98        }
99        Axis2dOrEdgeReference::Edge(edge) => {
100            let edge_id = edge.get_engine_id(exec_state, &args)?;
101
102            let resp = exec_state
103                .send_modeling_cmd(
104                    (&args).into(),
105                    ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
106                        ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
107                        edge_id,
108                    }),
109                )
110                .await?;
111
112            if let OkWebSocketResponseData::Modeling {
113                modeling_response: OkModelingCmdResponse::EntityMirrorAcrossEdge(mirror_info),
114            } = &resp
115            {
116                let face_edge_info = &mirror_info.entity_face_edge_ids;
117
118                starting_sketches
119                    .iter_mut()
120                    .zip(face_edge_info.iter())
121                    .try_for_each(|(sketch, info)| {
122                        sketch.id = info.object_id;
123                        let first_edge = info.edges.first().copied();
124                        match first_edge {
125                            Some(edge) => sketch.mirror = Some(edge),
126                            None => {
127                                return Err(KclError::new_engine(KclErrorDetails::new(
128                                    "No edges found in mirror info".to_string(),
129                                    vec![args.source_range],
130                                )));
131                            }
132                        }
133                        Ok(())
134                    })?;
135            } else {
136                return Err(KclError::new_engine(KclErrorDetails::new(
137                    format!("EntityMirrorAcrossEdge response was not as expected: {resp:?}"),
138                    vec![args.source_range],
139                )));
140            };
141        }
142    }
143
144    Ok(starting_sketches)
145}