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
54 .flush_batch_for_solids(ModelingCmdMeta::from_args(exec_state, &args), &solids)
55 .await?;
56
57 let result = exec_state
58 .send_modeling_cmd(
59 ModelingCmdMeta::from_args_id(exec_state, &args, solid_out_id),
60 ModelingCmd::from(mcmd::BooleanUnion {
61 solid_ids: solids.iter().map(|s| s.id).collect(),
62 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
63 }),
64 )
65 .await?;
66
67 let OkWebSocketResponseData::Modeling {
68 modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
69 } = result
70 else {
71 return Err(KclError::new_internal(KclErrorDetails::new(
72 "Failed to get the result of the union operation.".to_string(),
73 vec![args.source_range],
74 )));
75 };
76
77 for extra_solid_id in extra_solid_ids {
79 if extra_solid_id == solid_out_id {
80 continue;
81 }
82 let mut new_solid = solid.clone();
83 new_solid.set_id(extra_solid_id);
84 new_solids.push(new_solid);
85 }
86
87 Ok(new_solids)
88}
89
90pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
93 let solids: Vec<Solid> = args.get_unlabeled_kw_arg("solids", &RuntimeType::solids(), exec_state)?;
94 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
95
96 if solids.len() < 2 {
97 return Err(KclError::new_semantic(KclErrorDetails::new(
98 "At least two solids are required for an intersect operation.".to_string(),
99 vec![args.source_range],
100 )));
101 }
102
103 let solids = inner_intersect(solids, tolerance, exec_state, args).await?;
104 Ok(solids.into())
105}
106
107pub(crate) async fn inner_intersect(
108 solids: Vec<Solid>,
109 tolerance: Option<TyF64>,
110 exec_state: &mut ExecState,
111 args: Args,
112) -> Result<Vec<Solid>, KclError> {
113 let solid_out_id = exec_state.next_uuid();
114
115 let mut solid = solids[0].clone();
116 solid.set_id(solid_out_id);
117 let mut new_solids = vec![solid.clone()];
118
119 if args.ctx.no_engine_commands().await {
120 return Ok(new_solids);
121 }
122
123 exec_state
125 .flush_batch_for_solids(ModelingCmdMeta::from_args(exec_state, &args), &solids)
126 .await?;
127
128 let result = exec_state
129 .send_modeling_cmd(
130 ModelingCmdMeta::from_args_id(exec_state, &args, solid_out_id),
131 ModelingCmd::from(mcmd::BooleanIntersection {
132 solid_ids: solids.iter().map(|s| s.id).collect(),
133 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
134 }),
135 )
136 .await?;
137
138 let OkWebSocketResponseData::Modeling {
139 modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
140 } = result
141 else {
142 return Err(KclError::new_internal(KclErrorDetails::new(
143 "Failed to get the result of the intersection operation.".to_string(),
144 vec![args.source_range],
145 )));
146 };
147
148 for extra_solid_id in extra_solid_ids {
150 if extra_solid_id == solid_out_id {
151 continue;
152 }
153 let mut new_solid = solid.clone();
154 new_solid.set_id(extra_solid_id);
155 new_solids.push(new_solid);
156 }
157
158 Ok(new_solids)
159}
160
161pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
163 let solids: Vec<Solid> = args.get_unlabeled_kw_arg("solids", &RuntimeType::solids(), exec_state)?;
164 let tools: Vec<Solid> = args.get_kw_arg("tools", &RuntimeType::solids(), exec_state)?;
165
166 let tolerance: Option<TyF64> = args.get_kw_arg_opt("tolerance", &RuntimeType::length(), exec_state)?;
167
168 let solids = inner_subtract(solids, tools, tolerance, exec_state, args).await?;
169 Ok(solids.into())
170}
171
172pub(crate) async fn inner_subtract(
173 solids: Vec<Solid>,
174 tools: Vec<Solid>,
175 tolerance: Option<TyF64>,
176 exec_state: &mut ExecState,
177 args: Args,
178) -> Result<Vec<Solid>, KclError> {
179 let solid_out_id = exec_state.next_uuid();
180
181 let mut solid = solids[0].clone();
182 solid.set_id(solid_out_id);
183 let mut new_solids = vec![solid.clone()];
184
185 if args.ctx.no_engine_commands().await {
186 return Ok(new_solids);
187 }
188
189 let combined_solids = solids.iter().chain(tools.iter()).cloned().collect::<Vec<Solid>>();
191 exec_state
192 .flush_batch_for_solids(ModelingCmdMeta::from_args(exec_state, &args), &combined_solids)
193 .await?;
194
195 let result = exec_state
196 .send_modeling_cmd(
197 ModelingCmdMeta::from_args_id(exec_state, &args, solid_out_id),
198 ModelingCmd::from(mcmd::BooleanSubtract {
199 target_ids: solids.iter().map(|s| s.id).collect(),
200 tool_ids: tools.iter().map(|s| s.id).collect(),
201 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)),
202 }),
203 )
204 .await?;
205
206 let OkWebSocketResponseData::Modeling {
207 modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
208 } = result
209 else {
210 return Err(KclError::new_internal(KclErrorDetails::new(
211 "Failed to get the result of the subtract operation.".to_string(),
212 vec![args.source_range],
213 )));
214 };
215
216 for extra_solid_id in extra_solid_ids {
218 if extra_solid_id == solid_out_id {
219 continue;
220 }
221 let mut new_solid = solid.clone();
222 new_solid.set_id(extra_solid_id);
223 new_solids.push(new_solid);
224 }
225
226 Ok(new_solids)
227}