geop_core_topology/edit/
assemble_solid.rs1use std::collections::{HashMap, HashSet};
2
3use imbl::OrdMap;
4
5use crate::{
6 Body, CoedgeGeometry, CoedgeId, EdgeId, FaceId, Model, Shell, ShellId, Solid, SolidId,
7 VertexId, boundary::BoundaryType,
8};
9use geop_core_math::{
10 geop_error::{GeopError, GeopResult},
11 union_find::UnionFind,
12};
13
14impl Model {
15 pub fn assemble_solid(
33 &mut self,
34 consumed: &[Body],
35 keep: &[FaceId],
36 ) -> GeopResult<Option<SolidId>> {
37 let shells = self.assemble_shells(consumed, &connected(self, keep))?;
38 if shells.is_empty() {
39 return Ok(None);
40 }
41 let solid_id = self.insert_solid(Solid {
42 shells: shells.clone(),
43 });
44 for shell_id in shells {
45 self.get_shell_mut(shell_id)?.solid = Some(solid_id);
46 }
47 Ok(Some(solid_id))
48 }
49
50 pub fn assemble_sheet(
54 &mut self,
55 consumed: &[Body],
56 keep: &[FaceId],
57 ) -> GeopResult<Option<ShellId>> {
58 let groups = if keep.is_empty() {
59 Vec::new()
60 } else {
61 vec![keep.to_vec()]
62 };
63 Ok(self.assemble_shells(consumed, &groups)?.first().copied())
64 }
65
66 fn assemble_shells(
69 &mut self,
70 consumed: &[Body],
71 groups: &[Vec<FaceId>],
72 ) -> GeopResult<Vec<ShellId>> {
73 let mut consumed_faces: HashSet<FaceId> = HashSet::new();
74 for &body in consumed {
75 consumed_faces.extend(self.body_faces(body)?);
76 }
77 if let Some(face) = groups
78 .iter()
79 .flatten()
80 .find(|f| !consumed_faces.contains(f))
81 {
82 return Err(GeopError::new(format!(
83 "Model::assemble_solid: face {face} is to be kept, but belongs to none of the consumed bodies {consumed:?}"
84 )));
85 }
86 for &body in consumed {
87 match body {
88 Body::Solid(solid) => {
89 for shell in self.get_solid(solid)?.shells.clone() {
90 self.shells.remove(&shell);
91 }
92 self.solids.remove(&solid);
93 }
94 Body::Sheet(shell) => {
95 self.shells.remove(&shell);
96 }
97 }
98 }
99
100 let kept: HashSet<FaceId> = groups.iter().flatten().copied().collect();
101 retain(&mut self.faces, |id| {
102 !consumed_faces.contains(id) || kept.contains(id)
103 });
104
105 let mut shells = Vec::with_capacity(groups.len());
106 for faces in groups {
107 let shell_id = self.insert_shell(Shell {
108 faces: faces.clone(),
109 solid: None,
110 });
111 for &face_id in faces {
112 self.get_face_mut(face_id)?.shell = shell_id;
113 }
114 shells.push(shell_id);
115 }
116 self.prune_unreachable();
117 Ok(shells)
118 }
119
120 pub fn merge_solids(&mut self, into: SolidId, from: SolidId) -> GeopResult<()> {
125 if into == from {
126 return Err(GeopError::new(format!(
127 "Model::merge_solids: refusing to merge solid {into} into itself"
128 )));
129 }
130 self.get_solid(into)?;
131 let shells = self.get_solid(from)?.shells.clone();
132 for &shell in &shells {
133 self.get_shell_mut(shell)?.solid = Some(into);
134 }
135 self.solids.remove(&from);
136 self.get_solid_mut(into)?.shells.extend(shells);
137 Ok(())
138 }
139
140 pub(crate) fn prune_unreachable(&mut self) {
144 let live_faces: Vec<FaceId> = self.faces.keys().copied().collect();
145 let live_coedges: HashSet<CoedgeId> = live_faces
146 .iter()
147 .flat_map(|&f| self.iterate_face_coedges(f).collect::<Vec<_>>())
148 .collect();
149 retain(&mut self.coedges, |id| live_coedges.contains(id));
150
151 let live_edges: HashSet<EdgeId> = self
152 .coedges
153 .values()
154 .filter_map(|c| c.edge().ok())
155 .chain(self.wires.values().flat_map(|w| w.edges.iter().copied()))
156 .collect();
157 retain(&mut self.edges, |id| live_edges.contains(id));
158
159 let mut live_vertices: HashSet<VertexId> = self
160 .edges
161 .values()
162 .flat_map(|e| [e.start_vertex, e.end_vertex])
163 .collect();
164 for coedge in self.coedges.values() {
165 if let CoedgeGeometry::Vertex(v) = coedge.geometry {
166 live_vertices.insert(v);
167 }
168 }
169 for face in self.faces.values() {
170 for boundary in face.boundaries() {
171 if let BoundaryType::Vertex(v) = boundary {
172 live_vertices.insert(v);
173 }
174 }
175 }
176 for wire in self.wires.values() {
177 live_vertices.extend(wire.vertices.iter().copied());
178 }
179 retain(&mut self.vertices, |id| live_vertices.contains(id));
180 }
181}
182
183fn connected(model: &Model, faces: &[FaceId]) -> Vec<Vec<FaceId>> {
186 let mut sets = UnionFind::new(faces.len());
187 let mut first_user: HashMap<EdgeId, usize> = HashMap::new();
188 for (i, &face) in faces.iter().enumerate() {
189 for coedge in model.iterate_face_coedges(face) {
190 let Some(Ok(edge)) = model.coedges.get(&coedge).map(|c| c.edge()) else {
191 continue;
192 };
193 match first_user.get(&edge) {
194 Some(&j) => sets.union(i, j),
195 None => {
196 first_user.insert(edge, i);
197 }
198 }
199 }
200 }
201 let mut groups = sets.groups();
202 for group in &mut groups {
203 group.sort();
204 }
205 groups.sort_by_key(|g| g[0]);
206 groups
207 .into_iter()
208 .map(|g| g.into_iter().map(|i| faces[i]).collect())
209 .collect()
210}
211
212fn retain<K: Ord + Clone, V: Clone>(map: &mut OrdMap<K, V>, keep: impl Fn(&K) -> bool) {
215 let dropped: Vec<K> = map.keys().filter(|id| !keep(id)).cloned().collect();
216 for id in dropped {
217 map.remove(&id);
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use crate::{Model, test_fixtures::test_cube_solid};
224
225 #[test]
229 fn pruning_a_copy_keeps_it_shared() {
230 let mut model = Model::new();
231 test_cube_solid(&mut model);
232 let copy = model.clone();
233 model.prune_unreachable();
234 assert!(model.vertices.ptr_eq(©.vertices));
235 assert!(model.edges.ptr_eq(©.edges));
236 assert!(model.coedges.ptr_eq(©.coedges));
237 assert!(model.faces.ptr_eq(©.faces));
238 }
239}