1use anyhow::Result;
4use kcmc::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit};
5use kittycad_modeling_cmds::{
6 self as kcmc,
7 ok_response::OkModelingCmdResponse,
8 output::{BooleanIntersection, BooleanSubtract, BooleanUnion},
9 websocket::OkWebSocketResponseData,
10};
11
12use super::{DEFAULT_TOLERANCE_MM, args::TyF64};
13use crate::{
14 errors::{KclError, KclErrorDetails},
15 execution::{ExecState, KclValue, ModelingCmdMeta, Solid, types::RuntimeType},
16 std::{Args, patterns::GeometryTrait},
17};
18
19pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
21 let solids: Vec<Solid> =
22 args.get_unlabeled_kw_arg("solids", &RuntimeType::Union(vec![RuntimeType::solids()]), exec_state)?;
23 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
24
25 if solids.len() < 2 {
26 return Err(KclError::new_semantic(KclErrorDetails::new(
27 "At least two solids are required for a union operation.".to_string(),
28 vec![args.source_range],
29 )));
30 }
31
32 let solids = inner_union(solids, tolerance, exec_state, args).await?;
33 Ok(solids.into())
34}
35
36pub(crate) async fn inner_union(
37 solids: Vec<Solid>,
38 tolerance: Option<TyF64>,
39 exec_state: &mut ExecState,
40 args: Args,
41) -> Result<Vec<Solid>, KclError> {
42 let solid_out_id = exec_state.next_uuid();
43
44 let mut solid = solids[0].clone();
45 solid.set_id(solid_out_id);
46 let mut new_solids = vec![solid.clone()];
47
48 if args.ctx.no_engine_commands().await {
49 return Ok(new_solids);
50 }
51
52 exec_state.flush_batch_for_solids((&args).into(), &solids).await?;
54
55 let result = exec_state
56 .send_modeling_cmd(
57 ModelingCmdMeta::from_args_id(&args, solid_out_id),
58 ModelingCmd::from(mcmd::BooleanUnion {
59 solid_ids: solids.iter().map(|s| s.id).collect(),
60 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
61 }),
62 )
63 .await?;
64
65 let OkWebSocketResponseData::Modeling {
66 modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
67 } = result
68 else {
69 return Err(KclError::new_internal(KclErrorDetails::new(
70 "Failed to get the result of the union operation.".to_string(),
71 vec![args.source_range],
72 )));
73 };
74
75 if !extra_solid_ids.is_empty() {
77 solid.set_id(extra_solid_ids[0]);
78 new_solids.push(solid.clone());
79 }
80
81 Ok(new_solids)
82}
83
84pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
87 let solids: Vec<Solid> = args.get_unlabeled_kw_arg("solids", &RuntimeType::solids(), exec_state)?;
88 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
89
90 if solids.len() < 2 {
91 return Err(KclError::new_semantic(KclErrorDetails::new(
92 "At least two solids are required for an intersect operation.".to_string(),
93 vec![args.source_range],
94 )));
95 }
96
97 let solids = inner_intersect(solids, tolerance, exec_state, args).await?;
98 Ok(solids.into())
99}
100
101pub(crate) async fn inner_intersect(
102 solids: Vec<Solid>,
103 tolerance: Option<TyF64>,
104 exec_state: &mut ExecState,
105 args: Args,
106) -> Result<Vec<Solid>, KclError> {
107 let solid_out_id = exec_state.next_uuid();
108
109 let mut solid = solids[0].clone();
110 solid.set_id(solid_out_id);
111 let mut new_solids = vec![solid.clone()];
112
113 if args.ctx.no_engine_commands().await {
114 return Ok(new_solids);
115 }
116
117 exec_state.flush_batch_for_solids((&args).into(), &solids).await?;
119
120 let result = exec_state
121 .send_modeling_cmd(
122 ModelingCmdMeta::from_args_id(&args, solid_out_id),
123 ModelingCmd::from(mcmd::BooleanIntersection {
124 solid_ids: solids.iter().map(|s| s.id).collect(),
125 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
126 }),
127 )
128 .await?;
129
130 let OkWebSocketResponseData::Modeling {
131 modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
132 } = result
133 else {
134 return Err(KclError::new_internal(KclErrorDetails::new(
135 "Failed to get the result of the intersection operation.".to_string(),
136 vec![args.source_range],
137 )));
138 };
139
140 if !extra_solid_ids.is_empty() {
142 solid.set_id(extra_solid_ids[0]);
143 new_solids.push(solid.clone());
144 }
145
146 Ok(new_solids)
147}
148
149pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
151 let solids: Vec<Solid> = args.get_unlabeled_kw_arg("solids", &RuntimeType::solids(), exec_state)?;
152 let tools: Vec<Solid> = args.get_kw_arg("tools", &RuntimeType::solids(), exec_state)?;
153
154 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
155
156 let solids = inner_subtract(solids, tools, tolerance, exec_state, args).await?;
157 Ok(solids.into())
158}
159
160pub(crate) async fn inner_subtract(
161 solids: Vec<Solid>,
162 tools: Vec<Solid>,
163 tolerance: Option<TyF64>,
164 exec_state: &mut ExecState,
165 args: Args,
166) -> Result<Vec<Solid>, KclError> {
167 let solid_out_id = exec_state.next_uuid();
168
169 let mut solid = solids[0].clone();
170 solid.set_id(solid_out_id);
171 let mut new_solids = vec![solid.clone()];
172
173 if args.ctx.no_engine_commands().await {
174 return Ok(new_solids);
175 }
176
177 let combined_solids = solids.iter().chain(tools.iter()).cloned().collect::<Vec<Solid>>();
179 exec_state
180 .flush_batch_for_solids((&args).into(), &combined_solids)
181 .await?;
182
183 let result = exec_state
184 .send_modeling_cmd(
185 ModelingCmdMeta::from_args_id(&args, solid_out_id),
186 ModelingCmd::from(mcmd::BooleanSubtract {
187 target_ids: solids.iter().map(|s| s.id).collect(),
188 tool_ids: tools.iter().map(|s| s.id).collect(),
189 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
190 }),
191 )
192 .await?;
193
194 let OkWebSocketResponseData::Modeling {
195 modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
196 } = result
197 else {
198 return Err(KclError::new_internal(KclErrorDetails::new(
199 "Failed to get the result of the subtract operation.".to_string(),
200 vec![args.source_range],
201 )));
202 };
203
204 if !extra_solid_ids.is_empty() {
206 solid.set_id(extra_solid_ids[0]);
207 new_solids.push(solid.clone());
208 }
209
210 Ok(new_solids)
211}