Skip to main content

geop_core_topology/edit/
assemble_solid.rs

1use std::collections::HashSet;
2
3use crate::{
4    CoedgeGeometry, CoedgeId, EdgeId, FaceId, Model, Shell, Solid, SolidId, VertexId,
5    boundary::BoundaryType,
6};
7use geop_core_math::{
8    geop_error::{GeopError, GeopResult},
9    scalars::Scalar,
10};
11
12impl<S: Scalar> Model<S> {
13    /// Replace the solids `consumed` by one new solid of a single shell made
14    /// of `keep`, which must be faces of those solids. Every other face they
15    /// owned is deleted, and so is everything no face reaches any more.
16    ///
17    /// What a boolean does last, once it has decided which faces survive.
18    /// `Ok(None)` (and no solid) when `keep` is empty: the result is empty,
19    /// which is an answer rather than a failure.
20    ///
21    /// Deletion is by reachability rather than by tracking what was split or
22    /// re-homed on the way here, so no dangling id can be left behind — and
23    /// it only ever deletes from what `consumed` owned: faces of any other
24    /// solid in the model are none of this operation's business.
25    pub fn assemble_solid(
26        &mut self,
27        consumed: &[SolidId],
28        keep: &[FaceId],
29    ) -> GeopResult<Option<SolidId>> {
30        let mut consumed_faces: HashSet<FaceId> = HashSet::new();
31        for &solid in consumed {
32            consumed_faces.extend(self.solid_faces(solid)?);
33        }
34        if let Some(face) = keep.iter().find(|f| !consumed_faces.contains(f)) {
35            return Err(GeopError::new(format!(
36                "Model::assemble_solid: face {face} is to be kept, but belongs to none of the consumed solids {consumed:?}"
37            )));
38        }
39        for &solid in consumed {
40            for shell in self.get_solid(solid)?.shells.clone() {
41                self.shells.remove(&shell);
42            }
43            self.solids.remove(&solid);
44        }
45
46        let kept: HashSet<FaceId> = keep.iter().copied().collect();
47        self.faces
48            .retain(|id, _| !consumed_faces.contains(id) || kept.contains(id));
49
50        if keep.is_empty() {
51            self.prune_unreachable();
52            return Ok(None);
53        }
54
55        let shell_id = self.insert_shell(Shell {
56            faces: keep.to_vec(),
57            solid: SolidId(0),
58        });
59        let solid_id = self.insert_solid(Solid {
60            shells: vec![shell_id],
61        });
62        self.get_shell_mut(shell_id)?.solid = solid_id;
63        for &face_id in keep {
64            self.get_face_mut(face_id)?.shell = shell_id;
65        }
66
67        self.prune_unreachable();
68        Ok(Some(solid_id))
69    }
70
71    /// Move every shell of `from` into `into`, deleting `from`: one solid of
72    /// both bodies. Only valid for bodies that don't touch — nothing is
73    /// intersected, so two overlapping ones would make a solid whose shells
74    /// cross; combining those is a boolean union's job.
75    pub fn merge_solids(&mut self, into: SolidId, from: SolidId) -> GeopResult<()> {
76        if into == from {
77            return Err(GeopError::new(format!(
78                "Model::merge_solids: refusing to merge solid {into} into itself"
79            )));
80        }
81        self.get_solid(into)?;
82        let shells = self.get_solid(from)?.shells.clone();
83        for &shell in &shells {
84            self.get_shell_mut(shell)?.solid = into;
85        }
86        self.solids.remove(&from);
87        self.get_solid_mut(into)?.shells.extend(shells);
88        Ok(())
89    }
90
91    /// Drop everything no longer reachable from a face: the coedges no face
92    /// owns, then edges and vertices nothing refers to any more.
93    fn prune_unreachable(&mut self) {
94        let live_faces: Vec<FaceId> = self.faces.keys().copied().collect();
95        let live_coedges: HashSet<CoedgeId> = live_faces
96            .iter()
97            .flat_map(|&f| self.iterate_face_coedges(f).collect::<Vec<_>>())
98            .collect();
99        self.coedges.retain(|id, _| live_coedges.contains(id));
100
101        let live_edges: HashSet<EdgeId> = self
102            .coedges
103            .values()
104            .filter_map(|c| c.edge().ok())
105            .collect();
106        self.edges.retain(|id, _| live_edges.contains(id));
107
108        let mut live_vertices: HashSet<VertexId> = self
109            .edges
110            .values()
111            .flat_map(|e| [e.start_vertex, e.end_vertex])
112            .collect();
113        for coedge in self.coedges.values() {
114            if let CoedgeGeometry::Vertex(v) = coedge.geometry {
115                live_vertices.insert(v);
116            }
117        }
118        for face in self.faces.values() {
119            for boundary in face.boundaries() {
120                if let BoundaryType::Vertex(v) = boundary {
121                    live_vertices.insert(v);
122                }
123            }
124        }
125        self.vertices.retain(|id, _| live_vertices.contains(id));
126    }
127}