1use std::collections::HashSet;
4
5use anyhow::Result;
6use kcmc::{ModelingCmd, each_cmd as mcmd};
7use kittycad_modeling_cmds::{
8 self as kcmc, ok_response::OkModelingCmdResponse, output as mout, shared::BodyType,
9 websocket::OkWebSocketResponseData,
10};
11
12use crate::{
13 errors::{KclError, KclErrorDetails},
14 execution::{
15 ExecState, KclValue, ModelingCmdMeta, Solid,
16 types::{ArrayLen, RuntimeType},
17 },
18 std::{Args, args::TyF64, sketch::FaceTag},
19};
20
21pub async fn flip_surface(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
23 let surface = args.get_unlabeled_kw_arg("surface", &RuntimeType::solids(), exec_state)?;
24 let out = inner_flip_surface(surface, exec_state, args).await?;
25 Ok(out.into())
26}
27
28async fn inner_flip_surface(
29 surfaces: Vec<Solid>,
30 exec_state: &mut ExecState,
31 args: Args,
32) -> Result<Vec<Solid>, KclError> {
33 for surface in &surfaces {
34 exec_state
35 .batch_modeling_cmd(
36 ModelingCmdMeta::from_args(exec_state, &args),
37 ModelingCmd::from(mcmd::Solid3dFlip::builder().object_id(surface.id).build()),
38 )
39 .await?;
40 }
41
42 Ok(surfaces)
43}
44
45pub async fn is_solid(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
47 let argument = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
48 let meta = vec![crate::execution::Metadata {
49 source_range: args.source_range,
50 }];
51
52 let res = inner_is_equal_body_type(argument, exec_state, args, BodyType::Solid).await?;
53 Ok(KclValue::Bool { value: res, meta })
54}
55
56pub async fn is_surface(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
58 let argument = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
59 let meta = vec![crate::execution::Metadata {
60 source_range: args.source_range,
61 }];
62
63 let res = inner_is_equal_body_type(argument, exec_state, args, BodyType::Surface).await?;
64 Ok(KclValue::Bool { value: res, meta })
65}
66
67async fn inner_is_equal_body_type(
68 surface: Solid,
69 exec_state: &mut ExecState,
70 args: Args,
71 expected: BodyType,
72) -> Result<bool, KclError> {
73 let meta = ModelingCmdMeta::from_args(exec_state, &args);
74 let cmd = ModelingCmd::from(mcmd::Solid3dGetBodyType::builder().object_id(surface.id).build());
75
76 let response = exec_state.send_modeling_cmd(meta, cmd).await?;
77
78 let OkWebSocketResponseData::Modeling {
79 modeling_response: OkModelingCmdResponse::Solid3dGetBodyType(body),
80 } = response
81 else {
82 return Err(KclError::new_semantic(KclErrorDetails::new(
83 format!(
84 "Engine returned invalid response, it should have returned Solid3dGetBodyType but it returned {response:#?}"
85 ),
86 vec![args.source_range],
87 )));
88 };
89
90 Ok(expected == body.body_type)
91}
92
93pub async fn delete_face(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
94 let body = args.get_unlabeled_kw_arg("body", &RuntimeType::solid(), exec_state)?;
95 let faces: Option<Vec<FaceTag>> = args.get_kw_arg_opt(
96 "faces",
97 &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
98 exec_state,
99 )?;
100 let face_indices: Option<Vec<TyF64>> = args.get_kw_arg_opt(
101 "faceIndices",
102 &RuntimeType::Array(Box::new(RuntimeType::count()), ArrayLen::Minimum(1)),
103 exec_state,
104 )?;
105 let face_indices = if let Some(face_indices) = face_indices {
106 let faces = face_indices
107 .into_iter()
108 .map(|num| {
109 crate::try_f64_to_u32(num.n).ok_or_else(|| {
110 KclError::new_semantic(KclErrorDetails::new(
111 format!("Face indices must be whole numbers, got {}", num.n),
112 vec![args.source_range],
113 ))
114 })
115 })
116 .collect::<Result<Vec<_>, _>>()?;
117 Some(faces)
118 } else {
119 None
120 };
121 inner_delete_face(body, faces, face_indices, exec_state, args)
122 .await
123 .map(Box::new)
124 .map(|value| KclValue::Solid { value })
125}
126
127async fn inner_delete_face(
128 body: Solid,
129 tagged_faces: Option<Vec<FaceTag>>,
130 face_indices: Option<Vec<u32>>,
131 exec_state: &mut ExecState,
132 args: Args,
133) -> Result<Solid, KclError> {
134 if tagged_faces.is_none() && face_indices.is_none() {
137 return Err(KclError::new_semantic(KclErrorDetails::new(
138 "You must use either the `faces` or the `face_indices` parameter".to_string(),
139 vec![args.source_range],
140 )));
141 }
142
143 let no_engine_commands = args.ctx.no_engine_commands().await;
146 if no_engine_commands {
147 return Ok(body);
148 }
149
150 let tagged_faces = tagged_faces.unwrap_or_default();
152 let face_indices = face_indices.unwrap_or_default();
153 let mut face_ids = HashSet::with_capacity(face_indices.len() + tagged_faces.len());
155
156 for tagged_face in tagged_faces {
157 let face_id = tagged_face.get_face_id(&body, exec_state, &args, false).await?;
158 face_ids.insert(face_id);
159 }
160
161 for face_index in face_indices {
162 let face_uuid_response = exec_state
163 .send_modeling_cmd(
164 ModelingCmdMeta::from_args(exec_state, &args),
165 ModelingCmd::from(
166 mcmd::Solid3dGetFaceUuid::builder()
167 .object_id(body.id)
168 .face_index(face_index)
169 .build(),
170 ),
171 )
172 .await?;
173
174 let OkWebSocketResponseData::Modeling {
175 modeling_response: OkModelingCmdResponse::Solid3dGetFaceUuid(mout::Solid3dGetFaceUuid { face_id }),
176 } = face_uuid_response
177 else {
178 return Err(KclError::new_semantic(KclErrorDetails::new(
179 format!(
180 "Engine returned invalid response, it should have returned Solid3dGetFaceUuid but it returned {face_uuid_response:?}"
181 ),
182 vec![args.source_range],
183 )));
184 };
185 face_ids.insert(face_id);
186 }
187
188 let delete_face_response = exec_state
190 .send_modeling_cmd(
191 ModelingCmdMeta::from_args(exec_state, &args),
192 ModelingCmd::from(
193 mcmd::EntityDeleteChildren::builder()
194 .entity_id(body.id)
195 .child_entity_ids(face_ids)
196 .build(),
197 ),
198 )
199 .await?;
200
201 let OkWebSocketResponseData::Modeling {
202 modeling_response: OkModelingCmdResponse::EntityDeleteChildren(mout::EntityDeleteChildren { .. }),
203 } = delete_face_response
204 else {
205 return Err(KclError::new_semantic(KclErrorDetails::new(
206 format!(
207 "Engine returned invalid response, it should have returned EntityDeleteChildren but it returned {delete_face_response:?}"
208 ),
209 vec![args.source_range],
210 )));
211 };
212
213 Ok(body)
215}