1use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{each_cmd as mcmd, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData, ModelingCmd};
6use kittycad_modeling_cmds as kcmc;
7use uuid::Uuid;
8
9use crate::{
10 errors::{KclError, KclErrorDetails},
11 execution::{types::RuntimeType, ExecState, ExtrudeSurface, KclValue, TagIdentifier},
12 std::Args,
13};
14
15pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
17 let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
18
19 let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
20 Ok(KclValue::Uuid {
21 value: edge,
22 meta: vec![args.source_range.into()],
23 })
24}
25
26#[stdlib {
55 name = "getOppositeEdge",
56 unlabeled_first = true,
57 args = {
58 edge = { docs = "The tag of the edge you want to find the opposite edge of." },
59 },
60 tags = ["sketch"]
61}]
62async fn inner_get_opposite_edge(
63 edge: TagIdentifier,
64 exec_state: &mut ExecState,
65 args: Args,
66) -> Result<Uuid, KclError> {
67 if args.ctx.no_engine_commands().await {
68 return Ok(exec_state.next_uuid());
69 }
70 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
71
72 let id = exec_state.next_uuid();
73 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
74
75 let resp = args
76 .send_modeling_cmd(
77 id,
78 ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
79 edge_id: tagged_path.id,
80 object_id: tagged_path.sketch,
81 face_id,
82 }),
83 )
84 .await?;
85 let OkWebSocketResponseData::Modeling {
86 modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
87 } = &resp
88 else {
89 return Err(KclError::Engine(KclErrorDetails::new(
90 format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
91 vec![args.source_range],
92 )));
93 };
94
95 Ok(opposite_edge.edge)
96}
97
98pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
100 let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
101
102 let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
103 Ok(KclValue::Uuid {
104 value: edge,
105 meta: vec![args.source_range.into()],
106 })
107}
108
109#[stdlib {
138 name = "getNextAdjacentEdge",
139 unlabeled_first = true,
140 args = {
141 edge = { docs = "The tag of the edge you want to find the next adjacent edge of." },
142 },
143 tags = ["sketch"]
144}]
145async fn inner_get_next_adjacent_edge(
146 edge: TagIdentifier,
147 exec_state: &mut ExecState,
148 args: Args,
149) -> Result<Uuid, KclError> {
150 if args.ctx.no_engine_commands().await {
151 return Ok(exec_state.next_uuid());
152 }
153 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
154
155 let id = exec_state.next_uuid();
156 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
157
158 let resp = args
159 .send_modeling_cmd(
160 id,
161 ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
162 edge_id: tagged_path.id,
163 object_id: tagged_path.sketch,
164 face_id,
165 }),
166 )
167 .await?;
168
169 let OkWebSocketResponseData::Modeling {
170 modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
171 } = &resp
172 else {
173 return Err(KclError::Engine(KclErrorDetails::new(
174 format!(
175 "mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}",
176 resp
177 ),
178 vec![args.source_range],
179 )));
180 };
181
182 adjacent_edge.edge.ok_or_else(|| {
183 KclError::Type(KclErrorDetails::new(
184 format!("No edge found next adjacent to tag: `{}`", edge.value),
185 vec![args.source_range],
186 ))
187 })
188}
189
190pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
192 let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::tag_identifier(), exec_state)?;
193
194 let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
195 Ok(KclValue::Uuid {
196 value: edge,
197 meta: vec![args.source_range.into()],
198 })
199}
200
201#[stdlib {
230 name = "getPreviousAdjacentEdge",
231 unlabeled_first = true,
232 args = {
233 edge = { docs = "The tag of the edge you want to find the previous adjacent edge of." },
234 },
235 tags = ["sketch"]
236}]
237async fn inner_get_previous_adjacent_edge(
238 edge: TagIdentifier,
239 exec_state: &mut ExecState,
240 args: Args,
241) -> Result<Uuid, KclError> {
242 if args.ctx.no_engine_commands().await {
243 return Ok(exec_state.next_uuid());
244 }
245 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
246
247 let id = exec_state.next_uuid();
248 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
249
250 let resp = args
251 .send_modeling_cmd(
252 id,
253 ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
254 edge_id: tagged_path.id,
255 object_id: tagged_path.sketch,
256 face_id,
257 }),
258 )
259 .await?;
260 let OkWebSocketResponseData::Modeling {
261 modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
262 } = &resp
263 else {
264 return Err(KclError::Engine(KclErrorDetails::new(
265 format!(
266 "mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}",
267 resp
268 ),
269 vec![args.source_range],
270 )));
271 };
272
273 adjacent_edge.edge.ok_or_else(|| {
274 KclError::Type(KclErrorDetails::new(
275 format!("No edge found previous adjacent to tag: `{}`", edge.value),
276 vec![args.source_range],
277 ))
278 })
279}
280
281pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
283 let faces: Vec<TagIdentifier> = args.get_kw_arg("faces")?;
284
285 let edge = inner_get_common_edge(faces, exec_state, args.clone()).await?;
286 Ok(KclValue::Uuid {
287 value: edge,
288 meta: vec![args.source_range.into()],
289 })
290}
291
292#[stdlib {
316 name = "getCommonEdge",
317 feature_tree_operation = false,
318 unlabeled_first = false,
319 args = {
320 faces = { docs = "The tags of the faces you want to find the common edge between" },
321 },
322 tags = ["sketch"]
323}]
324async fn inner_get_common_edge(
325 faces: Vec<TagIdentifier>,
326 exec_state: &mut ExecState,
327 args: Args,
328) -> Result<Uuid, KclError> {
329 let id = exec_state.next_uuid();
330 if args.ctx.no_engine_commands().await {
331 return Ok(id);
332 }
333
334 if faces.len() != 2 {
335 return Err(KclError::Type(KclErrorDetails::new(
336 "getCommonEdge requires exactly two tags for faces".to_string(),
337 vec![args.source_range],
338 )));
339 }
340 let first_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[0], false).await?;
341 let second_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[1], false).await?;
342
343 let first_tagged_path = args.get_tag_engine_info(exec_state, &faces[0])?.clone();
344 let second_tagged_path = args.get_tag_engine_info(exec_state, &faces[1])?;
345
346 if first_tagged_path.sketch != second_tagged_path.sketch {
347 return Err(KclError::Type(KclErrorDetails::new(
348 "getCommonEdge requires the faces to be in the same original sketch".to_string(),
349 vec![args.source_range],
350 )));
351 }
352
353 if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
358 args.ctx.engine.flush_batch(true, args.source_range).await?;
359 } else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
360 args.ctx.engine.flush_batch(true, args.source_range).await?;
361 }
362
363 let resp = args
364 .send_modeling_cmd(
365 id,
366 ModelingCmd::from(mcmd::Solid3dGetCommonEdge {
367 object_id: first_tagged_path.sketch,
368 face_ids: [first_face_id, second_face_id],
369 }),
370 )
371 .await?;
372 let OkWebSocketResponseData::Modeling {
373 modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
374 } = &resp
375 else {
376 return Err(KclError::Engine(KclErrorDetails::new(
377 format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp),
378 vec![args.source_range],
379 )));
380 };
381
382 common_edge.edge.ok_or_else(|| {
383 KclError::Type(KclErrorDetails::new(
384 format!(
385 "No common edge was found between `{}` and `{}`",
386 faces[0].value, faces[1].value
387 ),
388 vec![args.source_range],
389 ))
390 })
391}