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}