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