Skip to main content

kcl_lib/std/
surfaces.rs

1//! Standard library appearance.
2
3use 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
21/// Flips the orientation of a surface, swapping which side is the front and which is the reverse.
22pub 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
45/// Check if this object is a solid or not.
46pub 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
56/// Check if this object is a surface or not.
57pub 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    // Validate args:
135    // User has to give us SOMETHING to delete.
136    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    // Early return for mock response, just return the same solid.
144    // If we tracked faces, we would remove some faces... but we don't really.
145    let no_engine_commands = args.ctx.no_engine_commands().await;
146    if no_engine_commands {
147        return Ok(body);
148    }
149
150    // Combine the list of faces, both tagged and indexed.
151    let tagged_faces = tagged_faces.unwrap_or_default();
152    let face_indices = face_indices.unwrap_or_default();
153    // Get the face's ID
154    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    // Now that we've got all the faces, delete them all.
189    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    // Return the same body, it just has fewer faces.
214    Ok(body)
215}