Skip to main content

geop_ops_model/
named_model.rs

1//! [`NamedModel`]: a part's topology with a name for every entity in it
2//! ([`NameRegistry`], [`Namer`]) — changed only through its own methods,
3//! so no entity is ever without a name.
4
5use std::sync::Arc;
6
7use geop_core_math::geop_error::{GeopError, GeopResult};
8use geop_core_program::{
9    element::ElementReference,
10    kept::Keeper,
11    target::{Target, TargetReference},
12};
13use geop_core_topology::Model;
14
15use crate::{Feature, ids::RefId, names::NameRegistry};
16
17/// A part's boundary-representation topology and the name of every vertex,
18/// edge, face and solid in it: what the steps of a program build, one
19/// revision each (see [`model`]).
20///
21/// The fields are private: the only way to change the topology is through
22/// its methods, each of which forwards straight to the identically named
23/// [`Model`] operation, registers every entity it created under the name
24/// the caller supplied, and forgets the name of every one it deleted. That
25/// keeps the invariant [`NamedModel::check_names`] checks — every entity
26/// has exactly one name, and every name one entity — true by construction
27/// rather than by each caller's diligence.
28///
29/// Both halves share their structure with the revisions made from them, so
30/// a revision costs what it changes, and comparing two revisions what tells
31/// them apart.
32#[derive(Clone, Debug)]
33pub struct NamedModel {
34    pub(crate) topology: Model,
35    pub(crate) names: NameRegistry,
36    /// What each step that combined tools with a solid did, in the order
37    /// the steps ran (see [`Feature`]).
38    pub(crate) features: Vec<(String, Arc<Feature>)>,
39}
40
41/// Every step that builds into the model produces it from the last; it is
42/// kept between processes, exactly, its entities shared between revisions
43/// ([`crate::kept`]).
44impl Target for NamedModel {
45    const CHAIN: bool = true;
46    const KEEPER: Option<Keeper<Self>> = Some(Keeper {
47        write: crate::kept::write,
48        read: crate::kept::read,
49    });
50}
51
52/// Equal when the topology, the names and the features are: a model built
53/// again into the same value is the same model, however it was built — what
54/// a version rebuilt unchanged, and a part built from scratch, are compared
55/// by. A feature shared with the other model is equal without a look.
56impl PartialEq for NamedModel {
57    fn eq(&self, other: &Self) -> bool {
58        self.topology == other.topology
59            && self.names == other.names
60            && self.features.len() == other.features.len()
61            && (self.features.iter().zip(&other.features))
62                .all(|((a, f), (b, g))| a == b && (Arc::ptr_eq(f, g) || f == g))
63    }
64}
65
66/// The model a part's steps build: defined by the first that builds any
67/// topology, revised by every later one.
68pub fn model() -> TargetReference<NamedModel> {
69    TargetReference::new("model")
70}
71
72/// The element `name` of the model, of the kind `K`: what a step's
73/// arguments refer to it by.
74pub fn element<K>(name: impl Into<String>) -> ElementReference<NamedModel, K> {
75    ElementReference::new(model(), name)
76}
77
78impl Default for NamedModel {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl NamedModel {
85    /// A model with nothing in it.
86    pub fn new() -> Self {
87        Self {
88            topology: Model::new(),
89            names: NameRegistry::new(),
90            features: Vec::new(),
91        }
92    }
93
94    /// The topology, to query. Changing it goes through the model's own
95    /// methods, so that names stay in sync.
96    pub fn topology(&self) -> &Model {
97        &self.topology
98    }
99
100    pub fn names(&self) -> &NameRegistry {
101        &self.names
102    }
103
104    pub fn name_of(&self, id: impl Into<RefId>) -> Option<&str> {
105        self.names.name_of(id)
106    }
107
108    /// The name of `id` — an error, naming the id, if it has none.
109    pub fn name(&self, id: impl Into<RefId>) -> GeopResult<&str> {
110        let id = id.into();
111        (self.name_of(id)).ok_or_else(|| GeopError::new(format!("{id} has no name")))
112    }
113
114    pub fn id_of(&self, name: &str) -> Option<RefId> {
115        self.names.id_of(name)
116    }
117
118    /// The vertices, edges and faces `text` — a validation report, which
119    /// knows only ids — mentions, as `id = name`, sorted.
120    pub fn names_mentioned(&self, text: &str) -> Vec<String> {
121        let model = &self.topology;
122        let mentioned = |forms: [String; 3]| forms.iter().any(|f| text.contains(f.as_str()));
123        let mut names = Vec::new();
124        let mut name =
125            |id: RefId| names.push(format!("{id} = {}", self.name_of(id).unwrap_or("?")));
126        for &id in model.vertices.keys() {
127            if mentioned([
128                format!("{id}"),
129                format!("vertex {} ", id.0),
130                format!("vertex {}'", id.0),
131            ]) {
132                name(id.into());
133            }
134        }
135        for &id in model.edges.keys() {
136            if mentioned([
137                format!("{id}"),
138                format!("edge {} ", id.0),
139                format!("edge {}'", id.0),
140            ]) {
141                name(id.into());
142            }
143        }
144        for &id in model.faces.keys() {
145            if mentioned([
146                format!("{id}"),
147                format!("face {} ", id.0),
148                format!("face {},", id.0),
149            ]) {
150                name(id.into());
151            }
152        }
153        names.sort();
154        names
155    }
156
157    /// Gives `id` the name `new_name` instead — see [`NameRegistry::rename`]
158    /// for the one situation this is for.
159    pub fn rename(&mut self, id: impl Into<RefId>, new_name: impl Into<String>) -> GeopResult<()> {
160        self.names.rename(id, new_name)
161    }
162
163    /// Gives `id` back the name `earlier` of what it turned out to be — a
164    /// face a boolean only trimmed, though its splits left it a piece's
165    /// name — its name so far standing for it too, so that what referred
166    /// to it by that still finds it.
167    pub fn restore_name(&mut self, id: impl Into<RefId>, earlier: &str) -> GeopResult<()> {
168        let id = id.into();
169        let current = self.name(id)?.to_string();
170        self.names.rename(id, earlier)?;
171        self.names.alias(current, earlier)
172    }
173
174    /// Lets the name `earlier`, of something now part of `id`, stand for it,
175    /// `id` keeping its own name.
176    pub fn stand_for(&mut self, id: impl Into<RefId>, earlier: &str) -> GeopResult<()> {
177        let current = self.name(id.into())?.to_string();
178        self.names.alias(earlier, current)
179    }
180
181    fn exists(&self, id: RefId) -> bool {
182        let topology = &self.topology;
183        match id {
184            RefId::Vertex(id) => topology.vertices.contains_key(&id),
185            RefId::Edge(id) => topology.edges.contains_key(&id),
186            RefId::Face(id) => topology.faces.contains_key(&id),
187            RefId::Solid(id) => topology.solids.contains_key(&id),
188        }
189    }
190
191    /// Forgets the name of every entity that no longer exists — for an
192    /// operation that deletes by reachability rather than one id at a time
193    /// (see [`Model::assemble_solid`]).
194    pub(crate) fn forget_dead_names(&mut self) {
195        let dead: Vec<RefId> = (self.names.iter())
196            .map(|(id, _)| id)
197            .filter(|&id| !self.exists(id))
198            .collect();
199        for id in dead {
200            self.names.remove(id);
201        }
202    }
203
204    /// Checks the invariant every method keeps: every vertex, edge, face
205    /// and solid has a name, and every name belongs to one of them.
206    pub fn check_names(&self) -> GeopResult<()> {
207        let topology = &self.topology;
208        let entities = (topology.vertices.keys().map(|&id| RefId::from(id)))
209            .chain(topology.edges.keys().map(|&id| id.into()))
210            .chain(topology.faces.keys().map(|&id| id.into()))
211            .chain(topology.solids.keys().map(|&id| id.into()));
212        let unnamed: Vec<String> = entities
213            .filter(|&id| self.name_of(id).is_none())
214            .map(|id| id.to_string())
215            .collect();
216        let dead: Vec<&str> = (self.names.iter())
217            .filter(|&(id, _)| !self.exists(id))
218            .map(|(_, name)| name)
219            .collect();
220        if unnamed.is_empty() && dead.is_empty() {
221            Ok(())
222        } else {
223            Err(GeopError::new(format!(
224                "NamedModel::check_names: unnamed entities {unnamed:?}, names of deleted entities {dead:?}"
225            )))
226        }
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use geop_core_math::vector::Vector3;
233    use geop_core_topology::{Body, VertexId, build::BodySpec};
234
235    use super::*;
236    use crate::{BodyNames, testing::unit_cube};
237
238    /// A free point named `name` at the origin: a wire of the vertex alone.
239    fn point(model: &mut NamedModel, name: &str) -> GeopResult<VertexId> {
240        let mut spec = BodySpec::new(false);
241        spec.vertices.push(Vector3::zero());
242        let names = BodyNames {
243            vertices: vec![name.to_string()],
244            edges: Vec::new(),
245            faces: Vec::new(),
246            solid: None,
247        };
248        Ok(model.build_body(spec, names)?.vertices[0])
249    }
250
251    /// Building names every entity it builds, and deleting a body forgets
252    /// the names of all of its entities again.
253    #[test]
254    fn building_and_deleting_keep_names_in_sync() {
255        let mut model = NamedModel::new();
256        let vertex = point(&mut model, "v0").unwrap();
257        assert_eq!(model.name_of(vertex), Some("v0"));
258        assert_eq!(model.id_of("v0"), Some(RefId::Vertex(vertex)));
259        model.check_names().unwrap();
260
261        let mut cube = unit_cube();
262        cube.check_names().unwrap();
263        let RefId::Solid(solid) = cube.id_of("s0").unwrap() else {
264            panic!("s0 names a solid");
265        };
266        cube.delete_bodies(&[Body::Solid(solid)]).unwrap();
267        assert_eq!(cube.id_of("v0"), None);
268        assert_eq!(cube.id_of("f0"), None);
269        assert_eq!(cube.id_of("s0"), None);
270        assert!(cube.topology().vertices.is_empty());
271        cube.check_names().unwrap();
272    }
273
274    /// Reusing a name that's already taken is rejected.
275    #[test]
276    fn duplicate_name_is_rejected() {
277        let mut model = NamedModel::new();
278        point(&mut model, "v0").unwrap();
279        assert!(point(&mut model, "v0").is_err());
280    }
281
282    /// A provisional name can be settled, but not onto a name in use.
283    #[test]
284    fn rename_settles_a_provisional_name() {
285        let mut model = NamedModel::new();
286        let vertex = point(&mut model, "v~0").unwrap();
287        point(&mut model, "v1").unwrap();
288        assert!(model.rename(vertex, "v1").is_err());
289        model.rename(vertex, "v0").unwrap();
290        assert_eq!(model.name_of(vertex), Some("v0"));
291        assert_eq!(model.id_of("v~0"), None);
292    }
293
294    /// A revision shares what it leaves untouched, and comparing it with
295    /// the model it was made from tells whether it changed.
296    #[test]
297    fn a_revision_is_equal_until_changed() {
298        let mut model = NamedModel::new();
299        point(&mut model, "v0").unwrap();
300        let mut revision = model.clone();
301        assert_eq!(revision, model);
302        point(&mut revision, "v1").unwrap();
303        assert_ne!(revision, model);
304    }
305}