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