Skip to main content

geop_ops_model/
resolve.rs

1//! Looking an entity up by its name instead of its id — what every
2//! operation that refers to existing entities by name is built on.
3
4use geop_core_geometry::shape::Plane;
5use geop_core_math::geop_error::{GeopError, GeopResult};
6use geop_core_topology::{Body, EdgeId, FaceId, ShellId, SolidId, VertexId};
7
8use super::NamedModel;
9use super::ids::RefId;
10
11impl NamedModel {
12    /// `name`'s id, checked to be the particular kind `extract` accepts.
13    fn named<T>(
14        &self,
15        name: &str,
16        kind: &str,
17        extract: impl Fn(RefId) -> Option<T>,
18    ) -> GeopResult<T> {
19        let id = self.names.id_of(name).ok_or_else(|| {
20            // The names of the same kind that the same step made: usually
21            // what was meant, if a later step renamed or split it.
22            let step = name.split_once(',').map_or(name, |(head, _)| head);
23            let step = step.split_once('(').map_or(step, |(_, id)| id);
24            let mut similar: Vec<&str> = self
25                .names()
26                .iter()
27                .map(|(_, n)| n)
28                .filter(|n| n.contains(step))
29                .collect();
30            similar.sort_unstable();
31            similar.truncate(12);
32            GeopError::new(format!(
33                "no entity is named {name:?} (names of {step:?}: {similar:?})"
34            ))
35        })?;
36        extract(id).ok_or_else(|| GeopError::new(format!("{name:?} names {id}, not a {kind}")))
37    }
38
39    pub fn vertex_id(&self, name: &str) -> GeopResult<VertexId> {
40        self.named(name, "vertex", |r| match r {
41            RefId::Vertex(id) => Some(id),
42            _ => None,
43        })
44    }
45
46    pub fn edge_id(&self, name: &str) -> GeopResult<EdgeId> {
47        self.named(name, "edge", |r| match r {
48            RefId::Edge(id) => Some(id),
49            _ => None,
50        })
51    }
52
53    pub fn face_id(&self, name: &str) -> GeopResult<FaceId> {
54        self.named(name, "face", |r| match r {
55            RefId::Face(id) => Some(id),
56            _ => None,
57        })
58    }
59
60    pub fn solid_id(&self, name: &str) -> GeopResult<SolidId> {
61        self.named(name, "solid", |r| match r {
62            RefId::Solid(id) => Some(id),
63            _ => None,
64        })
65    }
66
67    /// The solid `face` is part of. An error for a face of a sheet, naming
68    /// the face and saying `why` it has to be a solid's.
69    pub fn solid_of_face(&self, face: FaceId, why: &str) -> GeopResult<SolidId> {
70        match self.topology().body_of_face(face)? {
71            Body::Solid(solid) => Ok(solid),
72            Body::Sheet(_) => Err(GeopError::new(format!(
73                "face {:?} stands on its own: {why}",
74                self.name(face)?
75            ))),
76        }
77    }
78
79    /// The name of the solid `face` is part of, as [`Self::solid_of_face`].
80    pub fn solid_name_of_face(&self, face: FaceId, why: &str) -> GeopResult<String> {
81        let solid = self.solid_of_face(face, why)?;
82        Ok(self.name(solid)?.to_string())
83    }
84
85    /// The sheet `face` is part of. An error for a face of a solid, naming
86    /// the face and saying `why` it has to stand on its own.
87    pub fn sheet_of_face(&self, face: FaceId, why: &str) -> GeopResult<ShellId> {
88        match self.topology().body_of_face(face)? {
89            Body::Sheet(sheet) => Ok(sheet),
90            Body::Solid(_) => Err(GeopError::new(format!(
91                "face {:?} is a face of a solid, not one standing on its own: {why}",
92                self.name(face)?
93            ))),
94        }
95    }
96
97    /// The plane `face` lies in. An error for a face that is not planar,
98    /// naming the face and saying `why` it has to be.
99    pub fn plane_of_face(&self, face: FaceId, why: &str) -> GeopResult<Plane> {
100        match self.topology().get_face(face)?.surface.as_plane()? {
101            Some(plane) => Ok(plane),
102            None => Err(GeopError::new(format!(
103                "face {:?} is not planar: {why}",
104                self.name(face)?
105            ))),
106        }
107    }
108
109    /// Every solid's name, oldest first.
110    pub fn solid_names(&self) -> Vec<String> {
111        // The arenas are ordered by id, the order of creation.
112        self.topology()
113            .solids
114            .keys()
115            .filter_map(|&s| self.name_of(s).map(str::to_string))
116            .collect()
117    }
118
119    /// The newest solid's name, if there is any: what a field picking a
120    /// solid starts out holding.
121    pub fn newest_solid(&self) -> Option<String> {
122        self.solid_names().pop()
123    }
124
125    /// The name of every face standing on its own — of a sheet, part of no
126    /// solid — oldest first.
127    pub fn sheet_face_names(&self) -> Vec<String> {
128        let model = self.topology();
129        model
130            .faces
131            .iter()
132            .filter(|(_, f)| {
133                model
134                    .shells
135                    .get(&f.shell)
136                    .is_some_and(|s| s.solid.is_none())
137            })
138            .filter_map(|(&f, _)| self.name_of(f).map(str::to_string))
139            .collect()
140    }
141}