Skip to main content

geop_ops/part/
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_math::{
5    geop_error::{GeopError, GeopResult},
6    scalars::Scalar,
7};
8use geop_core_topology::{CoedgeId, EdgeId, FaceId, Sense, SolidId, VertexId};
9
10use super::Part;
11use super::ids::{DatumId, RefId, SketchId};
12
13impl<S: Scalar> Part<S> {
14    /// `name`'s id, checked to be the particular kind `extract` accepts.
15    fn named<T>(
16        &self,
17        name: &str,
18        kind: &str,
19        extract: impl Fn(RefId) -> Option<T>,
20    ) -> GeopResult<T> {
21        let id = self
22            .names
23            .id_of(name)
24            .ok_or_else(|| GeopError::new(format!("no entity is named {name:?}")))?;
25        extract(id).ok_or_else(|| GeopError::new(format!("{name:?} names {id}, not a {kind}")))
26    }
27
28    pub fn vertex_id(&self, name: &str) -> GeopResult<VertexId> {
29        self.named(name, "vertex", |r| match r {
30            RefId::Vertex(id) => Some(id),
31            _ => None,
32        })
33    }
34
35    pub fn edge_id(&self, name: &str) -> GeopResult<EdgeId> {
36        self.named(name, "edge", |r| match r {
37            RefId::Edge(id) => Some(id),
38            _ => None,
39        })
40    }
41
42    pub fn face_id(&self, name: &str) -> GeopResult<FaceId> {
43        self.named(name, "face", |r| match r {
44            RefId::Face(id) => Some(id),
45            _ => None,
46        })
47    }
48
49    pub fn solid_id(&self, name: &str) -> GeopResult<SolidId> {
50        self.named(name, "solid", |r| match r {
51            RefId::Solid(id) => Some(id),
52            _ => None,
53        })
54    }
55
56    pub fn sketch_id(&self, name: &str) -> GeopResult<SketchId> {
57        self.named(name, "sketch", |r| match r {
58            RefId::Sketch(id) => Some(id),
59            _ => None,
60        })
61    }
62
63    /// Every solid's name, oldest first.
64    pub fn solid_names(&self) -> Vec<String> {
65        let mut solids: Vec<SolidId> = self.topology().solids.keys().copied().collect();
66        solids.sort_by_key(|s| s.0);
67        solids
68            .into_iter()
69            .filter_map(|s| self.name_of(s).map(str::to_string))
70            .collect()
71    }
72
73    /// Every sketch's name, oldest first.
74    pub fn sketch_names(&self) -> Vec<String> {
75        self.sketches()
76            .filter_map(|(id, _)| self.name_of(id).map(str::to_string))
77            .collect()
78    }
79
80    pub fn datum_id(&self, name: &str) -> GeopResult<DatumId> {
81        self.named(name, "datum", |r| match r {
82            RefId::Datum(id) => Some(id),
83            _ => None,
84        })
85    }
86
87    /// The unique coedge of `edge_name` lying on `face_name`. An edge shared
88    /// by two distinct faces has exactly one coedge per face, so this pair
89    /// identifies it unambiguously — except for a connector edge
90    /// [`geop_core_topology::Model::mve`] or
91    /// [`geop_core_topology::Model::mekr`] mints, both of whose coedges sit
92    /// on the very same face; resolve one of those with
93    /// [`Part::coedge_id_with_sense`] instead.
94    pub fn coedge_id(&self, edge_name: &str, face_name: &str) -> GeopResult<CoedgeId> {
95        let edge = self.edge_id(edge_name)?;
96        let face = self.face_id(face_name)?;
97        let mut on_face = self
98            .topology
99            .coedges_of_edge(edge)
100            .into_iter()
101            .filter(|&c| {
102                self.topology
103                    .get_coedge(c)
104                    .map(|co| co.face == face)
105                    .unwrap_or(false)
106            });
107        let found = on_face.next().ok_or_else(|| {
108            GeopError::new(format!(
109                "edge {edge_name:?} has no coedge on face {face_name:?}"
110            ))
111        })?;
112        if on_face.next().is_some() {
113            return Err(GeopError::new(format!(
114                "edge {edge_name:?} has more than one coedge on face {face_name:?} — use coedge_id_with_sense to disambiguate"
115            )));
116        }
117        Ok(found)
118    }
119
120    /// The coedge of `edge_name` on `face_name` with sense `sense` — needed
121    /// only for a connector edge whose two coedges both sit on one face (see
122    /// [`Part::coedge_id`]'s own doc comment), where the plain `(edge, face)`
123    /// pair is ambiguous.
124    pub fn coedge_id_with_sense(
125        &self,
126        edge_name: &str,
127        face_name: &str,
128        sense: Sense,
129    ) -> GeopResult<CoedgeId> {
130        let edge = self.edge_id(edge_name)?;
131        let face = self.face_id(face_name)?;
132        self.topology
133            .coedges_of_edge(edge)
134            .into_iter()
135            .find(|&c| {
136                self.topology
137                    .get_coedge(c)
138                    .map(|co| co.face == face && co.sense == sense)
139                    .unwrap_or(false)
140            })
141            .ok_or_else(|| {
142                GeopError::new(format!(
143                    "edge {edge_name:?} has no {sense:?} coedge on face {face_name:?}"
144                ))
145            })
146    }
147}