Skip to main content

rscad_csg_traits/
lib.rs

1//! Unified CSG backend traits and BSP backend implementation.
2//!
3//! Each CSG backend defines a marker type implementing [`CsgBackend`], then
4//! provides [`ToCsg`] and [`ToSketch`] impls for `rscad-core` primitive types.
5//! Generic wrapper impls (transforms, booleans, containers) are provided as
6//! blanket impls over any `CsgBackend`.
7
8pub mod dynamic;
9pub mod stack;
10pub mod stl;
11
12pub use dynamic::{DynamicBackend, dynamic_to_csg, dynamic_to_sketch};
13pub use stack::on_csg_stack;
14
15use glam::DVec3;
16use rscad_core::Object;
17
18// ── Unified Trait Definitions ──────────────────────────────
19
20/// Marker trait for CSG backends.
21///
22/// Each backend defines a zero-sized marker type implementing this trait,
23/// specifying the concrete 3D and 2D output types and operations.
24pub trait CsgBackend {
25    /// 3D solid type produced by this backend.
26    type CsgType;
27    /// 2D sketch / cross-section type produced by this backend.
28    type SketchType;
29
30    // ── Empty values ───────────────────────────────────────
31    fn empty_csg() -> Self::CsgType;
32    fn empty_sketch() -> Self::SketchType;
33
34    // ── 3D Boolean operations ──────────────────────────────
35    fn union(a: Self::CsgType, b: Self::CsgType) -> Self::CsgType;
36    fn difference(a: Self::CsgType, b: Self::CsgType) -> Self::CsgType;
37    fn intersection(a: Self::CsgType, b: Self::CsgType) -> Self::CsgType;
38    fn xor(a: Self::CsgType, b: Self::CsgType) -> Self::CsgType;
39
40    // ── 2D Boolean operations ──────────────────────────────
41    fn union_2d(a: Self::SketchType, b: Self::SketchType) -> Self::SketchType;
42    fn difference_2d(a: Self::SketchType, b: Self::SketchType) -> Self::SketchType;
43    fn intersection_2d(a: Self::SketchType, b: Self::SketchType) -> Self::SketchType;
44    fn xor_2d(a: Self::SketchType, b: Self::SketchType) -> Self::SketchType;
45
46    // ── 3D Transformations ─────────────────────────────────
47    fn translate(csg: Self::CsgType, offset: DVec3) -> Self::CsgType;
48    fn rotate(csg: Self::CsgType, angles: DVec3) -> Self::CsgType;
49    fn scale(csg: Self::CsgType, factors: DVec3) -> Self::CsgType;
50    fn mirror(csg: Self::CsgType, axes: DVec3) -> Self::CsgType;
51
52    // ── 2D Transformations ─────────────────────────────────
53    fn translate_2d(sketch: Self::SketchType, offset: DVec3) -> Self::SketchType;
54    fn rotate_2d(sketch: Self::SketchType, angles: DVec3) -> Self::SketchType;
55    fn scale_2d(sketch: Self::SketchType, factors: DVec3) -> Self::SketchType;
56    fn mirror_2d(sketch: Self::SketchType, axes: DVec3) -> Self::SketchType;
57
58    // ── Color ──────────────────────────────────────────────
59    fn color(csg: Self::CsgType, color: [f32; 4]) -> Self::CsgType;
60    fn color_2d(sketch: Self::SketchType, color: [f32; 4]) -> Self::SketchType;
61
62    // ── Extrusion (2D → 3D) ───────────────────────────────
63    fn extrude(
64        sketch: Self::SketchType,
65        height: f64,
66        twist: f64,
67        scale: f64,
68        slices: usize,
69    ) -> Self::CsgType;
70
71    /// Revolve a sketch about its local Y axis (OpenSCAD `rotate_extrude`).
72    ///
73    /// Profile-x is the radius from the axis (x ≥ 0 expected; negative-x
74    /// regions produce undefined results), profile-y the height along it.
75    fn revolve(sketch: Self::SketchType, angle_degrees: f64, segments: usize) -> Self::CsgType;
76}
77
78/// Convert an `rscad-core` 3D geometry type into a backend's solid.
79pub trait ToCsg<B: CsgBackend> {
80    fn to_csg(&self) -> B::CsgType;
81}
82
83/// Convert an `rscad-core` 2D geometry type into a backend's sketch.
84pub trait ToSketch<B: CsgBackend> {
85    fn to_sketch(&self) -> B::SketchType;
86}
87
88/// Backend can produce Bevy meshes from its 3D solid type.
89#[cfg(feature = "bevy")]
90pub trait CsgToMesh: CsgBackend {
91    fn to_bevy_mesh(solid: &Self::CsgType) -> bevy_mesh::Mesh;
92}
93
94/// Backend can export its 3D solid type as STL.
95pub trait CsgToStl: CsgBackend {
96    fn write_stl_binary(
97        solid: &Self::CsgType,
98        writer: &mut dyn std::io::Write,
99    ) -> std::io::Result<()>;
100
101    fn write_stl_ascii(
102        solid: &Self::CsgType,
103        writer: &mut dyn std::io::Write,
104    ) -> std::io::Result<()>;
105}
106
107// ── Blanket impls: Transformations ─────────────────────────
108
109impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for rscad_core::Translate<T> {
110    fn to_csg(&self) -> B::CsgType {
111        B::translate(self.object.to_csg(), self.translation)
112    }
113}
114
115impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for rscad_core::Translate<T> {
116    fn to_sketch(&self) -> B::SketchType {
117        B::translate_2d(self.object.to_sketch(), self.translation)
118    }
119}
120
121impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for rscad_core::Scale<T> {
122    fn to_csg(&self) -> B::CsgType {
123        B::scale(self.object.to_csg(), self.scale)
124    }
125}
126
127impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for rscad_core::Scale<T> {
128    fn to_sketch(&self) -> B::SketchType {
129        B::scale_2d(self.object.to_sketch(), self.scale)
130    }
131}
132
133impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for rscad_core::Rotate<T> {
134    fn to_csg(&self) -> B::CsgType {
135        B::rotate(self.object.to_csg(), self.rotation)
136    }
137}
138
139impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for rscad_core::Rotate<T> {
140    fn to_sketch(&self) -> B::SketchType {
141        B::rotate_2d(self.object.to_sketch(), self.rotation)
142    }
143}
144
145impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for rscad_core::Mirror<T> {
146    fn to_csg(&self) -> B::CsgType {
147        B::mirror(self.object.to_csg(), self.mirror)
148    }
149}
150
151impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for rscad_core::Mirror<T> {
152    fn to_sketch(&self) -> B::SketchType {
153        B::mirror_2d(self.object.to_sketch(), self.mirror)
154    }
155}
156
157// ── Blanket impls: Boolean operations ──────────────────────
158
159impl<B: CsgBackend, A: Object + ToCsg<B>, O: Object + ToCsg<B>> ToCsg<B>
160    for rscad_core::Union<A, O>
161{
162    fn to_csg(&self) -> B::CsgType {
163        B::union(self.this.to_csg(), self.other.to_csg())
164    }
165}
166
167impl<B: CsgBackend, A: Object + ToSketch<B>, O: Object + ToSketch<B>> ToSketch<B>
168    for rscad_core::Union<A, O>
169{
170    fn to_sketch(&self) -> B::SketchType {
171        B::union_2d(self.this.to_sketch(), self.other.to_sketch())
172    }
173}
174
175impl<B: CsgBackend, A: Object + ToCsg<B>, O: Object + ToCsg<B>> ToCsg<B>
176    for rscad_core::Difference<A, O>
177{
178    fn to_csg(&self) -> B::CsgType {
179        B::difference(self.this.to_csg(), self.other.to_csg())
180    }
181}
182
183impl<B: CsgBackend, A: Object + ToSketch<B>, O: Object + ToSketch<B>> ToSketch<B>
184    for rscad_core::Difference<A, O>
185{
186    fn to_sketch(&self) -> B::SketchType {
187        B::difference_2d(self.this.to_sketch(), self.other.to_sketch())
188    }
189}
190
191impl<B: CsgBackend, A: Object + ToCsg<B>, O: Object + ToCsg<B>> ToCsg<B>
192    for rscad_core::Intersection<A, O>
193{
194    fn to_csg(&self) -> B::CsgType {
195        B::intersection(self.this.to_csg(), self.other.to_csg())
196    }
197}
198
199impl<B: CsgBackend, A: Object + ToSketch<B>, O: Object + ToSketch<B>> ToSketch<B>
200    for rscad_core::Intersection<A, O>
201{
202    fn to_sketch(&self) -> B::SketchType {
203        B::intersection_2d(self.this.to_sketch(), self.other.to_sketch())
204    }
205}
206
207impl<B: CsgBackend, A: Object + ToCsg<B>, O: Object + ToCsg<B>> ToCsg<B> for rscad_core::Xor<A, O> {
208    fn to_csg(&self) -> B::CsgType {
209        B::xor(self.this.to_csg(), self.other.to_csg())
210    }
211}
212
213impl<B: CsgBackend, A: Object + ToSketch<B>, O: Object + ToSketch<B>> ToSketch<B>
214    for rscad_core::Xor<A, O>
215{
216    fn to_sketch(&self) -> B::SketchType {
217        B::xor_2d(self.this.to_sketch(), self.other.to_sketch())
218    }
219}
220
221// ── Blanket impls: Containers / wrappers ───────────────────
222
223impl<B: CsgBackend, A: Object + ToCsg<B>, O: Object + ToCsg<B>> ToCsg<B>
224    for rscad_core::TwoContainer<A, O>
225{
226    fn to_csg(&self) -> B::CsgType {
227        B::union(self.a.to_csg(), self.b.to_csg())
228    }
229}
230
231impl<B: CsgBackend, A: Object + ToSketch<B>, O: Object + ToSketch<B>> ToSketch<B>
232    for rscad_core::TwoContainer<A, O>
233{
234    fn to_sketch(&self) -> B::SketchType {
235        B::union_2d(self.a.to_sketch(), self.b.to_sketch())
236    }
237}
238
239impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for rscad_core::Colored<T> {
240    fn to_csg(&self) -> B::CsgType {
241        let c = [
242            self.color.r as f32 / 255.0,
243            self.color.g as f32 / 255.0,
244            self.color.b as f32 / 255.0,
245            self.color.a as f32 / 255.0,
246        ];
247        B::color(self.object.to_csg(), c)
248    }
249}
250
251impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for rscad_core::Colored<T> {
252    fn to_sketch(&self) -> B::SketchType {
253        let c = [
254            self.color.r as f32 / 255.0,
255            self.color.g as f32 / 255.0,
256            self.color.b as f32 / 255.0,
257            self.color.a as f32 / 255.0,
258        ];
259        B::color_2d(self.object.to_sketch(), c)
260    }
261}
262
263impl<B: CsgBackend> ToCsg<B> for rscad_core::Empty {
264    fn to_csg(&self) -> B::CsgType {
265        B::empty_csg()
266    }
267}
268
269impl<B: CsgBackend> ToSketch<B> for rscad_core::Empty {
270    fn to_sketch(&self) -> B::SketchType {
271        B::empty_sketch()
272    }
273}
274
275impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for Option<T> {
276    fn to_csg(&self) -> B::CsgType {
277        match self {
278            Some(inner) => inner.to_csg(),
279            None => B::empty_csg(),
280        }
281    }
282}
283
284impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for Option<T> {
285    fn to_sketch(&self) -> B::SketchType {
286        match self {
287            Some(inner) => inner.to_sketch(),
288            None => B::empty_sketch(),
289        }
290    }
291}
292
293impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for Vec<T> {
294    fn to_csg(&self) -> B::CsgType {
295        self.iter()
296            .fold(B::empty_csg(), |acc, item| B::union(acc, item.to_csg()))
297    }
298}
299
300impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for Vec<T> {
301    fn to_sketch(&self) -> B::SketchType {
302        self.iter().fold(B::empty_sketch(), |acc, item| {
303            B::union_2d(acc, item.to_sketch())
304        })
305    }
306}
307
308impl<B: CsgBackend, T: Object + ToCsg<B>> ToCsg<B> for Box<T> {
309    fn to_csg(&self) -> B::CsgType {
310        (**self).to_csg()
311    }
312}
313
314impl<B: CsgBackend, T: Object + ToSketch<B>> ToSketch<B> for Box<T> {
315    fn to_sketch(&self) -> B::SketchType {
316        (**self).to_sketch()
317    }
318}
319
320impl<B: CsgBackend, T: Object + ToCsg<B>, const N: usize> ToCsg<B> for [T; N] {
321    fn to_csg(&self) -> B::CsgType {
322        self.iter()
323            .fold(B::empty_csg(), |acc, item| B::union(acc, item.to_csg()))
324    }
325}
326
327impl<B: CsgBackend, T: Object + ToSketch<B>, const N: usize> ToSketch<B> for [T; N] {
328    fn to_sketch(&self) -> B::SketchType {
329        self.iter().fold(B::empty_sketch(), |acc, item| {
330            B::union_2d(acc, item.to_sketch())
331        })
332    }
333}
334
335impl<B: CsgBackend> ToCsg<B> for () {
336    fn to_csg(&self) -> B::CsgType {
337        B::empty_csg()
338    }
339}
340
341impl<B: CsgBackend> ToSketch<B> for () {
342    fn to_sketch(&self) -> B::SketchType {
343        B::empty_sketch()
344    }
345}
346
347// ── Blanket impl: LinearExtrude (2D → 3D) ─────────────────
348
349impl<B: CsgBackend, T: Object + ToSketch<B>> ToCsg<B> for rscad_core::extrude::LinearExtrude<T> {
350    fn to_csg(&self) -> B::CsgType {
351        B::extrude(
352            self.object.to_sketch(),
353            self.height,
354            self.twist,
355            self.scale,
356            self.slices,
357        )
358    }
359}
360
361// ── Blanket impl: RotateExtrude (2D → 3D) ─────────────────
362
363impl<B: CsgBackend, T: Object + ToSketch<B>> ToCsg<B> for rscad_core::extrude::RotateExtrude<T> {
364    fn to_csg(&self) -> B::CsgType {
365        B::revolve(self.object.to_sketch(), self.angle_degrees, self.fn_)
366    }
367}