Another project
1use std::collections::HashSet;
2
3use bone_types::{EdgeLabel, FaceLabel, SketchEntityId, SolidKey, VertexLabel};
4use ron::extensions::Extensions;
5use serde::{Deserialize, Serialize};
6use truck_modeling::{EdgeID, Face, FaceID, Shell, Solid, VertexID};
7
8use super::build::{SolidLabeling, assemble};
9use super::edges::EdgeCurve3;
10use super::{BrepError, BrepSolid, LabelKind};
11
12const GRID_DECIMALS: usize = 6;
13
14fn blob_options() -> ron::Options {
15 ron::Options::default().with_default_extension(Extensions::UNWRAP_NEWTYPES)
16}
17
18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct EdgeReattach {
21 pub label: EdgeLabel,
22 pub curve: Option<EdgeCurve3>,
23}
24
25#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct BrepReattach {
28 faces: Vec<FaceLabel>,
29 edges: Vec<EdgeReattach>,
30 vertices: Vec<VertexLabel>,
31 closed_curves: Vec<SketchEntityId>,
32 order: SolidKey,
33}
34
35impl BrepReattach {
36 #[must_use]
37 pub fn faces(&self) -> &[FaceLabel] {
38 &self.faces
39 }
40
41 #[must_use]
42 pub fn edges(&self) -> &[EdgeReattach] {
43 &self.edges
44 }
45
46 #[must_use]
47 pub fn vertices(&self) -> &[VertexLabel] {
48 &self.vertices
49 }
50}
51
52pub(super) fn ordered_faces(solid: &Solid) -> Vec<FaceID> {
53 solid
54 .boundaries()
55 .iter()
56 .flat_map(Shell::face_iter)
57 .map(Face::id)
58 .collect()
59}
60
61pub(super) fn ordered_edges(solid: &Solid) -> Vec<EdgeID> {
62 let mut seen = HashSet::new();
63 solid
64 .edge_iter()
65 .map(|edge| edge.id())
66 .filter(|id| seen.insert(*id))
67 .collect()
68}
69
70pub(super) fn ordered_vertices(solid: &Solid) -> Vec<VertexID> {
71 let mut seen = HashSet::new();
72 solid
73 .vertex_iter()
74 .map(|vertex| vertex.id())
75 .filter(|id| seen.insert(*id))
76 .collect()
77}
78
79pub(crate) fn capture(solid: &Solid, labeling: &SolidLabeling) -> Result<BrepReattach, BrepError> {
80 let faces = ordered_faces(solid)
81 .into_iter()
82 .map(|id| {
83 labeling
84 .faces
85 .get(&id)
86 .copied()
87 .ok_or(BrepError::MissingLabel {
88 kind: LabelKind::Face,
89 })
90 })
91 .collect::<Result<Vec<_>, _>>()?;
92 let edges = ordered_edges(solid)
93 .into_iter()
94 .map(|id| {
95 let label = labeling
96 .edges
97 .get(&id)
98 .copied()
99 .ok_or(BrepError::MissingLabel {
100 kind: LabelKind::Edge,
101 })?;
102 Ok(EdgeReattach {
103 label,
104 curve: labeling.edge_curves.get(&id).cloned(),
105 })
106 })
107 .collect::<Result<Vec<_>, BrepError>>()?;
108 let vertices = ordered_vertices(solid)
109 .into_iter()
110 .map(|id| {
111 labeling
112 .vertices
113 .get(&id)
114 .copied()
115 .ok_or(BrepError::MissingLabel {
116 kind: LabelKind::Vertex,
117 })
118 })
119 .collect::<Result<Vec<_>, _>>()?;
120 let mut closed_curves: Vec<SketchEntityId> = labeling.closed_curves.iter().copied().collect();
121 closed_curves.sort_unstable();
122 Ok(BrepReattach {
123 faces,
124 edges,
125 vertices,
126 closed_curves,
127 order: order_key(solid),
128 })
129}
130
131pub(super) fn to_labeling(
132 solid: &Solid,
133 reattach: &BrepReattach,
134) -> Result<SolidLabeling, BrepError> {
135 if order_key(solid) != reattach.order {
136 return Err(BrepError::ReattachOrder);
137 }
138 let face_ids = ordered_faces(solid);
139 let edge_ids = ordered_edges(solid);
140 let vertex_ids = ordered_vertices(solid);
141 expect_count(LabelKind::Face, face_ids.len(), reattach.faces.len())?;
142 expect_count(LabelKind::Edge, edge_ids.len(), reattach.edges.len())?;
143 expect_count(LabelKind::Vertex, vertex_ids.len(), reattach.vertices.len())?;
144
145 let faces = face_ids
146 .iter()
147 .zip(&reattach.faces)
148 .map(|(id, label)| (*id, *label))
149 .collect();
150 let edges = edge_ids
151 .iter()
152 .zip(&reattach.edges)
153 .map(|(id, edge)| (*id, edge.label))
154 .collect();
155 let edge_curves = edge_ids
156 .iter()
157 .zip(&reattach.edges)
158 .filter_map(|(id, edge)| edge.curve.clone().map(|curve| (*id, curve)))
159 .collect();
160 let vertices = vertex_ids
161 .iter()
162 .zip(&reattach.vertices)
163 .map(|(id, label)| (*id, *label))
164 .collect();
165 let closed_curves = reattach.closed_curves.iter().copied().collect();
166 Ok(SolidLabeling {
167 faces,
168 edges,
169 vertices,
170 closed_curves,
171 edge_curves,
172 })
173}
174
175fn expect_count(kind: LabelKind, found: usize, expected: usize) -> Result<(), BrepError> {
176 if found == expected {
177 Ok(())
178 } else {
179 Err(BrepError::ReattachMismatch {
180 kind,
181 found,
182 expected,
183 })
184 }
185}
186
187fn quantize(value: f64) -> String {
188 let prec = GRID_DECIMALS;
189 let text = format!("{value:.prec$}");
190 if text.bytes().all(|byte| matches!(byte, b'-' | b'0' | b'.')) {
191 format!("{:.prec$}", 0.0)
192 } else {
193 text
194 }
195}
196
197fn point_token(point: truck_modeling::Point3) -> String {
198 format!(
199 "{},{},{}",
200 quantize(point.x),
201 quantize(point.y),
202 quantize(point.z)
203 )
204}
205
206fn edge_token(front: truck_modeling::Point3, back: truck_modeling::Point3) -> String {
207 let mut ends = [point_token(front), point_token(back)];
208 ends.sort_unstable();
209 ends.join("|")
210}
211
212fn digest_to_key(canonical: &str) -> SolidKey {
213 let digest = blake3::hash(canonical.as_bytes());
214 let mut key = [0u8; 16];
215 key.copy_from_slice(&digest.as_bytes()[..16]);
216 SolidKey::from_bytes(key)
217}
218
219pub(super) fn face_centroid(face: &Face) -> truck_modeling::Point3 {
220 let (x, y, z, count) = face
221 .boundaries()
222 .iter()
223 .flat_map(|wire| wire.vertex_iter().map(|vertex| vertex.point()))
224 .fold((0.0, 0.0, 0.0, 0.0_f64), |(x, y, z, count), point| {
225 (x + point.x, y + point.y, z + point.z, count + 1.0)
226 });
227 if count > 0.0 {
228 truck_modeling::Point3::new(x / count, y / count, z / count)
229 } else {
230 truck_modeling::Point3::new(0.0, 0.0, 0.0)
231 }
232}
233
234fn face_token(face: &Face) -> String {
235 let mut edges: Vec<String> = face
236 .boundaries()
237 .iter()
238 .flat_map(|wire| wire.edge_iter())
239 .map(|edge| edge_token(edge.front().point(), edge.back().point()))
240 .collect();
241 edges.sort_unstable();
242 edges.dedup();
243 format!("{}|{}", point_token(face_centroid(face)), edges.join(","))
244}
245
246pub(super) fn content_key(solid: &Solid) -> SolidKey {
247 let mut vertices: Vec<String> = solid
248 .vertex_iter()
249 .map(|vertex| point_token(vertex.point()))
250 .collect();
251 vertices.sort_unstable();
252 vertices.dedup();
253
254 let mut edges: Vec<String> = solid
255 .edge_iter()
256 .map(|edge| edge_token(edge.front().point(), edge.back().point()))
257 .collect();
258 edges.sort_unstable();
259 edges.dedup();
260
261 digest_to_key(&format!(
262 "t:{}/{}/{}\nv:{}\n{}\ne:{}\n{}\n",
263 ordered_faces(solid).len(),
264 ordered_edges(solid).len(),
265 ordered_vertices(solid).len(),
266 vertices.len(),
267 vertices.join("\n"),
268 edges.len(),
269 edges.join("\n")
270 ))
271}
272
273pub(super) fn order_key(solid: &Solid) -> SolidKey {
274 let mut seen_vertices = HashSet::new();
275 let vertices: Vec<String> = solid
276 .vertex_iter()
277 .filter(|vertex| seen_vertices.insert(vertex.id()))
278 .map(|vertex| point_token(vertex.point()))
279 .collect();
280 let mut seen_edges = HashSet::new();
281 let edges: Vec<String> = solid
282 .edge_iter()
283 .filter(|edge| seen_edges.insert(edge.id()))
284 .map(|edge| edge_token(edge.front().point(), edge.back().point()))
285 .collect();
286 let faces: Vec<String> = solid
287 .boundaries()
288 .iter()
289 .flat_map(Shell::face_iter)
290 .map(face_token)
291 .collect();
292 digest_to_key(&format!(
293 "ov:{}\noe:{}\nof:{}\n",
294 vertices.join("|"),
295 edges.join("|"),
296 faces.join("|")
297 ))
298}
299
300impl BrepSolid {
301 pub fn to_blob(&self) -> Result<Vec<u8>, BrepError> {
302 blob_options()
303 .to_string(self.arena.solid())
304 .map(String::into_bytes)
305 .map_err(|_| BrepError::BlobSerialize)
306 }
307
308 #[must_use]
309 pub fn reattach_data(&self) -> &BrepReattach {
310 &self.reattach
311 }
312
313 #[must_use]
314 pub fn content_key(&self) -> SolidKey {
315 content_key(self.arena.solid())
316 }
317
318 pub fn from_blob(bytes: &[u8], reattach: &BrepReattach) -> Result<BrepSolid, BrepError> {
319 let text = core::str::from_utf8(bytes).map_err(|_| BrepError::BlobParse)?;
320 let solid: Solid = blob_options()
321 .from_str(text)
322 .map_err(|_| BrepError::BlobParse)?;
323 let labeling = to_labeling(&solid, reattach)?;
324 assemble(solid, &labeling)
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::quantize;
331
332 #[test]
333 fn quantize_collapses_sub_grid_magnitudes_to_unsigned_zero() {
334 assert_eq!(quantize(0.0), "0.000000");
335 assert_eq!(quantize(-0.0), "0.000000");
336 assert_eq!(quantize(4.0e-7), "0.000000");
337 assert_eq!(quantize(-4.0e-7), "0.000000");
338 assert_eq!(quantize(-1.0e-12), "0.000000");
339 assert_eq!(quantize(4.0e-7), quantize(-4.0e-7));
340 }
341
342 #[test]
343 fn quantize_keeps_signed_values_above_the_grid() {
344 assert_eq!(quantize(-1.0e-6), "-0.000001");
345 assert_eq!(quantize(1.0e-6), "0.000001");
346 assert_eq!(quantize(-2.5), "-2.500000");
347 }
348}