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::Point3d;
9use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
10use kittycad_modeling_cmds::{self as kcmc};
11
12use crate::errors::KclError;
13use crate::errors::KclErrorDetails;
14use crate::execution::ExecState;
15use crate::execution::KclValue;
16use crate::execution::ModelingCmdMeta;
17use crate::execution::Sketch;
18use crate::execution::types::PrimitiveType;
19use crate::execution::types::RuntimeType;
20use crate::std::Args;
21use crate::std::axis_or_reference::Axis2dOrEdgeReference;
22
23pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
25 let sketches = args.get_unlabeled_kw_arg("sketches", &RuntimeType::sketches(), exec_state)?;
26 let axis = args.get_kw_arg(
27 "axis",
28 &RuntimeType::Union(vec![
29 RuntimeType::Primitive(PrimitiveType::Edge),
30 RuntimeType::Primitive(PrimitiveType::Axis2d),
31 RuntimeType::segment(),
32 ]),
33 exec_state,
34 )?;
35
36 let sketches = inner_mirror_2d(sketches, axis, exec_state, args).await?;
37 Ok(sketches.into())
38}
39
40async fn inner_mirror_2d(
42 sketches: Vec<Sketch>,
43 axis: Axis2dOrEdgeReference,
44 exec_state: &mut ExecState,
45 args: Args,
46) -> Result<Vec<Sketch>, KclError> {
47 let mut starting_sketches = sketches.clone();
48
49 if args.ctx.no_engine_commands().await {
50 for sketch in starting_sketches.iter_mut() {
53 sketch.is_closed = crate::execution::ProfileClosed::Maybe;
54 }
55 return Ok(starting_sketches);
56 }
57
58 match axis {
59 Axis2dOrEdgeReference::Axis { direction, origin } => {
60 let resp = exec_state
61 .send_modeling_cmd(
62 ModelingCmdMeta::from_args(exec_state, &args),
63 ModelingCmd::from(
64 mcmd::EntityMirror::builder()
65 .ids(starting_sketches.iter().map(|sketch| sketch.id).collect())
66 .axis(Point3d {
67 x: direction[0].to_mm(),
68 y: direction[1].to_mm(),
69 z: 0.0,
70 })
71 .point(Point3d {
72 x: LengthUnit(origin[0].to_mm()),
73 y: LengthUnit(origin[1].to_mm()),
74 z: LengthUnit(0.0),
75 })
76 .build(),
77 ),
78 )
79 .await?;
80
81 if let OkWebSocketResponseData::Modeling {
82 modeling_response: OkModelingCmdResponse::EntityMirror(mirror_info),
83 } = &resp
84 {
85 let face_edge_info = &mirror_info.entity_face_edge_ids;
86
87 starting_sketches
88 .iter_mut()
89 .zip(face_edge_info.iter())
90 .try_for_each(|(sketch, info)| {
91 sketch.id = info.object_id;
92 let first_edge = info.edges.first().copied();
93 match first_edge {
94 Some(edge) => sketch.mirror = Some(edge),
95 None => {
96 return Err(KclError::new_engine(KclErrorDetails::new(
97 "No edges found in mirror info".to_string(),
98 vec![args.source_range],
99 )));
100 }
101 }
102 sketch.is_closed = crate::execution::ProfileClosed::Maybe;
105 Ok(())
106 })?;
107 } else {
108 return Err(KclError::new_engine(KclErrorDetails::new(
109 format!("EntityMirror response was not as expected: {resp:?}"),
110 vec![args.source_range],
111 )));
112 };
113 }
114 Axis2dOrEdgeReference::Edge(edge) => {
115 let edge_id = edge.get_engine_id(exec_state, &args)?;
116
117 let resp = exec_state
118 .send_modeling_cmd(
119 ModelingCmdMeta::from_args(exec_state, &args),
120 ModelingCmd::from(
121 mcmd::EntityMirrorAcrossEdge::builder()
122 .ids(starting_sketches.iter().map(|sketch| sketch.id).collect())
123 .edge_id(edge_id)
124 .build(),
125 ),
126 )
127 .await?;
128
129 if let OkWebSocketResponseData::Modeling {
130 modeling_response: OkModelingCmdResponse::EntityMirrorAcrossEdge(mirror_info),
131 } = &resp
132 {
133 let face_edge_info = &mirror_info.entity_face_edge_ids;
134
135 starting_sketches
136 .iter_mut()
137 .zip(face_edge_info.iter())
138 .try_for_each(|(sketch, info)| {
139 sketch.id = info.object_id;
140 let first_edge = info.edges.first().copied();
141 match first_edge {
142 Some(edge) => sketch.mirror = Some(edge),
143 None => {
144 return Err(KclError::new_engine(KclErrorDetails::new(
145 "No edges found in mirror info".to_string(),
146 vec![args.source_range],
147 )));
148 }
149 }
150 Ok(())
151 })?;
152 } else {
153 return Err(KclError::new_engine(KclErrorDetails::new(
154 format!("EntityMirrorAcrossEdge response was not as expected: {resp:?}"),
155 vec![args.source_range],
156 )));
157 };
158 }
159 }
160
161 Ok(starting_sketches)
162}