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 (&args).into(),
46 ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
47 edge_id: tagged_path_id,
48 object_id: sketch_id,
49 face_id,
50 }),
51 )
52 .await?;
53 let OkWebSocketResponseData::Modeling {
54 modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
55 } = &resp
56 else {
57 return Err(KclError::new_engine(KclErrorDetails::new(
58 format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {resp:?}"),
59 vec![args.source_range],
60 )));
61 };
62
63 Ok(opposite_edge.edge)
64}
65
66pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
68 let input_edge = args.get_unlabeled_kw_arg("edge", &RuntimeType::tagged_edge(), exec_state)?;
69
70 let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
71 Ok(KclValue::Uuid {
72 value: edge,
73 meta: vec![args.source_range.into()],
74 })
75}
76
77async fn inner_get_next_adjacent_edge(
78 edge: TagIdentifier,
79 exec_state: &mut ExecState,
80 args: Args,
81) -> Result<Uuid, KclError> {
82 if args.ctx.no_engine_commands().await {
83 return Ok(exec_state.next_uuid());
84 }
85 let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
86
87 let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
88 let tagged_path_id = tagged_path.id;
89 let sketch_id = tagged_path.sketch;
90
91 let resp = exec_state
92 .send_modeling_cmd(
93 (&args).into(),
94 ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
95 edge_id: tagged_path_id,
96 object_id: sketch_id,
97 face_id,
98 }),
99 )
100 .await?;
101
102 let OkWebSocketResponseData::Modeling {
103 modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
104 } = &resp
105 else {
106 return Err(KclError::new_engine(KclErrorDetails::new(
107 format!("mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {resp:?}"),
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("edge", &RuntimeType::tagged_edge(), 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 tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
142 let tagged_path_id = tagged_path.id;
143 let sketch_id = tagged_path.sketch;
144
145 let resp = exec_state
146 .send_modeling_cmd(
147 (&args).into(),
148 ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
149 edge_id: tagged_path_id,
150 object_id: sketch_id,
151 face_id,
152 }),
153 )
154 .await?;
155 let OkWebSocketResponseData::Modeling {
156 modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
157 } = &resp
158 else {
159 return Err(KclError::new_engine(KclErrorDetails::new(
160 format!("mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {resp:?}"),
161 vec![args.source_range],
162 )));
163 };
164
165 adjacent_edge.edge.ok_or_else(|| {
166 KclError::new_type(KclErrorDetails::new(
167 format!("No edge found previous adjacent to tag: `{}`", edge.value),
168 vec![args.source_range],
169 ))
170 })
171}
172
173pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
175 let mut faces: Vec<FaceTag> = args.get_kw_arg(
176 "faces",
177 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Known(2)),
178 exec_state,
179 )?;
180
181 if faces.len() != 2 {
182 return Err(KclError::new_type(KclErrorDetails::new(
183 "getCommonEdge requires exactly two tags for faces".to_owned(),
184 vec![args.source_range],
185 )));
186 }
187
188 fn into_tag(face: FaceTag, source_range: SourceRange) -> Result<TagIdentifier, KclError> {
189 match face {
190 FaceTag::StartOrEnd(_) => Err(KclError::new_type(KclErrorDetails::new(
191 "getCommonEdge requires a tagged face, it cannot use `START` or `END` faces".to_owned(),
192 vec![source_range],
193 ))),
194 FaceTag::Tag(tag_identifier) => Ok(*tag_identifier),
195 }
196 }
197
198 let face2 = into_tag(faces.pop().unwrap(), args.source_range)?;
199 let face1 = into_tag(faces.pop().unwrap(), args.source_range)?;
200
201 let edge = inner_get_common_edge(face1, face2, exec_state, args.clone()).await?;
202 Ok(KclValue::Uuid {
203 value: edge,
204 meta: vec![args.source_range.into()],
205 })
206}
207
208async fn inner_get_common_edge(
209 face1: TagIdentifier,
210 face2: TagIdentifier,
211 exec_state: &mut ExecState,
212 args: Args,
213) -> Result<Uuid, KclError> {
214 let id = exec_state.next_uuid();
215 if args.ctx.no_engine_commands().await {
216 return Ok(id);
217 }
218
219 let first_face_id = args.get_adjacent_face_to_tag(exec_state, &face1, false).await?;
220 let second_face_id = args.get_adjacent_face_to_tag(exec_state, &face2, false).await?;
221
222 let first_tagged_path = args.get_tag_engine_info(exec_state, &face1)?.clone();
223 let second_tagged_path = args.get_tag_engine_info(exec_state, &face2)?;
224
225 if first_tagged_path.sketch != second_tagged_path.sketch {
226 return Err(KclError::new_type(KclErrorDetails::new(
227 "getCommonEdge requires the faces to be in the same original sketch".to_string(),
228 vec![args.source_range],
229 )));
230 }
231
232 if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = first_tagged_path.surface {
237 exec_state.flush_batch((&args).into(), true).await?;
238 } else if let Some(ExtrudeSurface::Chamfer { .. } | ExtrudeSurface::Fillet { .. }) = second_tagged_path.surface {
239 exec_state.flush_batch((&args).into(), true).await?;
240 }
241
242 let resp = exec_state
243 .send_modeling_cmd(
244 ModelingCmdMeta::from_args_id(&args, id),
245 ModelingCmd::from(mcmd::Solid3dGetCommonEdge {
246 object_id: first_tagged_path.sketch,
247 face_ids: [first_face_id, second_face_id],
248 }),
249 )
250 .await?;
251 let OkWebSocketResponseData::Modeling {
252 modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
253 } = &resp
254 else {
255 return Err(KclError::new_engine(KclErrorDetails::new(
256 format!("mcmd::Solid3dGetCommonEdge response was not as expected: {resp:?}"),
257 vec![args.source_range],
258 )));
259 };
260
261 common_edge.edge.ok_or_else(|| {
262 KclError::new_type(KclErrorDetails::new(
263 format!(
264 "No common edge was found between `{}` and `{}`",
265 face1.value, face2.value
266 ),
267 vec![args.source_range],
268 ))
269 })
270}