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