1use anyhow::Result;
4use kcmc::{ModelingCmd, each_cmd as mcmd, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData};
5use kittycad_modeling_cmds as kcmc;
6use uuid::Uuid;
7
8use crate::{
9 SourceRange,
10 errors::{KclError, KclErrorDetails},
11 execution::{
12 ExecState, ExtrudeSurface, KclValue, ModelingCmdMeta, TagIdentifier,
13 types::{ArrayLen, RuntimeType},
14 },
15 std::{Args, sketch::FaceTag},
16};
17
18pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
20 let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
21
22 let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
23 Ok(KclValue::Uuid {
24 value: edge,
25 meta: vec![args.source_range.into()],
26 })
27}
28
29async fn inner_get_opposite_edge(
30 edge: TagIdentifier,
31 exec_state: &mut ExecState,
32 args: Args,
33) -> Result<Uuid, KclError> {
34 if args.ctx.no_engine_commands().await {
35 return Ok(exec_state.next_uuid());
36 }
37 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
38
39 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
40 let tagged_path_id = tagged_path.id;
41 let sketch_id = tagged_path.sketch;
42
43 let resp = exec_state
44 .send_modeling_cmd(
45 ModelingCmdMeta::from_args(exec_state, &args),
46 ModelingCmd::from(
47 mcmd::Solid3dGetOppositeEdge::builder()
48 .edge_id(tagged_path_id)
49 .object_id(sketch_id)
50 .face_id(face_id)
51 .build(),
52 ),
53 )
54 .await?;
55 let OkWebSocketResponseData::Modeling {
56 modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
57 } = &resp
58 else {
59 return Err(KclError::new_engine(KclErrorDetails::new(
60 format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {resp:?}"),
61 vec![args.source_range],
62 )));
63 };
64
65 Ok(opposite_edge.edge)
66}
67
68pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
70 let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
71
72 let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
73 Ok(KclValue::Uuid {
74 value: edge,
75 meta: vec![args.source_range.into()],
76 })
77}
78
79async fn inner_get_next_adjacent_edge(
80 edge: TagIdentifier,
81 exec_state: &mut ExecState,
82 args: Args,
83) -> Result<Uuid, KclError> {
84 if args.ctx.no_engine_commands().await {
85 return Ok(exec_state.next_uuid());
86 }
87 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
88
89 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
90 let tagged_path_id = tagged_path.id;
91 let sketch_id = tagged_path.sketch;
92
93 let resp = exec_state
94 .send_modeling_cmd(
95 ModelingCmdMeta::from_args(exec_state, &args),
96 ModelingCmd::from(
97 mcmd::Solid3dGetNextAdjacentEdge::builder()
98 .edge_id(tagged_path_id)
99 .object_id(sketch_id)
100 .face_id(face_id)
101 .build(),
102 ),
103 )
104 .await?;
105
106 let OkWebSocketResponseData::Modeling {
107 modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
108 } = &resp
109 else {
110 return Err(KclError::new_engine(KclErrorDetails::new(
111 format!("mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {resp:?}"),
112 vec![args.source_range],
113 )));
114 };
115
116 adjacent_edge.edge.ok_or_else(|| {
117 KclError::new_type(KclErrorDetails::new(
118 format!("No edge found next adjacent to tag: `{}`", edge.value),
119 vec![args.source_range],
120 ))
121 })
122}
123
124pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
126 let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
127
128 let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
129 Ok(KclValue::Uuid {
130 value: edge,
131 meta: vec![args.source_range.into()],
132 })
133}
134
135async fn inner_get_previous_adjacent_edge(
136 edge: TagIdentifier,
137 exec_state: &mut ExecState,
138 args: Args,
139) -> Result<Uuid, KclError> {
140 if args.ctx.no_engine_commands().await {
141 return Ok(exec_state.next_uuid());
142 }
143 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
144
145 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
146 let tagged_path_id = tagged_path.id;
147 let sketch_id = tagged_path.sketch;
148
149 let resp = exec_state
150 .send_modeling_cmd(
151 ModelingCmdMeta::from_args(exec_state, &args),
152 ModelingCmd::from(
153 mcmd::Solid3dGetPrevAdjacentEdge::builder()
154 .edge_id(tagged_path_id)
155 .object_id(sketch_id)
156 .face_id(face_id)
157 .build(),
158 ),
159 )
160 .await?;
161 let OkWebSocketResponseData::Modeling {
162 modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
163 } = &resp
164 else {
165 return Err(KclError::new_engine(KclErrorDetails::new(
166 format!("mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {resp:?}"),
167 vec![args.source_range],
168 )));
169 };
170
171 adjacent_edge.edge.ok_or_else(|| {
172 KclError::new_type(KclErrorDetails::new(
173 format!("No edge found previous adjacent to tag: `{}`", edge.value),
174 vec![args.source_range],
175 ))
176 })
177}
178
179pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
181 let faces: Vec<FaceTag> = args.get_kw_arg(
182 "faces",
183 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Known(2)),
184 exec_state,
185 )?;
186
187 fn into_tag(face: FaceTag, source_range: SourceRange) -> Result<TagIdentifier, KclError> {
188 match face {
189 FaceTag::StartOrEnd(_) => Err(KclError::new_type(KclErrorDetails::new(
190 "getCommonEdge requires a tagged face, it cannot use `START` or `END` faces".to_owned(),
191 vec![source_range],
192 ))),
193 FaceTag::Tag(tag_identifier) => Ok(*tag_identifier),
194 }
195 }
196
197 let [face1, face2]: [FaceTag; 2] = faces.try_into().map_err(|_: Vec<FaceTag>| {
198 KclError::new_type(KclErrorDetails::new(
199 "getCommonEdge requires exactly two tags for faces".to_owned(),
200 vec![args.source_range],
201 ))
202 })?;
203
204 let face1 = into_tag(face1, args.source_range)?;
205 let face2 = into_tag(face2, args.source_range)?;
206
207 let edge = inner_get_common_edge(face1, face2, exec_state, args.clone()).await?;
208 Ok(KclValue::Uuid {
209 value: edge,
210 meta: vec![args.source_range.into()],
211 })
212}
213
214async fn inner_get_common_edge(
215 face1: TagIdentifier,
216 face2: TagIdentifier,
217 exec_state: &mut ExecState,
218 args: Args,
219) -> Result<Uuid, KclError> {
220 let id = exec_state.next_uuid();
221 if args.ctx.no_engine_commands().await {
222 return Ok(id);
223 }
224
225 let first_face_id = args.get_adjacent_face_to_tag(exec_state, &face1, false).await?;
226 let second_face_id = args.get_adjacent_face_to_tag(exec_state, &face2, false).await?;
227
228 let first_tagged_path = args.get_tag_engine_info(exec_state, &face1)?.clone();
229 let second_tagged_path = args.get_tag_engine_info(exec_state, &face2)?;
230
231 if first_tagged_path.sketch != second_tagged_path.sketch {
232 return Err(KclError::new_type(KclErrorDetails::new(
233 "getCommonEdge requires the faces to be in the same original sketch".to_string(),
234 vec![args.source_range],
235 )));
236 }
237
238 if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
243 exec_state
244 .flush_batch(ModelingCmdMeta::from_args(exec_state, &args), true)
245 .await?;
246 } else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
247 exec_state
248 .flush_batch(ModelingCmdMeta::from_args(exec_state, &args), true)
249 .await?;
250 }
251
252 let resp = exec_state
253 .send_modeling_cmd(
254 ModelingCmdMeta::from_args_id(exec_state, &args, id),
255 ModelingCmd::from(
256 mcmd::Solid3dGetCommonEdge::builder()
257 .object_id(first_tagged_path.sketch)
258 .face_ids([first_face_id, second_face_id])
259 .build(),
260 ),
261 )
262 .await?;
263 let OkWebSocketResponseData::Modeling {
264 modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
265 } = &resp
266 else {
267 return Err(KclError::new_engine(KclErrorDetails::new(
268 format!("mcmd::Solid3dGetCommonEdge response was not as expected: {resp:?}"),
269 vec![args.source_range],
270 )));
271 };
272
273 common_edge.edge.ok_or_else(|| {
274 KclError::new_type(KclErrorDetails::new(
275 format!(
276 "No common edge was found between `{}` and `{}`",
277 face1.value, face2.value
278 ),
279 vec![args.source_range],
280 ))
281 })
282}