1use 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
21pub 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 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 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
107pub 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 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()) .object_id(solid.id)
136 .shell_thickness(LengthUnit(thickness.to_mm()))
137 .build(),
138 ),
139 )
140 .await?;
141
142 Ok(solid)
143}