1use anyhow::Result;
4use kcmc::ModelingCmd;
5use kcmc::each_cmd as mcmd;
6use kittycad_modeling_cmds::length_unit::LengthUnit;
7use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
8use kittycad_modeling_cmds::shared::MirrorAcross;
9use kittycad_modeling_cmds::shared::Point3d;
10use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
11use kittycad_modeling_cmds::{self as kcmc};
12
13use crate::errors::KclError;
14use crate::errors::KclErrorDetails;
15use crate::execution::ArtifactId;
16use crate::execution::ExecState;
17use crate::execution::KclValue;
18use crate::execution::ModelingCmdMeta;
19use crate::execution::Sketch;
20use crate::execution::Solid;
21use crate::execution::types::PrimitiveType;
22use crate::execution::types::RuntimeType;
23use crate::std::Args;
24use crate::std::axis_or_reference::Axis2dOrEdgeReference;
25use crate::std::axis_or_reference::MirrorAcross3d;
26
27pub async fn mirror_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
29 let bodies = args.get_unlabeled_kw_arg("bodies", &RuntimeType::solids(), exec_state)?;
30 let across = args.get_kw_arg(
31 "across",
32 &RuntimeType::Union(vec![
33 RuntimeType::Primitive(PrimitiveType::Edge),
34 RuntimeType::Primitive(PrimitiveType::Axis3d),
35 RuntimeType::Primitive(PrimitiveType::Plane),
36 RuntimeType::segment(),
37 ]),
38 exec_state,
39 )?;
40
41 let bodies = inner_mirror_3d(bodies, across, exec_state, args).await?;
42 Ok(bodies.into())
43}
44
45async fn inner_mirror_3d(
47 bodies: Vec<Solid>,
48 across: MirrorAcross3d,
49 exec_state: &mut ExecState,
50 args: Args,
51) -> Result<Vec<Solid>, KclError> {
52 let mut mirrored_bodies = bodies.clone();
53
54 if args.ctx.no_engine_commands().await {
55 return Ok(mirrored_bodies);
56 }
57
58 exec_state
59 .flush_batch_for_solids(ModelingCmdMeta::from_args(exec_state, &args), &bodies)
60 .await?;
61
62 let across = match across {
63 MirrorAcross3d::Axis { direction, origin } => MirrorAcross::Axis {
64 axis: Point3d {
65 x: direction[0].to_mm(),
66 y: direction[1].to_mm(),
67 z: direction[2].to_mm(),
68 },
69 point: Point3d {
70 x: LengthUnit(origin[0].to_mm()),
71 y: LengthUnit(origin[1].to_mm()),
72 z: LengthUnit(origin[2].to_mm()),
73 },
74 },
75 MirrorAcross3d::Edge(edge) => MirrorAcross::Edge {
76 id: edge.get_engine_id(exec_state, &args)?,
77 },
78 MirrorAcross3d::Plane(mut plane) => {
79 if plane.is_uninitialized() {
80 crate::std::sketch::ensure_sketch_plane_in_engine(
81 &mut plane,
82 exec_state,
83 &args.ctx,
84 args.source_range,
85 args.node_path.clone(),
86 )
87 .await?;
88 }
89 MirrorAcross::Plane { id: plane.id }
90 }
91 };
92
93 let resp = exec_state
94 .send_modeling_cmd(
95 ModelingCmdMeta::from_args(exec_state, &args),
96 ModelingCmd::from(
97 mcmd::EntityMirrorAcross::builder()
98 .ids(bodies.iter().map(|body| body.id).collect())
99 .across(across)
100 .build(),
101 ),
102 )
103 .await?;
104
105 let OkWebSocketResponseData::Modeling {
106 modeling_response: OkModelingCmdResponse::EntityMirrorAcross(mirror_info),
107 } = &resp
108 else {
109 return Err(KclError::new_engine(KclErrorDetails::new(
110 format!("EntityMirrorAcross response was not as expected: {resp:?}"),
111 vec![args.source_range],
112 )));
113 };
114
115 if mirrored_bodies.len() != mirror_info.entity_face_edge_ids.len() {
116 return Err(KclError::new_engine(KclErrorDetails::new(
117 format!(
118 "EntityMirrorAcross response had {} mirrored bodies for {} input bodies",
119 mirror_info.entity_face_edge_ids.len(),
120 mirrored_bodies.len()
121 ),
122 vec![args.source_range],
123 )));
124 }
125
126 for (body, info) in mirrored_bodies.iter_mut().zip(mirror_info.entity_face_edge_ids.iter()) {
127 body.id = info.object_id;
128 body.artifact_id = ArtifactId::new(info.object_id);
129 if let Some(sketch) = body.sketch_mut() {
130 sketch.id = info.object_id;
131 }
132 }
133
134 Ok(mirrored_bodies)
135}
136
137pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
139 let sketches = args.get_unlabeled_kw_arg("sketches", &RuntimeType::sketches(), exec_state)?;
140 let axis = args.get_kw_arg(
141 "axis",
142 &RuntimeType::Union(vec![
143 RuntimeType::Primitive(PrimitiveType::Edge),
144 RuntimeType::Primitive(PrimitiveType::Axis2d),
145 RuntimeType::segment(),
146 ]),
147 exec_state,
148 )?;
149
150 let sketches = inner_mirror_2d(sketches, axis, exec_state, args).await?;
151 Ok(sketches.into())
152}
153
154async fn inner_mirror_2d(
156 sketches: Vec<Sketch>,
157 axis: Axis2dOrEdgeReference,
158 exec_state: &mut ExecState,
159 args: Args,
160) -> Result<Vec<Sketch>, KclError> {
161 let mut starting_sketches = sketches.clone();
162
163 if args.ctx.no_engine_commands().await {
164 for sketch in starting_sketches.iter_mut() {
167 sketch.is_closed = crate::execution::ProfileClosed::Maybe;
168 }
169 return Ok(starting_sketches);
170 }
171
172 match axis {
173 Axis2dOrEdgeReference::Axis { direction, origin } => {
174 let resp = exec_state
175 .send_modeling_cmd(
176 ModelingCmdMeta::from_args(exec_state, &args),
177 ModelingCmd::from(
178 mcmd::EntityMirror::builder()
179 .ids(starting_sketches.iter().map(|sketch| sketch.id).collect())
180 .axis(Point3d {
181 x: direction[0].to_mm(),
182 y: direction[1].to_mm(),
183 z: 0.0,
184 })
185 .point(Point3d {
186 x: LengthUnit(origin[0].to_mm()),
187 y: LengthUnit(origin[1].to_mm()),
188 z: LengthUnit(0.0),
189 })
190 .build(),
191 ),
192 )
193 .await?;
194
195 if let OkWebSocketResponseData::Modeling {
196 modeling_response: OkModelingCmdResponse::EntityMirror(mirror_info),
197 } = &resp
198 {
199 let face_edge_info = &mirror_info.entity_face_edge_ids;
200
201 starting_sketches
202 .iter_mut()
203 .zip(face_edge_info.iter())
204 .try_for_each(|(sketch, info)| {
205 sketch.id = info.object_id;
206 let first_edge = info.edges.first().copied();
207 match first_edge {
208 Some(edge) => sketch.mirror = Some(edge),
209 None => {
210 return Err(KclError::new_engine(KclErrorDetails::new(
211 "No edges found in mirror info".to_string(),
212 vec![args.source_range],
213 )));
214 }
215 }
216 sketch.is_closed = crate::execution::ProfileClosed::Maybe;
219 Ok(())
220 })?;
221 } else {
222 return Err(KclError::new_engine(KclErrorDetails::new(
223 format!("EntityMirror response was not as expected: {resp:?}"),
224 vec![args.source_range],
225 )));
226 };
227 }
228 Axis2dOrEdgeReference::Edge(edge) => {
229 let edge_id = edge.get_engine_id(exec_state, &args)?;
230
231 let resp = exec_state
232 .send_modeling_cmd(
233 ModelingCmdMeta::from_args(exec_state, &args),
234 ModelingCmd::from(
235 mcmd::EntityMirrorAcrossEdge::builder()
236 .ids(starting_sketches.iter().map(|sketch| sketch.id).collect())
237 .edge_id(edge_id)
238 .build(),
239 ),
240 )
241 .await?;
242
243 if let OkWebSocketResponseData::Modeling {
244 modeling_response: OkModelingCmdResponse::EntityMirrorAcrossEdge(mirror_info),
245 } = &resp
246 {
247 let face_edge_info = &mirror_info.entity_face_edge_ids;
248
249 starting_sketches
250 .iter_mut()
251 .zip(face_edge_info.iter())
252 .try_for_each(|(sketch, info)| {
253 sketch.id = info.object_id;
254 let first_edge = info.edges.first().copied();
255 match first_edge {
256 Some(edge) => sketch.mirror = Some(edge),
257 None => {
258 return Err(KclError::new_engine(KclErrorDetails::new(
259 "No edges found in mirror info".to_string(),
260 vec![args.source_range],
261 )));
262 }
263 }
264 Ok(())
265 })?;
266 } else {
267 return Err(KclError::new_engine(KclErrorDetails::new(
268 format!("EntityMirrorAcrossEdge response was not as expected: {resp:?}"),
269 vec![args.source_range],
270 )));
271 };
272 }
273 }
274
275 Ok(starting_sketches)
276}