1use std::collections::BTreeMap;
6
7use geop_core_math::{
8 geop_error::{GeopError, GeopResult},
9 primitives::{CoordinateSystem, Datum, DatumKind},
10 scalars::Scalar,
11 vector::Vector3,
12};
13use geop_core_topology::Model;
14
15mod datum;
16mod describe;
17mod edit;
18mod euler;
19mod ids;
20mod names;
21mod resolve;
22mod sketch;
23
24pub use describe::{EdgeDescription, FaceDescription, PartDescription};
25pub use ids::{DatumId, RefId, SketchId};
26pub use names::{NameRegistry, Namer, validate_operation_id};
27pub use sketch::PlacedSketch;
28
29#[derive(Clone)]
41pub struct Part<S: Scalar> {
42 pub(crate) topology: Model<S>,
43 pub(crate) names: NameRegistry,
44 pub(crate) sketches: BTreeMap<SketchId, PlacedSketch<S>>,
45 pub(crate) datums: BTreeMap<DatumId, Datum<S>>,
46 next_id: u64,
49}
50
51pub const ORIGIN: &str = "origin";
55
56impl<S: Scalar> Part<S> {
57 pub fn new() -> Self {
59 let mut part = Self {
60 topology: Model::new(),
61 names: NameRegistry::new(),
62 sketches: BTreeMap::new(),
63 datums: BTreeMap::new(),
64 next_id: 1,
65 };
66 let origin = Datum {
67 kind: DatumKind::Frame,
68 frame: CoordinateSystem::world_at(Vector3::zero()),
69 };
70 part.add_datum(origin, ORIGIN)
71 .expect("a new part has no names taken");
72 part
73 }
74
75 pub fn topology(&self) -> &Model<S> {
78 &self.topology
79 }
80
81 pub fn names(&self) -> &NameRegistry {
82 &self.names
83 }
84
85 pub fn name_of(&self, id: impl Into<RefId>) -> Option<&str> {
86 self.names.name_of(id)
87 }
88
89 pub fn id_of(&self, name: &str) -> Option<RefId> {
90 self.names.id_of(name)
91 }
92
93 pub fn rename(&mut self, id: impl Into<RefId>, new_name: impl Into<String>) -> GeopResult<()> {
96 self.names.rename(id, new_name)
97 }
98
99 pub(crate) fn fresh_id(&mut self) -> u64 {
101 let id = self.next_id;
102 self.next_id += 1;
103 id
104 }
105
106 fn exists(&self, id: RefId) -> bool {
107 match id {
108 RefId::Vertex(id) => self.topology.vertices.contains_key(&id),
109 RefId::Edge(id) => self.topology.edges.contains_key(&id),
110 RefId::Face(id) => self.topology.faces.contains_key(&id),
111 RefId::Solid(id) => self.topology.solids.contains_key(&id),
112 RefId::Sketch(id) => self.sketches.contains_key(&id),
113 RefId::Datum(id) => self.datums.contains_key(&id),
114 }
115 }
116
117 pub(crate) fn forget_dead_names(&mut self) {
121 let alive: std::collections::HashSet<RefId> = self
122 .names
123 .iter()
124 .map(|(id, _)| id)
125 .filter(|&id| self.exists(id))
126 .collect();
127 self.names.retain(|id| alive.contains(&id));
128 }
129
130 pub fn check_names(&self) -> GeopResult<()> {
134 let topology = &self.topology;
135 let entities = topology
136 .vertices
137 .keys()
138 .map(|&id| RefId::from(id))
139 .chain(topology.edges.keys().map(|&id| id.into()))
140 .chain(topology.faces.keys().map(|&id| id.into()))
141 .chain(topology.solids.keys().map(|&id| id.into()))
142 .chain(self.sketches.keys().map(|&id| id.into()))
143 .chain(self.datums.keys().map(|&id| id.into()));
144 let unnamed: Vec<String> = entities
145 .filter(|&id| self.names.name_of(id).is_none())
146 .map(|id| id.to_string())
147 .collect();
148 let dead: Vec<&str> = self
149 .names
150 .iter()
151 .filter(|&(id, _)| !self.exists(id))
152 .map(|(_, name)| name)
153 .collect();
154 if unnamed.is_empty() && dead.is_empty() {
155 Ok(())
156 } else {
157 Err(GeopError::new(format!(
158 "Part::check_names: unnamed entities {unnamed:?}, names of deleted entities {dead:?}"
159 )))
160 }
161 }
162}
163
164impl<S: Scalar> Default for Part<S> {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use geop_core_math::{scalars::scal_in_f64::ScalInF64, vector::Vector3};
173 use geop_core_sketch::Sketch;
174
175 use super::*;
176
177 fn origin() -> Vector3<ScalInF64> {
178 Vector3::from_array([ScalInF64::from_f64(0.0); 3])
179 }
180
181 #[test]
184 fn mvfs_and_kvfs_keep_names_in_sync() {
185 let mut part = Part::<ScalInF64>::new();
186 let (vertex, face, solid) = part.mvfs(origin(), "v0", "f0", "s0").unwrap();
187
188 assert_eq!(part.name_of(vertex), Some("v0"));
189 assert_eq!(part.name_of(face), Some("f0"));
190 assert_eq!(part.name_of(solid), Some("s0"));
191 assert_eq!(part.id_of("v0"), Some(RefId::Vertex(vertex)));
192 part.check_names().unwrap();
193
194 part.kvfs(solid).unwrap();
195
196 assert_eq!(part.name_of(vertex), None);
197 assert_eq!(part.name_of(face), None);
198 assert_eq!(part.name_of(solid), None);
199 assert!(part.topology().vertices.is_empty());
200 assert!(part.topology().faces.is_empty());
201 assert!(part.topology().solids.is_empty());
202 part.check_names().unwrap();
203 }
204
205 #[test]
207 fn duplicate_name_is_rejected() {
208 let mut part = Part::<ScalInF64>::new();
209 part.mvfs(origin(), "v0", "f0", "s0").unwrap();
210
211 let err = part.mvfs(origin(), "v1", "f1", "s0");
212 assert!(err.is_err());
213 }
214
215 #[test]
218 fn sketches_are_named() {
219 let mut part = Part::<ScalInF64>::new();
220 let placed = PlacedSketch {
221 plane: geop_core_math::primitives::CoordinateSystem::try_new(
222 origin(),
223 Vector3::from_array([ScalInF64::ONE, ScalInF64::ZERO, ScalInF64::ZERO]),
224 Vector3::from_array([ScalInF64::ZERO, ScalInF64::ONE, ScalInF64::ZERO]),
225 Vector3::from_array([ScalInF64::ZERO, ScalInF64::ZERO, ScalInF64::ONE]),
226 )
227 .unwrap(),
228 sketch: Sketch::new(),
229 };
230 let id = part.add_sketch(placed, "sketch0").unwrap();
231 assert_eq!(part.name_of(id), Some("sketch0"));
232 assert_eq!(part.sketch_id("sketch0").unwrap(), id);
233 part.check_names().unwrap();
234
235 part.remove_sketch(id).unwrap();
236 assert_eq!(part.name_of(id), None);
237 assert!(part.sketch(id).is_err());
238 }
239
240 #[test]
242 fn rename_settles_a_provisional_name() {
243 let mut part = Part::<ScalInF64>::new();
244 let (vertex, _, _) = part.mvfs(origin(), "v~0", "f0", "s0").unwrap();
245 assert!(part.rename(vertex, "f0").is_err());
246 part.rename(vertex, "v0").unwrap();
247 assert_eq!(part.name_of(vertex), Some("v0"));
248 assert_eq!(part.id_of("v~0"), None);
249 }
250}