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