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::{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 tag: TagIdentifier = args.get_data()?;
18
19 let edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?;
20 Ok(KclValue::Uuid {
21 value: edge,
22 meta: vec![args.source_range.into()],
23 })
24}
25
26#[stdlib {
54 name = "getOppositeEdge",
55}]
56async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> {
57 if args.ctx.no_engine_commands().await {
58 return Ok(exec_state.next_uuid());
59 }
60 let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
61
62 let id = exec_state.next_uuid();
63 let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
64
65 let resp = args
66 .send_modeling_cmd(
67 id,
68 ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
69 edge_id: tagged_path.id,
70 object_id: tagged_path.sketch,
71 face_id,
72 }),
73 )
74 .await?;
75 let OkWebSocketResponseData::Modeling {
76 modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
77 } = &resp
78 else {
79 return Err(KclError::Engine(KclErrorDetails {
80 message: format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
81 source_ranges: vec![args.source_range],
82 }));
83 };
84
85 Ok(opposite_edge.edge)
86}
87
88pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
90 let tag: TagIdentifier = args.get_data()?;
91
92 let edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?;
93 Ok(KclValue::Uuid {
94 value: edge,
95 meta: vec![args.source_range.into()],
96 })
97}
98
99#[stdlib {
127 name = "getNextAdjacentEdge",
128}]
129async fn inner_get_next_adjacent_edge(
130 tag: TagIdentifier,
131 exec_state: &mut ExecState,
132 args: Args,
133) -> Result<Uuid, KclError> {
134 if args.ctx.no_engine_commands().await {
135 return Ok(exec_state.next_uuid());
136 }
137 let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
138
139 let id = exec_state.next_uuid();
140 let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
141
142 let resp = args
143 .send_modeling_cmd(
144 id,
145 ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
146 edge_id: tagged_path.id,
147 object_id: tagged_path.sketch,
148 face_id,
149 }),
150 )
151 .await?;
152
153 let OkWebSocketResponseData::Modeling {
154 modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
155 } = &resp
156 else {
157 return Err(KclError::Engine(KclErrorDetails {
158 message: format!(
159 "mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}",
160 resp
161 ),
162 source_ranges: vec![args.source_range],
163 }));
164 };
165
166 adjacent_edge.edge.ok_or_else(|| {
167 KclError::Type(KclErrorDetails {
168 message: format!("No edge found next adjacent to tag: `{}`", tag.value),
169 source_ranges: vec![args.source_range],
170 })
171 })
172}
173
174pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
176 let tag: TagIdentifier = args.get_data()?;
177
178 let edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?;
179 Ok(KclValue::Uuid {
180 value: edge,
181 meta: vec![args.source_range.into()],
182 })
183}
184
185#[stdlib {
213 name = "getPreviousAdjacentEdge",
214}]
215async fn inner_get_previous_adjacent_edge(
216 tag: TagIdentifier,
217 exec_state: &mut ExecState,
218 args: Args,
219) -> Result<Uuid, KclError> {
220 if args.ctx.no_engine_commands().await {
221 return Ok(exec_state.next_uuid());
222 }
223 let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
224
225 let id = exec_state.next_uuid();
226 let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
227
228 let resp = args
229 .send_modeling_cmd(
230 id,
231 ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
232 edge_id: tagged_path.id,
233 object_id: tagged_path.sketch,
234 face_id,
235 }),
236 )
237 .await?;
238 let OkWebSocketResponseData::Modeling {
239 modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
240 } = &resp
241 else {
242 return Err(KclError::Engine(KclErrorDetails {
243 message: format!(
244 "mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}",
245 resp
246 ),
247 source_ranges: vec![args.source_range],
248 }));
249 };
250
251 adjacent_edge.edge.ok_or_else(|| {
252 KclError::Type(KclErrorDetails {
253 message: format!("No edge found previous adjacent to tag: `{}`", tag.value),
254 source_ranges: vec![args.source_range],
255 })
256 })
257}
258
259pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
261 let faces: Vec<TagIdentifier> = args.get_kw_arg("faces")?;
262
263 let edge = inner_get_common_edge(faces, exec_state, args.clone()).await?;
264 Ok(KclValue::Uuid {
265 value: edge,
266 meta: vec![args.source_range.into()],
267 })
268}
269
270#[stdlib {
294 name = "getCommonEdge",
295 feature_tree_operation = false,
296 keywords = true,
297 unlabeled_first = false,
298 args = {
299 faces = { docs = "The tags of the faces you want to find the common edge between" },
300 },
301}]
302async fn inner_get_common_edge(
303 faces: Vec<TagIdentifier>,
304 exec_state: &mut ExecState,
305 args: Args,
306) -> Result<Uuid, KclError> {
307 let id = exec_state.next_uuid();
308 if args.ctx.no_engine_commands().await {
309 return Ok(id);
310 }
311
312 if faces.len() != 2 {
313 return Err(KclError::Type(KclErrorDetails {
314 message: "getCommonEdge requires exactly two tags for faces".to_string(),
315 source_ranges: vec![args.source_range],
316 }));
317 }
318 let first_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[0], false).await?;
319 let second_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[1], false).await?;
320
321 let first_tagged_path = args.get_tag_engine_info(exec_state, &faces[0])?.clone();
322 let second_tagged_path = args.get_tag_engine_info(exec_state, &faces[1])?;
323
324 if first_tagged_path.sketch != second_tagged_path.sketch {
325 return Err(KclError::Type(KclErrorDetails {
326 message: "getCommonEdge requires the faces to be in the same original sketch".to_string(),
327 source_ranges: vec![args.source_range],
328 }));
329 }
330
331 if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
336 args.ctx.engine.flush_batch(true, args.source_range).await?;
337 } else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
338 args.ctx.engine.flush_batch(true, args.source_range).await?;
339 }
340
341 let resp = args
342 .send_modeling_cmd(
343 id,
344 ModelingCmd::from(mcmd::Solid3dGetCommonEdge {
345 object_id: first_tagged_path.sketch,
346 face_ids: [first_face_id, second_face_id],
347 }),
348 )
349 .await?;
350 let OkWebSocketResponseData::Modeling {
351 modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
352 } = &resp
353 else {
354 return Err(KclError::Engine(KclErrorDetails {
355 message: format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp),
356 source_ranges: vec![args.source_range],
357 }));
358 };
359
360 common_edge.edge.ok_or_else(|| {
361 KclError::Type(KclErrorDetails {
362 message: format!(
363 "No common edge was found between `{}` and `{}`",
364 faces[0].value, faces[1].value
365 ),
366 source_ranges: vec![args.source_range],
367 })
368 })
369}