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}