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}