Skip to main content

kcl_lib/std/
shell.rs

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