1use 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
17pub 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 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 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
101pub 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 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(), object_id: solid.id,
129 shell_thickness: LengthUnit(thickness.to_mm()),
130 }),
131 )
132 .await?;
133
134 Ok(solid)
135}