1use 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
19pub 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
35async 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 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 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}