kcl_lib/std/
shell.rs

1//! Standard library shells.
2
3use anyhow::Result;
4use kcl_derive_docs::stdlib;
5use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
6use kittycad_modeling_cmds as kcmc;
7
8use crate::{
9    errors::{KclError, KclErrorDetails},
10    execution::{types::RuntimeType, ExecState, KclValue, Solid},
11    std::{sketch::FaceTag, Args},
12};
13
14use super::args::TyF64;
15
16/// Create a shell.
17pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
18    let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
19    let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::count(), exec_state)?;
20    let faces = args.get_kw_arg("faces")?;
21
22    let result = inner_shell(solids, thickness.n, faces, exec_state, args).await?;
23    Ok(result.into())
24}
25
26/// Remove volume from a 3-dimensional shape such that a wall of the
27/// provided thickness remains, taking volume starting at the provided
28/// face, leaving it open in that direction.
29///
30/// ```no_run
31/// // Remove the end face for the extrusion.
32/// firstSketch = startSketchOn(XY)
33///     |> startProfileAt([-12, 12], %)
34///     |> line(end = [24, 0])
35///     |> line(end = [0, -24])
36///     |> line(end = [-24, 0])
37///     |> close()
38///     |> extrude(length = 6)
39///
40/// // Remove the end face for the extrusion.
41/// shell(
42///     firstSketch,
43///     faces = [END],
44///     thickness = 0.25,
45/// )
46/// ```
47///
48/// ```no_run
49/// // Remove the start face for the extrusion.
50/// firstSketch = startSketchOn(-XZ)
51///     |> startProfileAt([-12, 12], %)
52///     |> line(end = [24, 0])
53///     |> line(end = [0, -24])
54///     |> line(end = [-24, 0])
55///     |> close()
56///     |> extrude(length = 6)
57///
58/// // Remove the start face for the extrusion.
59/// shell(
60///     firstSketch,
61///     faces = [START],
62///     thickness = 0.25,
63/// )
64/// ```
65///
66/// ```no_run
67/// // Remove a tagged face and the end face for the extrusion.
68/// firstSketch = startSketchOn(XY)
69///     |> startProfileAt([-12, 12], %)
70///     |> line(end = [24, 0])
71///     |> line(end = [0, -24])
72///     |> line(end = [-24, 0], tag = $myTag)
73///     |> close()
74///     |> extrude(length = 6)
75///
76/// // Remove a tagged face for the extrusion.
77/// shell(
78///     firstSketch,
79///     faces = [myTag],
80///     thickness = 0.25,
81/// )
82/// ```
83///
84/// ```no_run
85/// // Remove multiple faces at once.
86/// firstSketch = startSketchOn(XY)
87///     |> startProfileAt([-12, 12], %)
88///     |> line(end = [24, 0])
89///     |> line(end = [0, -24])
90///     |> line(end = [-24, 0], tag = $myTag)
91///     |> close()
92///     |> extrude(length = 6)
93///
94/// // Remove a tagged face and the end face for the extrusion.
95/// shell(
96///     firstSketch,
97///     faces = [myTag, END],
98///     thickness = 0.25,
99/// )
100/// ```
101///
102/// ```no_run
103/// // Shell a sketch on face.
104/// size = 100
105/// case = startSketchOn(-XZ)
106///     |> startProfileAt([-size, -size], %)
107///     |> line(end = [2 * size, 0])
108///     |> line(end = [0, 2 * size])
109///     |> tangentialArc(endAbsolute = [-size, size])
110///     |> close()
111///     |> extrude(length = 65)
112///
113/// thing1 = startSketchOn(case, face = END)
114///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
115///     |> extrude(length = 50)
116///
117/// thing2 = startSketchOn(case, face = END)
118///     |> circle( center = [size / 2, -size / 2], radius = 25 )
119///     |> extrude(length = 50)
120///
121/// // We put "case" in the shell function to shell the entire object.
122/// shell(case, faces = [START], thickness = 5)
123/// ```
124///
125/// ```no_run
126/// // Shell a sketch on face object on the end face.
127/// size = 100
128/// case = startSketchOn(XY)
129///     |> startProfileAt([-size, -size], %)
130///     |> line(end = [2 * size, 0])
131///     |> line(end = [0, 2 * size])
132///     |> tangentialArc(endAbsolute = [-size, size])
133///     |> close()
134///     |> extrude(length = 65)
135///
136/// thing1 = startSketchOn(case, face = END)
137///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
138///     |> extrude(length = 50)
139///
140/// thing2 = startSketchOn(case, face = END)
141///     |> circle( center = [size / 2, -size / 2], radius = 25 )
142///     |> extrude(length = 50)
143///
144/// // We put "thing1" in the shell function to shell the end face of the object.
145/// shell(thing1, faces = [END], thickness = 5)
146/// ```
147///
148/// ```no_run
149/// // Shell sketched on face objects on the end face, include all sketches to shell
150/// // the entire object.
151///
152/// size = 100
153/// case = startSketchOn(XY)
154///     |> startProfileAt([-size, -size], %)
155///     |> line(end = [2 * size, 0])
156///     |> line(end = [0, 2 * size])
157///     |> tangentialArc(endAbsolute = [-size, size])
158///     |> close()
159///     |> extrude(length = 65)
160///
161/// thing1 = startSketchOn(case, face = END)
162///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
163///     |> extrude(length = 50)
164///
165/// thing2 = startSketchOn(case, face = END)
166///     |> circle( center = [size / 2, -size / 2], radius = 25)
167///     |> extrude(length = 50)
168///
169/// // We put "thing1" and "thing2" in the shell function to shell the end face of the object.
170/// shell([thing1, thing2], faces = [END], thickness = 5)
171/// ```
172#[stdlib {
173    name = "shell",
174    feature_tree_operation = true,
175    keywords = true,
176    unlabeled_first = true,
177    args = {
178        solids = { docs = "Which solid (or solids) to shell out"},
179        thickness = {docs = "The thickness of the shell"},
180        faces = {docs = "The faces you want removed"},
181    }
182}]
183async fn inner_shell(
184    solids: Vec<Solid>,
185    thickness: f64,
186    faces: Vec<FaceTag>,
187    exec_state: &mut ExecState,
188    args: Args,
189) -> Result<Vec<Solid>, KclError> {
190    if faces.is_empty() {
191        return Err(KclError::Type(KclErrorDetails {
192            message: "You must shell at least one face".to_string(),
193            source_ranges: vec![args.source_range],
194        }));
195    }
196
197    if solids.is_empty() {
198        return Err(KclError::Type(KclErrorDetails {
199            message: "You must shell at least one solid".to_string(),
200            source_ranges: vec![args.source_range],
201        }));
202    }
203
204    let mut face_ids = Vec::new();
205    for solid in &solids {
206        // Flush the batch for our fillets/chamfers if there are any.
207        // If we do not do these for sketch on face, things will fail with face does not exist.
208        args.flush_batch_for_solids(exec_state, &[solid.clone()]).await?;
209
210        for tag in &faces {
211            let extrude_plane_id = tag.get_face_id(solid, exec_state, &args, false).await?;
212
213            face_ids.push(extrude_plane_id);
214        }
215    }
216
217    if face_ids.is_empty() {
218        return Err(KclError::Type(KclErrorDetails {
219            message: "Expected at least one valid face".to_string(),
220            source_ranges: vec![args.source_range],
221        }));
222    }
223
224    // Make sure all the solids have the same id, as we are going to shell them all at
225    // once.
226    if !solids.iter().all(|eg| eg.id == solids[0].id) {
227        return Err(KclError::Type(KclErrorDetails {
228            message: "All solids stem from the same root object, like multiple sketch on face extrusions, etc."
229                .to_string(),
230            source_ranges: vec![args.source_range],
231        }));
232    }
233
234    args.batch_modeling_cmd(
235        exec_state.next_uuid(),
236        ModelingCmd::from(mcmd::Solid3dShellFace {
237            hollow: false,
238            face_ids,
239            object_id: solids[0].id,
240            shell_thickness: LengthUnit(thickness),
241        }),
242    )
243    .await?;
244
245    Ok(solids)
246}
247
248/// Make the inside of a 3D object hollow.
249pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
250    let (thickness, solid) = args.get_data_and_solid(exec_state)?;
251
252    let value = inner_hollow(thickness.n, solid, exec_state, args).await?;
253    Ok(KclValue::Solid { value })
254}
255
256/// Make the inside of a 3D object hollow.
257///
258/// Remove volume from a 3-dimensional shape such that a wall of the
259/// provided thickness remains around the exterior of the shape.
260///
261/// ```no_run
262/// // Hollow a basic sketch.
263/// firstSketch = startSketchOn(XY)
264///     |> startProfileAt([-12, 12], %)
265///     |> line(end = [24, 0])
266///     |> line(end = [0, -24])
267///     |> line(end = [-24, 0])
268///     |> close()
269///     |> extrude(length = 6)
270///     |> hollow (0.25, %)
271/// ```
272///
273/// ```no_run
274/// // Hollow a basic sketch.
275/// firstSketch = startSketchOn(-XZ)
276///     |> startProfileAt([-12, 12], %)
277///     |> line(end = [24, 0])
278///     |> line(end = [0, -24])
279///     |> line(end = [-24, 0])
280///     |> close()
281///     |> extrude(length = 6)
282///     |> hollow (0.5, %)
283/// ```
284///
285/// ```no_run
286/// // Hollow a sketch on face object.
287/// size = 100
288/// case = startSketchOn(-XZ)
289///     |> startProfileAt([-size, -size], %)
290///     |> line(end = [2 * size, 0])
291///     |> line(end = [0, 2 * size])
292///     |> tangentialArc(endAbsolute = [-size, size])
293///     |> close()
294///     |> extrude(length = 65)
295///
296/// thing1 = startSketchOn(case, face = END)
297///     |> circle( center = [-size / 2, -size / 2], radius = 25 )
298///     |> extrude(length = 50)
299///
300/// thing2 = startSketchOn(case, face = END)
301///     |> circle( center = [size / 2, -size / 2], radius = 25 )
302///     |> extrude(length = 50)
303///
304/// hollow(0.5, case)
305/// ```
306#[stdlib {
307    name = "hollow",
308    feature_tree_operation = true,
309}]
310async fn inner_hollow(
311    thickness: f64,
312    solid: Box<Solid>,
313    exec_state: &mut ExecState,
314    args: Args,
315) -> Result<Box<Solid>, KclError> {
316    // Flush the batch for our fillets/chamfers if there are any.
317    // If we do not do these for sketch on face, things will fail with face does not exist.
318    args.flush_batch_for_solids(exec_state, &[(*solid).clone()]).await?;
319
320    args.batch_modeling_cmd(
321        exec_state.next_uuid(),
322        ModelingCmd::from(mcmd::Solid3dShellFace {
323            hollow: true,
324            face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
325            object_id: solid.id,
326            shell_thickness: LengthUnit(thickness),
327        }),
328    )
329    .await?;
330
331    Ok(solid)
332}