1use std::collections::BTreeMap;
2
3use geop_core_math::{
4 geop_error::{GeopError, GeopResult},
5 scalars::Scalar,
6};
7use geop_core_topology::Model;
8
9use crate::datum::Datum;
10use crate::ids::{DatumId, RefId, SketchId};
11use crate::names::NameRegistry;
12use crate::sketch::PlacedSketch;
13
14#[derive(Clone)]
26pub struct Part<S: Scalar> {
27 pub(crate) topology: Model<S>,
28 pub(crate) names: NameRegistry,
29 pub(crate) sketches: BTreeMap<SketchId, PlacedSketch<S>>,
30 pub(crate) datums: BTreeMap<DatumId, Datum<S>>,
31 next_id: u64,
34}
35
36impl<S: Scalar> Part<S> {
37 pub fn new() -> Self {
38 Self {
39 topology: Model::new(),
40 names: NameRegistry::new(),
41 sketches: BTreeMap::new(),
42 datums: BTreeMap::new(),
43 next_id: 1,
44 }
45 }
46
47 pub fn topology(&self) -> &Model<S> {
50 &self.topology
51 }
52
53 pub fn names(&self) -> &NameRegistry {
54 &self.names
55 }
56
57 pub fn name_of(&self, id: impl Into<RefId>) -> Option<&str> {
58 self.names.name_of(id)
59 }
60
61 pub fn id_of(&self, name: &str) -> Option<RefId> {
62 self.names.id_of(name)
63 }
64
65 pub fn rename(&mut self, id: impl Into<RefId>, new_name: impl Into<String>) -> GeopResult<()> {
68 self.names.rename(id, new_name)
69 }
70
71 pub(crate) fn fresh_id(&mut self) -> u64 {
73 let id = self.next_id;
74 self.next_id += 1;
75 id
76 }
77
78 fn exists(&self, id: RefId) -> bool {
79 match id {
80 RefId::Vertex(id) => self.topology.vertices.contains_key(&id),
81 RefId::Edge(id) => self.topology.edges.contains_key(&id),
82 RefId::Face(id) => self.topology.faces.contains_key(&id),
83 RefId::Solid(id) => self.topology.solids.contains_key(&id),
84 RefId::Sketch(id) => self.sketches.contains_key(&id),
85 RefId::Datum(id) => self.datums.contains_key(&id),
86 }
87 }
88
89 pub(crate) fn forget_dead_names(&mut self) {
93 let alive: std::collections::HashSet<RefId> = self
94 .names
95 .iter()
96 .map(|(id, _)| id)
97 .filter(|&id| self.exists(id))
98 .collect();
99 self.names.retain(|id| alive.contains(&id));
100 }
101
102 pub fn check_names(&self) -> GeopResult<()> {
106 let topology = &self.topology;
107 let entities = topology
108 .vertices
109 .keys()
110 .map(|&id| RefId::from(id))
111 .chain(topology.edges.keys().map(|&id| id.into()))
112 .chain(topology.faces.keys().map(|&id| id.into()))
113 .chain(topology.solids.keys().map(|&id| id.into()))
114 .chain(self.sketches.keys().map(|&id| id.into()))
115 .chain(self.datums.keys().map(|&id| id.into()));
116 let unnamed: Vec<String> = entities
117 .filter(|&id| self.names.name_of(id).is_none())
118 .map(|id| id.to_string())
119 .collect();
120 let dead: Vec<&str> = self
121 .names
122 .iter()
123 .filter(|&(id, _)| !self.exists(id))
124 .map(|(_, name)| name)
125 .collect();
126 if unnamed.is_empty() && dead.is_empty() {
127 Ok(())
128 } else {
129 Err(GeopError::new(format!(
130 "Part::check_names: unnamed entities {unnamed:?}, names of deleted entities {dead:?}"
131 )))
132 }
133 }
134}
135
136impl<S: Scalar> Default for Part<S> {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use geop_core_math::{scalars::scal_in_f64::ScalInF64, vector::Vector3};
145 use geop_core_sketch::Sketch;
146
147 use super::*;
148 use crate::PlacedSketch;
149
150 fn origin() -> Vector3<ScalInF64> {
151 Vector3::from_array([ScalInF64::from_f64(0.0); 3])
152 }
153
154 #[test]
157 fn mvfs_and_kvfs_keep_names_in_sync() {
158 let mut part = Part::<ScalInF64>::new();
159 let (vertex, face, solid) = part.mvfs(origin(), "v0", "f0", "s0").unwrap();
160
161 assert_eq!(part.name_of(vertex), Some("v0"));
162 assert_eq!(part.name_of(face), Some("f0"));
163 assert_eq!(part.name_of(solid), Some("s0"));
164 assert_eq!(part.id_of("v0"), Some(RefId::Vertex(vertex)));
165 part.check_names().unwrap();
166
167 part.kvfs(solid).unwrap();
168
169 assert_eq!(part.name_of(vertex), None);
170 assert_eq!(part.name_of(face), None);
171 assert_eq!(part.name_of(solid), None);
172 assert!(part.topology().vertices.is_empty());
173 assert!(part.topology().faces.is_empty());
174 assert!(part.topology().solids.is_empty());
175 part.check_names().unwrap();
176 }
177
178 #[test]
180 fn duplicate_name_is_rejected() {
181 let mut part = Part::<ScalInF64>::new();
182 part.mvfs(origin(), "v0", "f0", "s0").unwrap();
183
184 let err = part.mvfs(origin(), "v1", "f1", "s0");
185 assert!(err.is_err());
186 }
187
188 #[test]
191 fn sketches_are_named() {
192 let mut part = Part::<ScalInF64>::new();
193 let placed = PlacedSketch {
194 plane: geop_core_math::primitives::CoordinateSystem::try_new(
195 origin(),
196 Vector3::from_array([ScalInF64::ONE, ScalInF64::ZERO, ScalInF64::ZERO]),
197 Vector3::from_array([ScalInF64::ZERO, ScalInF64::ONE, ScalInF64::ZERO]),
198 Vector3::from_array([ScalInF64::ZERO, ScalInF64::ZERO, ScalInF64::ONE]),
199 )
200 .unwrap(),
201 sketch: Sketch::new(),
202 };
203 let id = part.add_sketch(placed, "sketch0").unwrap();
204 assert_eq!(part.name_of(id), Some("sketch0"));
205 assert_eq!(part.sketch_id("sketch0").unwrap(), id);
206 part.check_names().unwrap();
207
208 part.remove_sketch(id).unwrap();
209 assert_eq!(part.name_of(id), None);
210 assert!(part.sketch(id).is_err());
211 }
212
213 #[test]
215 fn rename_settles_a_provisional_name() {
216 let mut part = Part::<ScalInF64>::new();
217 let (vertex, _, _) = part.mvfs(origin(), "v~0", "f0", "s0").unwrap();
218 assert!(part.rename(vertex, "f0").is_err());
219 part.rename(vertex, "v0").unwrap();
220 assert_eq!(part.name_of(vertex), Some("v0"));
221 assert_eq!(part.id_of("v~0"), None);
222 }
223}