geop_core_topology/model/
iterate.rs1use std::collections::HashSet;
2
3use crate::{
4 Body, CoedgeGeometry, CoedgeId, EdgeId, FaceId, Sense, SolidId, VertexId,
5 boundary::{BoundaryIndex, BoundaryType},
6};
7use geop_core_math::geop_error::{GeopError, GeopResult};
8
9use super::Model;
10
11pub type Corners = Vec<(VertexId, [f64; 3])>;
14
15impl Model {
16 pub fn coedges_of_edge(&self, edge: EdgeId) -> Vec<CoedgeId> {
19 self.coedges
20 .iter()
21 .filter(|(_, c)| c.geometry == CoedgeGeometry::Edge(edge))
22 .map(|(id, _)| *id)
23 .collect()
24 }
25
26 pub fn iterate_loop_coedges(&self, anchor: CoedgeId) -> impl Iterator<Item = CoedgeId> + '_ {
33 std::iter::successors(Some(anchor), move |c| {
34 let next = self.coedges[c].next;
35 (next != anchor).then_some(next)
36 })
37 }
38
39 pub fn face_corners(&self, face_id: FaceId) -> GeopResult<Vec<Corners>> {
44 let at = |v: VertexId| -> GeopResult<(VertexId, [f64; 3])> {
45 let p = self.get_vertex(v)?.point;
46 Ok((v, p.to_f64_array()))
47 };
48 self.get_face(face_id)?
49 .boundaries()
50 .map(|boundary| match boundary {
51 BoundaryType::Vertex(v) => Ok(vec![at(v)?]),
52 BoundaryType::Loop(anchor) => self
53 .iterate_loop_coedges(anchor)
54 .map(|c| at(self.coedge_start_vertex_id(c)?))
55 .collect(),
56 })
57 .collect()
58 }
59
60 pub fn iterate_face_coedges(&self, face_id: FaceId) -> impl Iterator<Item = CoedgeId> + '_ {
63 self.faces[&face_id]
64 .boundaries()
65 .filter_map(|boundary| match boundary {
66 BoundaryType::Loop(anchor) => Some(anchor),
67 BoundaryType::Vertex(_) => None,
68 })
69 .flat_map(|anchor| self.iterate_loop_coedges(anchor))
70 }
71
72 pub fn face_edges(&self, face_id: FaceId) -> impl Iterator<Item = EdgeId> + '_ {
76 self.iterate_face_coedges(face_id)
77 .filter_map(|c| match self.coedges[&c].geometry {
78 CoedgeGeometry::Edge(edge) => Some(edge),
79 CoedgeGeometry::Vertex(_) => None,
80 })
81 }
82
83 pub fn edges_at_vertex(&self, vertex: VertexId) -> Vec<EdgeId> {
85 self.edges
86 .iter()
87 .filter(|(_, e)| e.start_vertex == vertex || e.end_vertex == vertex)
88 .map(|(&id, _)| id)
89 .collect()
90 }
91
92 pub fn faces_at_vertex(&self, vertex: VertexId) -> GeopResult<Vec<FaceId>> {
95 let mut faces = Vec::new();
96 for (&id, coedge) in &self.coedges {
97 if coedge.edge().is_ok() && self.coedge_start_vertex_id(id)? == vertex {
98 faces.push(coedge.face);
99 }
100 }
101 faces.sort();
102 faces.dedup();
103 Ok(faces)
104 }
105
106 pub fn faces_of_edge(&self, edge: EdgeId) -> Option<[FaceId; 2]> {
110 let mut forward = None;
111 let mut reversed = None;
112 for c in self.coedges_of_edge(edge) {
113 let coedge = &self.coedges[&c];
114 match coedge.sense {
115 Sense::Forward => forward = Some(coedge.face),
116 Sense::Reversed => reversed = Some(coedge.face),
117 }
118 }
119 Some([forward?, reversed?])
120 }
121
122 fn body_coedge_geometry(
124 &self,
125 body: impl Into<Body>,
126 ) -> GeopResult<impl Iterator<Item = CoedgeGeometry> + '_> {
127 Ok(self
128 .body_faces(body)?
129 .into_iter()
130 .flat_map(|face| self.iterate_face_coedges(face))
131 .map(|c| self.coedges[&c].geometry))
132 }
133
134 pub fn iter_body_vertices(
139 &self,
140 body: impl Into<Body>,
141 ) -> GeopResult<impl Iterator<Item = VertexId> + '_> {
142 let mut seen = HashSet::new();
143 Ok(self
144 .body_coedge_geometry(body)?
145 .flat_map(|geometry| match geometry {
146 CoedgeGeometry::Edge(edge_id) => {
147 let edge = &self.edges[&edge_id];
148 vec![edge.start_vertex, edge.end_vertex]
149 }
150 CoedgeGeometry::Vertex(vertex_id) => vec![vertex_id],
151 })
152 .filter(move |&v| seen.insert(v)))
153 }
154
155 pub fn iter_body_edges(
158 &self,
159 body: impl Into<Body>,
160 ) -> GeopResult<impl Iterator<Item = EdgeId> + '_> {
161 let mut seen = HashSet::new();
162 Ok(self
163 .body_coedge_geometry(body)?
164 .filter_map(|geometry| match geometry {
165 CoedgeGeometry::Edge(edge_id) => Some(edge_id),
166 CoedgeGeometry::Vertex(_) => None,
167 })
168 .filter(move |&e| seen.insert(e)))
169 }
170
171 pub fn find_boundary_containing(
182 &self,
183 face_id: FaceId,
184 coedge: CoedgeId,
185 ) -> GeopResult<BoundaryIndex> {
186 let face = &self.faces[&face_id];
187 let contains = |boundary: BoundaryType| match boundary {
188 BoundaryType::Loop(anchor) => self.iterate_loop_coedges(anchor).any(|c| c == coedge),
189 BoundaryType::Vertex(_) => false,
190 };
191 if contains(face.outer) {
192 return Ok(BoundaryIndex::Outer);
193 }
194 face.holes
195 .iter()
196 .position(|&h| contains(h))
197 .map(BoundaryIndex::Hole)
198 .ok_or_else(|| {
199 GeopError::new(format!(
200 "Model::find_boundary_containing: could not find a boundary containing coedge={coedge} on face={face_id}"
201 ))
202 })
203 }
204
205 pub fn remove_boundary(&mut self, face_id: FaceId, index: BoundaryIndex) -> GeopResult<()> {
212 let BoundaryIndex::Hole(i) = index else {
213 return Err(GeopError::new(format!(
214 "Model::remove_boundary: refusing to remove face={face_id}'s outer boundary — a face must always have exactly one"
215 )));
216 };
217 let face = self.get_face_mut(face_id)?;
218 if i >= face.holes.len() {
219 return Err(GeopError::new(format!(
220 "Model::remove_boundary: face={face_id} has no hole at index {i}"
221 )));
222 }
223 face.holes.remove(i);
224 Ok(())
225 }
226
227 pub fn solid_faces(&self, solid_id: SolidId) -> GeopResult<Vec<FaceId>> {
229 self.body_faces(solid_id)
230 }
231
232 pub fn body_faces(&self, body: impl Into<Body>) -> GeopResult<Vec<FaceId>> {
235 let shells = match body.into() {
236 Body::Solid(solid) => self.get_solid(solid)?.shells.clone(),
237 Body::Sheet(shell) => vec![shell],
238 };
239 let mut faces = Vec::new();
240 for shell_id in shells {
241 faces.extend(self.get_shell(shell_id)?.faces.iter().copied());
242 }
243 Ok(faces)
244 }
245
246 pub fn solid_of_face(&self, face_id: FaceId) -> GeopResult<Option<SolidId>> {
248 Ok(match self.body_of_face(face_id)? {
249 Body::Solid(solid) => Some(solid),
250 Body::Sheet(_) => None,
251 })
252 }
253
254 pub fn body_of_face(&self, face_id: FaceId) -> GeopResult<Body> {
257 let shell_id = self.get_face(face_id)?.shell;
258 Ok(match self.get_shell(shell_id)?.solid {
259 Some(solid) => Body::Solid(solid),
260 None => Body::Sheet(shell_id),
261 })
262 }
263}