1use std::collections::HashSet;
4
5use anyhow::Result;
6use kcmc::{ModelingCmd, each_cmd as mcmd};
7use kittycad_modeling_cmds::{
8 self as kcmc,
9 ok_response::OkModelingCmdResponse,
10 output as mout,
11 shared::{BodyType, FractionOfEdge, SurfaceEdgeReference},
12 websocket::OkWebSocketResponseData,
13};
14
15use crate::{
16 errors::{KclError, KclErrorDetails},
17 execution::{
18 BoundedEdge, ExecState, KclValue, ModelingCmdMeta, Solid, SolidCreator,
19 types::{ArrayLen, PrimitiveType, RuntimeType},
20 },
21 std::{Args, args::TyF64, sketch::FaceTag},
22};
23
24pub async fn flip_surface(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
26 let surface = args.get_unlabeled_kw_arg("surface", &RuntimeType::solids(), exec_state)?;
27 let out = inner_flip_surface(surface, exec_state, args).await?;
28 Ok(out.into())
29}
30
31async fn inner_flip_surface(
32 surfaces: Vec<Solid>,
33 exec_state: &mut ExecState,
34 args: Args,
35) -> Result<Vec<Solid>, KclError> {
36 for surface in &surfaces {
37 exec_state
38 .batch_modeling_cmd(
39 ModelingCmdMeta::from_args(exec_state, &args),
40 ModelingCmd::from(mcmd::Solid3dFlip::builder().object_id(surface.id).build()),
41 )
42 .await?;
43 }
44
45 Ok(surfaces)
46}
47
48pub async fn is_solid(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
50 let argument = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
51 let meta = vec![crate::execution::Metadata {
52 source_range: args.source_range,
53 }];
54
55 let res = inner_is_equal_body_type(argument, exec_state, args, BodyType::Solid).await?;
56 Ok(KclValue::Bool { value: res, meta })
57}
58
59pub async fn is_surface(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
61 let argument = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
62 let meta = vec![crate::execution::Metadata {
63 source_range: args.source_range,
64 }];
65
66 let res = inner_is_equal_body_type(argument, exec_state, args, BodyType::Surface).await?;
67 Ok(KclValue::Bool { value: res, meta })
68}
69
70async fn inner_is_equal_body_type(
71 surface: Solid,
72 exec_state: &mut ExecState,
73 args: Args,
74 expected: BodyType,
75) -> Result<bool, KclError> {
76 let meta = ModelingCmdMeta::from_args(exec_state, &args);
77 let cmd = ModelingCmd::from(mcmd::Solid3dGetBodyType::builder().object_id(surface.id).build());
78
79 let response = exec_state.send_modeling_cmd(meta, cmd).await?;
80
81 let OkWebSocketResponseData::Modeling {
82 modeling_response: OkModelingCmdResponse::Solid3dGetBodyType(body),
83 } = response
84 else {
85 return Err(KclError::new_semantic(KclErrorDetails::new(
86 format!(
87 "Engine returned invalid response, it should have returned Solid3dGetBodyType but it returned {response:#?}"
88 ),
89 vec![args.source_range],
90 )));
91 };
92
93 Ok(expected == body.body_type)
94}
95
96pub async fn delete_face(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
97 let body = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
98 let faces: Option<Vec<FaceTag>> = args.get_kw_arg_opt(
99 "faces",
100 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
101 exec_state,
102 )?;
103 let face_indices: Option<Vec<TyF64>> = args.get_kw_arg_opt(
104 "faceIndices",
105 &RuntimeType::Array(Box::new(RuntimeType::count()), ArrayLen::Minimum(1)),
106 exec_state,
107 )?;
108 let face_indices = if let Some(face_indices) = face_indices {
109 let faces = face_indices
110 .into_iter()
111 .map(|num| {
112 crate::try_f64_to_u32(num.n).ok_or_else(|| {
113 KclError::new_semantic(KclErrorDetails::new(
114 format!("Face indices must be whole numbers, got {}", num.n),
115 vec![args.source_range],
116 ))
117 })
118 })
119 .collect::<Result<Vec<_>, _>>()?;
120 Some(faces)
121 } else {
122 None
123 };
124 inner_delete_face(body, faces, face_indices, exec_state, args)
125 .await
126 .map(Box::new)
127 .map(|value| KclValue::Solid { value })
128}
129
130async fn inner_delete_face(
131 body: Solid,
132 tagged_faces: Option<Vec<FaceTag>>,
133 face_indices: Option<Vec<u32>>,
134 exec_state: &mut ExecState,
135 args: Args,
136) -> Result<Solid, KclError> {
137 if tagged_faces.is_none() && face_indices.is_none() {
140 return Err(KclError::new_semantic(KclErrorDetails::new(
141 "You must use either the `faces` or the `faceIndices` parameter".to_string(),
142 vec![args.source_range],
143 )));
144 }
145
146 let no_engine_commands = args.ctx.no_engine_commands().await;
149 if no_engine_commands {
150 return Ok(body);
151 }
152
153 let tagged_faces = tagged_faces.unwrap_or_default();
155 let face_indices = face_indices.unwrap_or_default();
156 let mut face_ids = HashSet::with_capacity(face_indices.len() + tagged_faces.len());
158
159 for tagged_face in tagged_faces {
160 let face_id = tagged_face.get_face_id(&body, exec_state, &args, false).await?;
161 face_ids.insert(face_id);
162 }
163
164 for face_index in face_indices {
165 let face_uuid_response = exec_state
166 .send_modeling_cmd(
167 ModelingCmdMeta::from_args(exec_state, &args),
168 ModelingCmd::from(
169 mcmd::Solid3dGetFaceUuid::builder()
170 .object_id(body.id)
171 .face_index(face_index)
172 .build(),
173 ),
174 )
175 .await?;
176
177 let OkWebSocketResponseData::Modeling {
178 modeling_response: OkModelingCmdResponse::Solid3dGetFaceUuid(inner_resp),
179 } = face_uuid_response
180 else {
181 return Err(KclError::new_semantic(KclErrorDetails::new(
182 format!(
183 "Engine returned invalid response, it should have returned Solid3dGetFaceUuid but it returned {face_uuid_response:?}"
184 ),
185 vec![args.source_range],
186 )));
187 };
188 face_ids.insert(inner_resp.face_id);
189 }
190
191 let delete_face_response = exec_state
193 .send_modeling_cmd(
194 ModelingCmdMeta::from_args(exec_state, &args),
195 ModelingCmd::from(
196 mcmd::EntityDeleteChildren::builder()
197 .entity_id(body.id)
198 .child_entity_ids(face_ids)
199 .build(),
200 ),
201 )
202 .await?;
203
204 let OkWebSocketResponseData::Modeling {
205 modeling_response: OkModelingCmdResponse::EntityDeleteChildren(mout::EntityDeleteChildren { .. }),
206 } = delete_face_response
207 else {
208 return Err(KclError::new_semantic(KclErrorDetails::new(
209 format!(
210 "Engine returned invalid response, it should have returned EntityDeleteChildren but it returned {delete_face_response:?}"
211 ),
212 vec![args.source_range],
213 )));
214 };
215
216 Ok(body)
218}
219
220pub async fn blend(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
222 let bounded_edges = args.get_unlabeled_kw_arg(
223 "edges",
224 &RuntimeType::Array(
225 Box::new(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
226 ArrayLen::Known(2),
227 ),
228 exec_state,
229 )?;
230
231 inner_blend(bounded_edges, exec_state, args.clone())
232 .await
233 .map(Box::new)
234 .map(|value| KclValue::Solid { value })
235}
236
237async fn inner_blend(edges: Vec<BoundedEdge>, exec_state: &mut ExecState, args: Args) -> Result<Solid, KclError> {
238 let id = exec_state.next_uuid();
239
240 let surface_refs: Vec<SurfaceEdgeReference> = edges
241 .iter()
242 .map(|edge| {
243 SurfaceEdgeReference::builder()
244 .object_id(edge.face_id)
245 .edges(vec![
246 FractionOfEdge::builder()
247 .edge_id(edge.edge_id)
248 .lower_bound(edge.lower_bound)
249 .upper_bound(edge.upper_bound)
250 .build(),
251 ])
252 .build()
253 })
254 .collect();
255
256 exec_state
257 .batch_modeling_cmd(
258 ModelingCmdMeta::from_args_id(exec_state, &args, id),
259 ModelingCmd::from(mcmd::SurfaceBlend::builder().surfaces(surface_refs).build()),
260 )
261 .await?;
262
263 let solid = Solid {
264 id,
265 artifact_id: id.into(),
266 value: vec![],
267 creator: SolidCreator::Procedural,
268 start_cap_id: None,
269 end_cap_id: None,
270 edge_cuts: vec![],
271 units: exec_state.length_unit(),
272 sectional: false,
273 meta: vec![crate::execution::Metadata {
274 source_range: args.source_range,
275 }],
276 };
277 Ok(solid)
279}