1use anyhow::Result;
4use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
5use kittycad_modeling_cmds as kcmc;
6
7use super::args::TyF64;
8use crate::{
9 errors::{KclError, KclErrorDetails},
10 execution::{
11 types::{ArrayLen, RuntimeType},
12 ExecState, KclValue, Solid,
13 },
14 std::{sketch::FaceTag, Args},
15};
16
17pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
19 let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
20 let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::length(), exec_state)?;
21 let faces = args.get_kw_arg_typed(
22 "faces",
23 &RuntimeType::Array(Box::new(RuntimeType::tag()), ArrayLen::NonEmpty),
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::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::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 args.flush_batch_for_solids(exec_state, &[solid.clone()]).await?;
57
58 for tag in &faces {
59 let extrude_plane_id = tag.get_face_id(solid, exec_state, &args, false).await?;
60
61 face_ids.push(extrude_plane_id);
62 }
63 }
64
65 if face_ids.is_empty() {
66 return Err(KclError::Type(KclErrorDetails::new(
67 "Expected at least one valid face".to_owned(),
68 vec![args.source_range],
69 )));
70 }
71
72 if !solids.iter().all(|eg| eg.id == solids[0].id) {
75 return Err(KclError::Type(KclErrorDetails::new(
76 "All solids stem from the same root object, like multiple sketch on face extrusions, etc.".to_owned(),
77 vec![args.source_range],
78 )));
79 }
80
81 args.batch_modeling_cmd(
82 exec_state.next_uuid(),
83 ModelingCmd::from(mcmd::Solid3dShellFace {
84 hollow: false,
85 face_ids,
86 object_id: solids[0].id,
87 shell_thickness: LengthUnit(thickness.to_mm()),
88 }),
89 )
90 .await?;
91
92 Ok(solids)
93}
94
95pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
97 let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::solid(), exec_state)?;
98 let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::length(), exec_state)?;
99
100 let value = inner_hollow(solid, thickness, exec_state, args).await?;
101 Ok(KclValue::Solid { value })
102}
103
104async fn inner_hollow(
105 solid: Box<Solid>,
106 thickness: TyF64,
107 exec_state: &mut ExecState,
108 args: Args,
109) -> Result<Box<Solid>, KclError> {
110 args.flush_batch_for_solids(exec_state, &[(*solid).clone()]).await?;
113
114 args.batch_modeling_cmd(
115 exec_state.next_uuid(),
116 ModelingCmd::from(mcmd::Solid3dShellFace {
117 hollow: true,
118 face_ids: Vec::new(), object_id: solid.id,
120 shell_thickness: LengthUnit(thickness.to_mm()),
121 }),
122 )
123 .await?;
124
125 Ok(solid)
126}