microcad_core/geo3d/
collection.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! 3D Geometry collection
5
6use std::rc::Rc;
7
8use derive_more::{Deref, DerefMut};
9
10use crate::{
11    geo3d::{CalcBounds3D, bounds::Bounds3D},
12    *,
13};
14
15/// 3D geometry collection.
16#[derive(Debug, Clone, Default, Deref, DerefMut)]
17pub struct Geometries3D(Vec<Rc<Geometry3D>>);
18
19impl Geometries3D {
20    /// New geometry collection.
21    pub fn new(geometries: Vec<Geometry3D>) -> Self {
22        Self(geometries.into_iter().map(Rc::new).collect())
23    }
24
25    /// Append another geometry collection.
26    pub fn append(&mut self, mut geometries: Geometries3D) {
27        self.0.append(&mut geometries.0)
28    }
29
30    /// Apply boolean operation on collection and render to manifold.
31    pub fn boolean_op(&self, op: &BooleanOp) -> Rc<Manifold> {
32        let manifold_list: Vec<_> = self
33            .0
34            .iter()
35            // Render each geometry into a multipolygon and filter out empty ones
36            .filter_map(|geo| {
37                let manifold: Rc<Manifold> = geo.as_ref().clone().into();
38                if manifold.is_empty() {
39                    None
40                } else {
41                    Some(manifold)
42                }
43            })
44            .collect();
45
46        if manifold_list.is_empty() {
47            return Rc::new(Manifold::empty());
48        }
49
50        manifold_list[1..]
51            .iter()
52            .fold(manifold_list[0].clone(), |acc, other| {
53                Rc::new(acc.boolean_op(other, op.into()))
54            })
55    }
56}
57
58impl FromIterator<Rc<Geometry3D>> for Geometries3D {
59    fn from_iter<T: IntoIterator<Item = Rc<Geometry3D>>>(iter: T) -> Self {
60        Geometries3D(iter.into_iter().collect())
61    }
62}
63
64impl CalcBounds3D for Geometries3D {
65    fn calc_bounds_3d(&self) -> Bounds3D {
66        self.0.iter().fold(Bounds3D::default(), |bounds, geometry| {
67            bounds.extend(geometry.calc_bounds_3d())
68        })
69    }
70}
71
72impl Transformed3D for Geometries3D {
73    fn transformed_3d(&self, mat: &Mat4) -> Self {
74        Self(
75            self.iter()
76                .map(|geometry| Rc::new(geometry.transformed_3d(mat)))
77                .collect::<Vec<_>>(),
78        )
79    }
80}