kcl_lib/std/
shell.rs

1//! Standard library shells.
2
3use anyhow::Result;
4use kcmc::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit};
5use kittycad_modeling_cmds as kcmc;
6
7use super::args::TyF64;
8use crate::{
9    errors::{KclError, KclErrorDetails},
10    execution::{
11        ExecState, KclValue, ModelingCmdMeta, Solid,
12        types::{ArrayLen, RuntimeType},
13    },
14    std::{Args, sketch::FaceTag},
15};
16
17/// Create a shell.
18pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
19    let solids = args.get_unlabeled_kw_arg("solids", &RuntimeType::solids(), exec_state)?;
20    let thickness: TyF64 = args.get_kw_arg("thickness", &RuntimeType::length(), exec_state)?;
21    let faces = args.get_kw_arg(
22        "faces",
23        &RuntimeType::Array(Box::new(RuntimeType::tagged_face()), ArrayLen::Minimum(1)),
24        exec_state,
25    )?;
26
27    let result = inner_shell(solids, thickness, faces, exec_state, args).await?;
28    Ok(result.into())
29}
30
31async fn inner_shell(
32    solids: Vec<Solid>,
33    thickness: TyF64,
34    faces: Vec<FaceTag>,
35    exec_state: &mut ExecState,
36    args: Args,
37) -> Result<Vec<Solid>, KclError> {
38    if faces.is_empty() {
39        return Err(KclError::new_type(KclErrorDetails::new(
40            "You must shell at least one face".to_owned(),
41            vec![args.source_range],
42        )));
43    }
44
45    if solids.is_empty() {
46        return Err(KclError::new_type(KclErrorDetails::new(
47            "You must shell at least one solid".to_owned(),
48            vec![args.source_range],
49        )));
50    }
51
52    let mut face_ids = Vec::new();
53    for solid in &solids {
54        // Flush the batch for our fillets/chamfers if there are any.
55        // If we do not do these for sketch on face, things will fail with face does not exist.
56        exec_state
57            .flush_batch_for_solids(
58                ModelingCmdMeta::from_args(exec_state, &args),
59                std::slice::from_ref(solid),
60            )
61            .await?;
62
63        for tag in &faces {
64            let extrude_plane_id = tag.get_face_id(solid, exec_state, &args, false).await?;
65
66            face_ids.push(extrude_plane_id);
67        }
68    }
69
70    if face_ids.is_empty() {
71        return Err(KclError::new_type(KclErrorDetails::new(
72            "Expected at least one valid face".to_owned(),
73            vec![args.source_range],
74        )));
75    }
76
77    // Make sure all the solids have the same id, as we are going to shell them all at
78    // once.
79    if !solids.iter().all(|eg| eg.id == solids[0].id) {
80        return Err(KclError::new_type(KclErrorDetails::new(
81            "All solids stem from the same root object, like multiple sketch on face extrusions, etc.".to_owned(),
82            vec![args.source_range],
83        )));
84    }
85
86    exec_state
87        .batch_modeling_cmd(
88            ModelingCmdMeta::from_args(exec_state, &args),
89            ModelingCmd::from(mcmd::Solid3dShellFace {
90                hollow: false,
91                face_ids,
92                object_id: solids[0].id,
93                shell_thickness: LengthUnit(thickness.to_mm()),
94            }),
95        )
96        .await?;
97
98    Ok(solids)
99}
100
101/// Make the inside of a 3D object hollow.
102pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
103    let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?;
104    let thickness: TyF64 = args.get_kw_arg("thickness", &RuntimeType::length(), exec_state)?;
105
106    let value = inner_hollow(solid, thickness, exec_state, args).await?;
107    Ok(KclValue::Solid { value })
108}
109
110async fn inner_hollow(
111    solid: Box<Solid>,
112    thickness: TyF64,
113    exec_state: &mut ExecState,
114    args: Args,
115) -> Result<Box<Solid>, KclError> {
116    // Flush the batch for our fillets/chamfers if there are any.
117    // If we do not do these for sketch on face, things will fail with face does not exist.
118    exec_state
119        .flush_batch_for_solids(ModelingCmdMeta::from_args(exec_state, &args), &[(*solid).clone()])
120        .await?;
121
122    exec_state
123        .batch_modeling_cmd(
124            ModelingCmdMeta::from_args(exec_state, &args),
125            ModelingCmd::from(mcmd::Solid3dShellFace {
126                hollow: true,
127                face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
128                object_id: solid.id,
129                shell_thickness: LengthUnit(thickness.to_mm()),
130            }),
131        )
132        .await?;
133
134    Ok(solid)
135}