1use serde::{Deserialize, Serialize};
8
9use crate::format::CadFormat;
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct MeshBBox {
14 pub min: [f64; 3],
15 pub max: [f64; 3],
16}
17
18impl MeshBBox {
19 pub fn size(&self) -> [f64; 3] {
21 [
22 self.max[0] - self.min[0],
23 self.max[1] - self.min[1],
24 self.max[2] - self.min[2],
25 ]
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct MeshMetadata {
32 pub triangles: Option<u64>,
33 pub vertices: Option<u64>,
34 #[serde(default)]
35 pub bbox: Option<MeshBBox>,
36}
37
38pub fn extract(content: &[u8], format: CadFormat) -> Option<MeshMetadata> {
41 match format {
42 CadFormat::Stl => Some(extract_stl(content)),
43 CadFormat::Obj => Some(extract_obj(content)),
44 _ => None,
45 }
46}
47
48struct BBoxAccum {
49 min: [f64; 3],
50 max: [f64; 3],
51 any: bool,
52}
53
54impl BBoxAccum {
55 fn new() -> Self {
56 BBoxAccum {
57 min: [f64::INFINITY; 3],
58 max: [f64::NEG_INFINITY; 3],
59 any: false,
60 }
61 }
62 fn add(&mut self, p: [f64; 3]) {
63 self.any = true;
64 for (m, &v) in self.min.iter_mut().zip(p.iter()) {
65 *m = m.min(v);
66 }
67 for (m, &v) in self.max.iter_mut().zip(p.iter()) {
68 *m = m.max(v);
69 }
70 }
71 fn finish(self) -> Option<MeshBBox> {
72 self.any.then_some(MeshBBox {
73 min: self.min,
74 max: self.max,
75 })
76 }
77}
78
79fn extract_stl(content: &[u8]) -> MeshMetadata {
81 if content.len() >= 84 {
84 let count =
85 u32::from_le_bytes([content[80], content[81], content[82], content[83]]) as usize;
86 if content.len() == 84 + count * 50 {
87 return stl_binary(content, count);
88 }
89 }
90 stl_ascii(content)
91}
92
93fn stl_binary(content: &[u8], count: usize) -> MeshMetadata {
94 let mut bbox = BBoxAccum::new();
95 let read_f32 = |b: &[u8]| f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64;
96 for t in 0..count {
97 let base = 84 + t * 50 + 12; for v in 0..3 {
99 let o = base + v * 12;
100 bbox.add([
101 read_f32(&content[o..o + 4]),
102 read_f32(&content[o + 4..o + 8]),
103 read_f32(&content[o + 8..o + 12]),
104 ]);
105 }
106 }
107 MeshMetadata {
108 triangles: Some(count as u64),
109 vertices: Some(count as u64 * 3),
110 bbox: bbox.finish(),
111 }
112}
113
114fn stl_ascii(content: &[u8]) -> MeshMetadata {
115 let text = String::from_utf8_lossy(content);
116 let mut triangles = 0u64;
117 let mut bbox = BBoxAccum::new();
118 for line in text.lines() {
119 let line = line.trim_start();
120 if line.starts_with("facet") {
121 triangles += 1;
122 } else if let Some(rest) = line.strip_prefix("vertex") {
123 if let Some(p) = parse_xyz(rest) {
124 bbox.add(p);
125 }
126 }
127 }
128 MeshMetadata {
129 triangles: Some(triangles),
130 vertices: Some(triangles * 3),
131 bbox: bbox.finish(),
132 }
133}
134
135fn extract_obj(content: &[u8]) -> MeshMetadata {
137 let text = String::from_utf8_lossy(content);
138 let mut vertices = 0u64;
139 let mut triangles = 0u64;
140 let mut bbox = BBoxAccum::new();
141 for line in text.lines() {
142 let line = line.trim_start();
143 if let Some(rest) = line.strip_prefix("v ") {
144 vertices += 1;
145 if let Some(p) = parse_xyz(rest) {
146 bbox.add(p);
147 }
148 } else if let Some(rest) = line.strip_prefix("f ") {
149 let refs = rest.split_whitespace().count() as u64;
150 triangles += refs.saturating_sub(2);
151 }
152 }
153 MeshMetadata {
154 triangles: Some(triangles),
155 vertices: Some(vertices),
156 bbox: bbox.finish(),
157 }
158}
159
160fn parse_xyz(s: &str) -> Option<[f64; 3]> {
162 let mut it = s.split_whitespace();
163 let x = it.next()?.parse().ok()?;
164 let y = it.next()?.parse().ok()?;
165 let z = it.next()?.parse().ok()?;
166 Some([x, y, z])
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn ascii_stl_counts() {
175 let stl = "solid s\n\
176 facet normal 0 0 1\n outer loop\n vertex 0 0 0\n vertex 1 0 0\n vertex 0 1 0\n endloop\n endfacet\n\
177 facet normal 0 0 1\n outer loop\n vertex 0 0 0\n vertex 1 0 0\n vertex 1 1 2\n endloop\n endfacet\n\
178 endsolid s\n";
179 let m = extract(stl.as_bytes(), CadFormat::Stl).unwrap();
180 assert_eq!(m.triangles, Some(2));
181 assert_eq!(m.vertices, Some(6));
182 assert_eq!(m.bbox.unwrap().max, [1.0, 1.0, 2.0]);
183 }
184
185 #[test]
186 fn obj_counts_and_triangulates() {
187 let obj = "v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf 1 2 3 4\nf 1 2 3\n";
188 let m = extract(obj.as_bytes(), CadFormat::Obj).unwrap();
189 assert_eq!(m.vertices, Some(4));
190 assert_eq!(m.triangles, Some(3));
192 assert_eq!(m.bbox.unwrap().min, [0.0, 0.0, 0.0]);
193 }
194
195 #[test]
196 fn binary_stl_one_triangle() {
197 let mut data = vec![0u8; 84 + 50];
198 data[80..84].copy_from_slice(&1u32.to_le_bytes());
199 let verts: [f32; 9] = [0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0, 0.0];
201 let mut o = 84 + 12;
202 for f in verts {
203 data[o..o + 4].copy_from_slice(&f.to_le_bytes());
204 o += 4;
205 }
206 let m = extract(&data, CadFormat::Stl).unwrap();
207 assert_eq!(m.triangles, Some(1));
208 assert_eq!(m.bbox.unwrap().max, [2.0, 3.0, 0.0]);
209 }
210}