1use 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#[derive(Clone, Debug)]
33pub struct NamedModel {
34 pub(crate) topology: Model,
35 pub(crate) names: NameRegistry,
36 pub(crate) features: Vec<(String, Arc<Feature>)>,
39}
40
41impl 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
52impl 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
66pub fn model() -> TargetReference<NamedModel> {
69 TargetReference::new("model")
70}
71
72pub 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 pub fn new() -> Self {
87 Self {
88 topology: Model::new(),
89 names: NameRegistry::new(),
90 features: Vec::new(),
91 }
92 }
93
94 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 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 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 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 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 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 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 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 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 #[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 #[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 #[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 #[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}