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