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, 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 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}