Skip to main content

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, ModelingCmdMeta, 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        // Currently, frontend doesn't know if mirror2d will close the sketch or not.
46        // Track that information.
47        for sketch in starting_sketches.iter_mut() {
48            sketch.is_closed = crate::execution::ProfileClosed::Maybe;
49        }
50        return Ok(starting_sketches);
51    }
52
53    match axis {
54        Axis2dOrEdgeReference::Axis { direction, origin } => {
55            let resp = exec_state
56                .send_modeling_cmd(
57                    ModelingCmdMeta::from_args(exec_state, &args),
58                    ModelingCmd::from(
59                        mcmd::EntityMirror::builder()
60                            .ids(starting_sketches.iter().map(|sketch| sketch.id).collect())
61                            .axis(Point3d {
62                                x: direction[0].to_mm(),
63                                y: direction[1].to_mm(),
64                                z: 0.0,
65                            })
66                            .point(Point3d {
67                                x: LengthUnit(origin[0].to_mm()),
68                                y: LengthUnit(origin[1].to_mm()),
69                                z: LengthUnit(0.0),
70                            })
71                            .build(),
72                    ),
73                )
74                .await?;
75
76            if let OkWebSocketResponseData::Modeling {
77                modeling_response: OkModelingCmdResponse::EntityMirror(mirror_info),
78            } = &resp
79            {
80                let face_edge_info = &mirror_info.entity_face_edge_ids;
81
82                starting_sketches
83                    .iter_mut()
84                    .zip(face_edge_info.iter())
85                    .try_for_each(|(sketch, info)| {
86                        sketch.id = info.object_id;
87                        let first_edge = info.edges.first().copied();
88                        match first_edge {
89                            Some(edge) => sketch.mirror = Some(edge),
90                            None => {
91                                return Err(KclError::new_engine(KclErrorDetails::new(
92                                    "No edges found in mirror info".to_string(),
93                                    vec![args.source_range],
94                                )));
95                            }
96                        }
97                        // Currently, frontend doesn't know if mirror2d will close the sketch or not.
98                        // Track that information.
99                        sketch.is_closed = crate::execution::ProfileClosed::Maybe;
100                        Ok(())
101                    })?;
102            } else {
103                return Err(KclError::new_engine(KclErrorDetails::new(
104                    format!("EntityMirror response was not as expected: {resp:?}"),
105                    vec![args.source_range],
106                )));
107            };
108        }
109        Axis2dOrEdgeReference::Edge(edge) => {
110            let edge_id = edge.get_engine_id(exec_state, &args)?;
111
112            let resp = exec_state
113                .send_modeling_cmd(
114                    ModelingCmdMeta::from_args(exec_state, &args),
115                    ModelingCmd::from(
116                        mcmd::EntityMirrorAcrossEdge::builder()
117                            .ids(starting_sketches.iter().map(|sketch| sketch.id).collect())
118                            .edge_id(edge_id)
119                            .build(),
120                    ),
121                )
122                .await?;
123
124            if let OkWebSocketResponseData::Modeling {
125                modeling_response: OkModelingCmdResponse::EntityMirrorAcrossEdge(mirror_info),
126            } = &resp
127            {
128                let face_edge_info = &mirror_info.entity_face_edge_ids;
129
130                starting_sketches
131                    .iter_mut()
132                    .zip(face_edge_info.iter())
133                    .try_for_each(|(sketch, info)| {
134                        sketch.id = info.object_id;
135                        let first_edge = info.edges.first().copied();
136                        match first_edge {
137                            Some(edge) => sketch.mirror = Some(edge),
138                            None => {
139                                return Err(KclError::new_engine(KclErrorDetails::new(
140                                    "No edges found in mirror info".to_string(),
141                                    vec![args.source_range],
142                                )));
143                            }
144                        }
145                        Ok(())
146                    })?;
147            } else {
148                return Err(KclError::new_engine(KclErrorDetails::new(
149                    format!("EntityMirrorAcrossEdge response was not as expected: {resp:?}"),
150                    vec![args.source_range],
151                )));
152            };
153        }
154    }
155
156    Ok(starting_sketches)
157}