kcl_lib/std/csg.rs
1//! Constructive Solid Geometry (CSG) operations.
2
3use anyhow::Result;
4use kcl_derive_docs::stdlib;
5
6use crate::{
7 errors::{KclError, KclErrorDetails},
8 execution::{
9 kcl_value::{ArrayLen, RuntimeType},
10 ExecState, KclValue, PrimitiveType, Solid,
11 },
12 std::Args,
13};
14
15/// Union two or more solids into a single solid.
16pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
17 let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed(
18 "objects",
19 &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
20 exec_state,
21 )?;
22
23 if solids.len() < 2 {
24 return Err(KclError::UndefinedValue(KclErrorDetails {
25 message: "At least two solids are required for a union operation.".to_string(),
26 source_ranges: vec![args.source_range],
27 }));
28 }
29
30 let solids = inner_union(solids, exec_state, args).await?;
31 Ok(solids.into())
32}
33
34/// Union two or more solids into a single solid.
35///
36/// ```no_run
37/// fn cube(center) {
38/// return startSketchOn('XY')
39/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
40/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
41/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
42/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
43/// |> close()
44/// |> extrude(length = 10)
45/// }
46///
47/// part001 = cube([0, 0])
48/// part002 = cube([20, 10])
49///
50/// unionedPart = union([part001, part002])
51/// ```
52#[stdlib {
53 name = "union",
54 feature_tree_operation = false,
55 keywords = true,
56 unlabeled_first = true,
57 deprecated = true,
58 args = {
59 solids = {docs = "The solids to union."},
60 }
61}]
62async fn inner_union(solids: Vec<Solid>, exec_state: &mut ExecState, args: Args) -> Result<Vec<Solid>, KclError> {
63 // Flush the fillets for the solids.
64 args.flush_batch_for_solids(exec_state, &solids).await?;
65
66 // TODO: call the engine union operation.
67 // TODO: figure out all the shit after for the faces etc.
68
69 // For now just return the first solid.
70 // Til we have a proper implementation.
71 Ok(vec![solids[0].clone()])
72}
73
74/// Intersect returns the shared volume between multiple solids, preserving only
75/// overlapping regions.
76pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
77 let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed(
78 "objects",
79 &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
80 exec_state,
81 )?;
82
83 if solids.len() < 2 {
84 return Err(KclError::UndefinedValue(KclErrorDetails {
85 message: "At least two solids are required for an intersect operation.".to_string(),
86 source_ranges: vec![args.source_range],
87 }));
88 }
89
90 let solids = inner_intersect(solids, exec_state, args).await?;
91 Ok(solids.into())
92}
93
94/// Intersect returns the shared volume between multiple solids, preserving only
95/// overlapping regions.
96///
97/// Intersect computes the geometric intersection of multiple solid bodies,
98/// returning a new solid representing the volume that is common to all input
99/// solids. This operation is useful for determining shared material regions,
100/// verifying fit, and analyzing overlapping geometries in assemblies.
101///
102/// ```no_run
103/// fn cube(center) {
104/// return startSketchOn('XY')
105/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
106/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
107/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
108/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
109/// |> close()
110/// |> extrude(length = 10)
111/// }
112///
113/// part001 = cube([0, 0])
114/// part002 = cube([8, 8])
115///
116/// intersectedPart = intersect([part001, part002])
117/// ```
118#[stdlib {
119 name = "intersect",
120 feature_tree_operation = false,
121 keywords = true,
122 unlabeled_first = true,
123 deprecated = true,
124 args = {
125 solids = {docs = "The solids to intersect."},
126 }
127}]
128async fn inner_intersect(solids: Vec<Solid>, exec_state: &mut ExecState, args: Args) -> Result<Vec<Solid>, KclError> {
129 // Flush the fillets for the solids.
130 args.flush_batch_for_solids(exec_state, &solids).await?;
131
132 // TODO: call the engine union operation.
133 // TODO: figure out all the shit after for the faces etc.
134
135 // For now just return the first solid.
136 // Til we have a proper implementation.
137 Ok(vec![solids[0].clone()])
138}
139
140/// Subtract removes tool solids from base solids, leaving the remaining material.
141pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
142 let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed(
143 "objects",
144 &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
145 exec_state,
146 )?;
147 let tools: Vec<Solid> = args.get_kw_arg_typed(
148 "tools",
149 &RuntimeType::Union(vec![RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty)]),
150 exec_state,
151 )?;
152
153 let solids = inner_subtract(solids, tools, exec_state, args).await?;
154 Ok(solids.into())
155}
156
157/// Subtract removes tool solids from base solids, leaving the remaining material.
158///
159/// Performs a boolean subtraction operation, removing the volume of one or more
160/// tool solids from one or more base solids. The result is a new solid
161/// representing the material that remains after all tool solids have been cut
162/// away. This function is essential for machining simulations, cavity creation,
163/// and complex multi-body part modeling.
164///
165/// ```no_run
166/// fn cube(center) {
167/// return startSketchOn('XY')
168/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
169/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
170/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
171/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
172/// |> close()
173/// |> extrude(length = 10)
174/// }
175///
176/// part001 = cube([0, 0])
177/// part002 = startSketchOn('XY')
178/// |> circle(center = [0, 0], radius = 2)
179/// |> extrude(length = 10)
180///
181/// subtractedPart = subtract([part001], tools=[part002])
182/// ```
183#[stdlib {
184 name = "subtract",
185 feature_tree_operation = false,
186 keywords = true,
187 unlabeled_first = true,
188 deprecated = true,
189 args = {
190 solids = {docs = "The solids to intersect."},
191 tools = {docs = "The solids to subtract."},
192 }
193}]
194async fn inner_subtract(
195 solids: Vec<Solid>,
196 tools: Vec<Solid>,
197 exec_state: &mut ExecState,
198 args: Args,
199) -> Result<Vec<Solid>, KclError> {
200 // Flush the fillets for the solids and the tools.
201 let combined_solids = solids.iter().chain(tools.iter()).cloned().collect::<Vec<Solid>>();
202 args.flush_batch_for_solids(exec_state, &combined_solids).await?;
203
204 // TODO: call the engine union operation.
205 // TODO: figure out all the shit after for the faces etc.
206
207 // For now just return the first solid.
208 // Til we have a proper implementation.
209 Ok(vec![solids[0].clone()])
210}