1use crate::topology::{
23 BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, ShellRecord, VertexRecord,
24};
25use crate::{NurbsCurve, NurbsSurface, Vec3, Vec4};
26use std::collections::BTreeMap;
27
28pub const SOLID_CODEC_VERSION: f64 = 1.0;
29
30const MAX_EXACT_ID: u64 = 1 << 53;
34
35fn id_to_f64(id: u64) -> Result<f64, String> {
36 if id > MAX_EXACT_ID {
37 return Err(format!(
38 "solid codec: id {id} exceeds 2^53 and cannot round-trip through f64"
39 ));
40 }
41 Ok(id as f64)
42}
43
44#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
45pub struct SolidNames {
46 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
47 pub faces: BTreeMap<u64, String>,
48 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
49 pub edges: BTreeMap<u64, String>,
50}
51
52fn push_curve(data: &mut Vec<f64>, curve: &NurbsCurve) {
53 data.push(curve.degree as f64);
54 data.push(curve.knots.len() as f64);
55 data.extend_from_slice(&curve.knots);
56 data.push(curve.control_points.len() as f64);
57 for point in &curve.control_points {
58 data.extend_from_slice(&[point.x, point.y, point.z, point.w]);
59 }
60}
61
62fn push_surface(data: &mut Vec<f64>, surface: &NurbsSurface) {
63 data.push(surface.degree_u as f64);
64 data.push(surface.degree_v as f64);
65 data.push(surface.knots_u.len() as f64);
66 data.extend_from_slice(&surface.knots_u);
67 data.push(surface.knots_v.len() as f64);
68 data.extend_from_slice(&surface.knots_v);
69 data.push(surface.control_points.len() as f64);
70 data.push(surface.control_points[0].len() as f64);
71 for row in &surface.control_points {
72 for point in row {
73 data.extend_from_slice(&[point.x, point.y, point.z, point.w]);
74 }
75 }
76}
77
78pub fn encode_solid(solid: &BrepSolid) -> Result<(Vec<f64>, SolidNames), String> {
79 let mut names = SolidNames::default();
80 let mut data = Vec::with_capacity(4096);
81 data.extend_from_slice(&[
82 SOLID_CODEC_VERSION,
83 id_to_f64(solid.id)?,
84 solid.genus as f64,
85 solid.vertices.len() as f64,
86 solid.edges.len() as f64,
87 solid.shells.len() as f64,
88 ]);
89 for vertex in &solid.vertices {
90 data.extend_from_slice(&[
91 id_to_f64(vertex.id)?,
92 vertex.point.x,
93 vertex.point.y,
94 vertex.point.z,
95 ]);
96 }
97 for edge in &solid.edges {
98 data.extend_from_slice(&[
99 id_to_f64(edge.id)?,
100 edge.t0,
101 edge.t1,
102 id_to_f64(edge.start_vertex_id)?,
103 id_to_f64(edge.end_vertex_id)?,
104 if edge.degenerate { 1.0 } else { 0.0 },
105 ]);
106 push_curve(&mut data, &edge.curve);
107 if let Some(name) = &edge.name {
108 names.edges.insert(edge.id, name.clone());
109 }
110 }
111 for shell in &solid.shells {
112 data.push(id_to_f64(shell.id)?);
113 data.push(shell.faces.len() as f64);
114 for face in &shell.faces {
115 data.push(id_to_f64(face.id)?);
116 data.push(if face.same_sense { 1.0 } else { 0.0 });
117 push_surface(&mut data, &face.surface);
118 data.push(face.loops.len() as f64);
119 for loop_record in &face.loops {
120 data.push(id_to_f64(loop_record.id)?);
121 data.push(loop_record.coedges.len() as f64);
122 for coedge in &loop_record.coedges {
123 data.extend_from_slice(&[
124 id_to_f64(coedge.id)?,
125 id_to_f64(coedge.edge_id)?,
126 if coedge.forward { 1.0 } else { 0.0 },
127 ]);
128 push_curve(&mut data, &coedge.pcurve);
129 }
130 }
131 if let Some(name) = &face.name {
132 names.faces.insert(face.id, name.clone());
133 }
134 }
135 }
136 Ok((data, names))
137}
138
139struct Reader<'a> {
140 data: &'a [f64],
141 cursor: usize,
142}
143
144impl<'a> Reader<'a> {
145 fn next(&mut self) -> Result<f64, String> {
146 let value = *self
147 .data
148 .get(self.cursor)
149 .ok_or("solid codec: truncated buffer")?;
150 self.cursor += 1;
151 Ok(value)
152 }
153
154 fn next_usize(&mut self) -> Result<usize, String> {
155 let value = self.next()?;
156 if value < 0.0 || value.fract() != 0.0 {
157 return Err(format!("solid codec: expected count, got {value}"));
158 }
159 Ok(value as usize)
160 }
161
162 fn next_id(&mut self) -> Result<u64, String> {
163 let value = self.next()?;
164 if value < 0.0 || value.fract() != 0.0 || value > MAX_EXACT_ID as f64 {
165 return Err(format!("solid codec: expected id, got {value}"));
166 }
167 Ok(value as u64)
168 }
169
170 fn next_slice(&mut self, count: usize) -> Result<&'a [f64], String> {
171 let end = self.cursor + count;
172 let slice = self
173 .data
174 .get(self.cursor..end)
175 .ok_or("solid codec: truncated buffer")?;
176 self.cursor = end;
177 Ok(slice)
178 }
179
180 fn curve(&mut self) -> Result<NurbsCurve, String> {
181 let degree = self.next_usize()?;
182 let knot_count = self.next_usize()?;
183 let knots = self.next_slice(knot_count)?.to_vec();
184 let control_count = self.next_usize()?;
185 let raw = self.next_slice(control_count * 4)?;
186 let control_points = raw
187 .chunks_exact(4)
188 .map(|chunk| Vec4 {
189 x: chunk[0],
190 y: chunk[1],
191 z: chunk[2],
192 w: chunk[3],
193 })
194 .collect();
195 NurbsCurve::new(degree, knots, control_points)
196 }
197
198 fn surface(&mut self) -> Result<NurbsSurface, String> {
199 let degree_u = self.next_usize()?;
200 let degree_v = self.next_usize()?;
201 let knots_u_count = self.next_usize()?;
202 let knots_u = self.next_slice(knots_u_count)?.to_vec();
203 let knots_v_count = self.next_usize()?;
204 let knots_v = self.next_slice(knots_v_count)?.to_vec();
205 let rows = self.next_usize()?;
206 let columns = self.next_usize()?;
207 let raw = self.next_slice(rows * columns * 4)?;
208 let control_points = raw
209 .chunks_exact(columns * 4)
210 .map(|row| {
211 row.chunks_exact(4)
212 .map(|chunk| Vec4 {
213 x: chunk[0],
214 y: chunk[1],
215 z: chunk[2],
216 w: chunk[3],
217 })
218 .collect()
219 })
220 .collect();
221 NurbsSurface::new(degree_u, degree_v, knots_u, knots_v, control_points)
222 }
223}
224
225pub fn decode_solid(data: &[f64], names: &SolidNames) -> Result<BrepSolid, String> {
226 let mut reader = Reader { data, cursor: 0 };
227 let version = reader.next()?;
228 if version != SOLID_CODEC_VERSION {
229 return Err(format!("solid codec: unsupported version {version}"));
230 }
231 let id = reader.next_id()?;
232 let genus = reader.next()? as i64;
233 let vertex_count = reader.next_usize()?;
234 let edge_count = reader.next_usize()?;
235 let shell_count = reader.next_usize()?;
236 let mut vertices = Vec::with_capacity(vertex_count);
237 for _ in 0..vertex_count {
238 vertices.push(VertexRecord {
239 id: reader.next_id()?,
240 point: Vec3::new(reader.next()?, reader.next()?, reader.next()?),
241 });
242 }
243 let mut edges = Vec::with_capacity(edge_count);
244 for _ in 0..edge_count {
245 let id = reader.next_id()?;
246 let t0 = reader.next()?;
247 let t1 = reader.next()?;
248 let start_vertex_id = reader.next_id()?;
249 let end_vertex_id = reader.next_id()?;
250 let degenerate = reader.next()? != 0.0;
251 let curve = reader.curve()?;
252 edges.push(EdgeRecord {
253 id,
254 curve,
255 t0,
256 t1,
257 start_vertex_id,
258 end_vertex_id,
259 degenerate,
260 name: names.edges.get(&id).cloned(),
261 });
262 }
263 let mut shells = Vec::with_capacity(shell_count);
264 for _ in 0..shell_count {
265 let shell_id = reader.next_id()?;
266 let face_count = reader.next_usize()?;
267 let mut faces = Vec::with_capacity(face_count);
268 for _ in 0..face_count {
269 let face_id = reader.next_id()?;
270 let same_sense = reader.next()? != 0.0;
271 let surface = reader.surface()?;
272 let loop_count = reader.next_usize()?;
273 let mut loops = Vec::with_capacity(loop_count);
274 for _ in 0..loop_count {
275 let loop_id = reader.next_id()?;
276 let coedge_count = reader.next_usize()?;
277 let mut coedges = Vec::with_capacity(coedge_count);
278 for _ in 0..coedge_count {
279 let coedge_id = reader.next_id()?;
280 let edge_id = reader.next_id()?;
281 let forward = reader.next()? != 0.0;
282 let pcurve = reader.curve()?;
283 coedges.push(CoedgeRecord {
284 id: coedge_id,
285 edge_id,
286 forward,
287 pcurve,
288 });
289 }
290 loops.push(LoopRecord {
291 id: loop_id,
292 coedges,
293 });
294 }
295 faces.push(FaceRecord {
296 id: face_id,
297 surface,
298 same_sense,
299 loops,
300 name: names.faces.get(&face_id).cloned(),
301 });
302 }
303 shells.push(ShellRecord {
304 id: shell_id,
305 faces,
306 });
307 }
308 if reader.cursor != data.len() {
309 return Err("solid codec: trailing data".into());
310 }
311 Ok(BrepSolid {
312 id,
313 vertices,
314 edges,
315 shells,
316 genus,
317 })
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::{boolean_operation, make_box_brep, BooleanOperation, BooleanOptions};
324
325 #[test]
326 fn ids_beyond_f64_exact_range_are_rejected_loudly() {
327 assert_eq!(id_to_f64(1 << 53).unwrap(), 9007199254740992.0);
328 assert!(id_to_f64((1 << 53) + 1).is_err());
329
330 let mut solid = make_box_brep(Vec3::default(), 1.0, 1.0, 1.0).unwrap();
331 solid.edges[0].id = (1 << 53) + 1;
332 let error = encode_solid(&solid).unwrap_err();
333 assert!(error.contains("2^53"), "unexpected error: {error}");
334
335 let (mut data, names) =
336 encode_solid(&make_box_brep(Vec3::default(), 1.0, 1.0, 1.0).unwrap()).unwrap();
337 data[1] = (MAX_EXACT_ID as f64) * 2.0;
339 assert!(decode_solid(&data, &names).is_err());
340 }
341
342 #[test]
343 fn boolean_result_round_trips_through_flat_codec() {
344 let mut first = make_box_brep(Vec3::default(), 4.0, 4.0, 4.0).unwrap();
345 for (index, face) in first
346 .shells
347 .iter_mut()
348 .flat_map(|shell| &mut shell.faces)
349 .enumerate()
350 {
351 face.name = Some(format!("A_{index}"));
352 }
353 let second = make_box_brep(Vec3::new(2.0, 1.0, 1.0), 4.0, 4.0, 4.0).unwrap();
354 let solid = boolean_operation(
355 &first,
356 &second,
357 BooleanOperation::Union,
358 &BooleanOptions::default(),
359 )
360 .unwrap();
361 let (data, names) = encode_solid(&solid).unwrap();
362 let decoded = decode_solid(&data, &names).unwrap();
363 let original = serde_json::to_string(&solid).unwrap();
364 let round_tripped = serde_json::to_string(&decoded).unwrap();
365 assert_eq!(original, round_tripped);
366 assert!(decoded.validate().is_empty());
367 }
368}