1use std::fs;
9use std::io::{self, Cursor, Read};
10use std::path::Path;
11
12use draco_core::draco_types::DataType;
13use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
14use draco_core::geometry_indices::{FaceIndex, PointIndex};
15use draco_core::mesh::Mesh;
16
17use crate::traits::{ReadFromBytes, Reader};
18
19const BINARY_HEADER_LENGTH: usize = 84;
21const BINARY_TRIANGLE_LENGTH: usize = 50;
23
24#[derive(Debug)]
26pub struct StlReader {
27 source: StlReaderSource,
28}
29
30#[derive(Debug, Clone)]
31enum StlReaderSource {
32 Path(std::path::PathBuf),
33 Bytes(Vec<u8>),
34}
35
36#[derive(Debug, Clone, Copy, PartialEq)]
38struct StlTriangle {
39 normal: [f32; 3],
40 vertices: [[f32; 3]; 3],
41}
42
43impl StlReader {
44 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
46 let path = path.as_ref().to_path_buf();
47 if !path.exists() {
48 return Err(io::Error::new(
49 io::ErrorKind::NotFound,
50 format!("File not found: {}", path.display()),
51 ));
52 }
53 Ok(Self {
54 source: StlReaderSource::Path(path),
55 })
56 }
57
58 pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
60 Self {
61 source: StlReaderSource::Bytes(bytes.into()),
62 }
63 }
64
65 pub fn read_from_bytes(bytes: &[u8]) -> io::Result<Mesh> {
67 let mut reader = Self::from_bytes(bytes.to_vec());
68 reader.read_mesh()
69 }
70
71 pub fn read_mesh(&mut self) -> io::Result<Mesh> {
73 let bytes = match &self.source {
74 StlReaderSource::Path(path) => fs::read(path)?,
75 StlReaderSource::Bytes(bytes) => bytes.clone(),
76 };
77 Ok(triangles_to_mesh(&read_stl_bytes(&bytes)?))
78 }
79}
80
81impl Reader for StlReader {
82 fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
83 StlReader::open(path)
84 }
85
86 fn read_meshes(&mut self) -> io::Result<Vec<Mesh>> {
87 Ok(vec![self.read_mesh()?])
88 }
89}
90
91impl ReadFromBytes for StlReader {
92 fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
93 Ok(Self::from_bytes(bytes.to_vec()))
94 }
95}
96
97fn invalid_stl(message: &str) -> io::Error {
98 io::Error::new(io::ErrorKind::InvalidData, message.to_string())
99}
100
101fn is_binary_stl(bytes: &[u8]) -> bool {
108 if bytes.len() < BINARY_HEADER_LENGTH {
109 return false;
110 }
111 let count = u32::from_le_bytes(bytes[80..84].try_into().unwrap()) as usize;
112 if let Some(expected) = count
113 .checked_mul(BINARY_TRIANGLE_LENGTH)
114 .and_then(|body| body.checked_add(BINARY_HEADER_LENGTH))
115 {
116 if bytes.len() == expected {
117 return true;
118 }
119 }
120 !bytes.starts_with(b"solid")
123}
124
125fn read_stl_bytes(bytes: &[u8]) -> io::Result<Vec<StlTriangle>> {
126 if is_binary_stl(bytes) {
127 read_binary_stl(bytes)
128 } else {
129 read_ascii_stl(
130 std::str::from_utf8(bytes)
131 .map_err(|_| invalid_stl("STL is neither a valid binary file nor UTF-8 text"))?,
132 )
133 }
134}
135
136fn read_binary_stl(bytes: &[u8]) -> io::Result<Vec<StlTriangle>> {
137 if bytes.len() < BINARY_HEADER_LENGTH {
138 return Err(invalid_stl("Binary STL is shorter than its header"));
139 }
140 let declared = u32::from_le_bytes(bytes[80..84].try_into().unwrap()) as usize;
141 let available = (bytes.len() - BINARY_HEADER_LENGTH) / BINARY_TRIANGLE_LENGTH;
144 if declared > available {
145 return Err(invalid_stl(
146 "Binary STL declares more triangles than it contains",
147 ));
148 }
149
150 let mut cursor = Cursor::new(&bytes[BINARY_HEADER_LENGTH..]);
151 let mut triangles = Vec::with_capacity(declared);
152 let mut record = [0u8; BINARY_TRIANGLE_LENGTH];
153 for _ in 0..declared {
154 cursor.read_exact(&mut record)?;
155 let value =
156 |index: usize| f32::from_le_bytes(record[index * 4..index * 4 + 4].try_into().unwrap());
157 triangles.push(StlTriangle {
158 normal: [value(0), value(1), value(2)],
159 vertices: [
160 [value(3), value(4), value(5)],
161 [value(6), value(7), value(8)],
162 [value(9), value(10), value(11)],
163 ],
164 });
165 }
166 Ok(triangles)
167}
168
169fn read_ascii_stl(text: &str) -> io::Result<Vec<StlTriangle>> {
170 let mut triangles = Vec::new();
171 let mut normal = [0.0f32; 3];
172 let mut corners: Vec<[f32; 3]> = Vec::with_capacity(3);
173
174 for line in text.lines() {
175 let mut tokens = line.split_whitespace();
176 let keyword = match tokens.next() {
177 Some(keyword) => keyword,
178 None => continue,
179 };
180 match keyword {
181 "facet" => {
184 corners.clear();
185 normal = read_ascii_triple(tokens.skip(1)).unwrap_or([0.0; 3]);
186 }
187 "vertex" => {
188 let vertex = read_ascii_triple(tokens)
189 .ok_or_else(|| invalid_stl("ASCII STL vertex needs three coordinates"))?;
190 corners.push(vertex);
191 }
192 "endfacet" => {
193 if corners.len() < 3 {
196 return Err(invalid_stl("ASCII STL facet has fewer than three vertices"));
197 }
198 for corner in 1..corners.len() - 1 {
199 triangles.push(StlTriangle {
200 normal,
201 vertices: [corners[0], corners[corner], corners[corner + 1]],
202 });
203 }
204 corners.clear();
205 }
206 _ => {}
207 }
208 }
209
210 Ok(triangles)
211}
212
213fn read_ascii_triple<'a>(tokens: impl Iterator<Item = &'a str>) -> Option<[f32; 3]> {
214 let values: Vec<f32> = tokens
215 .take(3)
216 .filter_map(|token| token.parse().ok())
217 .collect();
218 match values.as_slice() {
219 [x, y, z] => Some([*x, *y, *z]),
220 _ => None,
221 }
222}
223
224fn triangles_to_mesh(triangles: &[StlTriangle]) -> Mesh {
225 let mut mesh = Mesh::new();
226 if triangles.is_empty() {
227 return mesh;
228 }
229
230 let point_count = triangles.len() * 3;
231 mesh.set_num_points(point_count);
232 mesh.set_num_faces(triangles.len());
233
234 let mut positions = Vec::with_capacity(point_count);
235 let mut normals = Vec::with_capacity(point_count);
236 for triangle in triangles {
237 for vertex in triangle.vertices {
238 positions.push(vertex);
239 normals.push(triangle.normal);
243 }
244 }
245
246 mesh.add_attribute(make_f32x3_attribute(
247 GeometryAttributeType::Position,
248 &positions,
249 ));
250 if normals.iter().any(|normal| normal != &[0.0, 0.0, 0.0]) {
251 mesh.add_attribute(make_f32x3_attribute(
252 GeometryAttributeType::Normal,
253 &normals,
254 ));
255 }
256
257 for (index, _) in triangles.iter().enumerate() {
258 let base = (index * 3) as u32;
259 mesh.set_face(
260 FaceIndex(index as u32),
261 [PointIndex(base), PointIndex(base + 1), PointIndex(base + 2)],
262 );
263 }
264
265 mesh
266}
267
268fn make_f32x3_attribute(
269 attribute_type: GeometryAttributeType,
270 values: &[[f32; 3]],
271) -> PointAttribute {
272 let mut attribute = PointAttribute::new();
273 attribute.init(attribute_type, 3, DataType::Float32, false, values.len());
274 let buffer = attribute.buffer_mut();
275 for (index, value) in values.iter().enumerate() {
276 let bytes: Vec<u8> = value
277 .iter()
278 .flat_map(|component| component.to_le_bytes())
279 .collect();
280 buffer.write(index * 12, &bytes);
281 }
282 attribute
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 fn binary_stl(header: &[u8], triangles: &[StlTriangle]) -> Vec<u8> {
290 let mut bytes = vec![0u8; 80];
291 bytes[..header.len().min(80)].copy_from_slice(&header[..header.len().min(80)]);
292 bytes.extend_from_slice(&(triangles.len() as u32).to_le_bytes());
293 for triangle in triangles {
294 for component in triangle.normal {
295 bytes.extend_from_slice(&component.to_le_bytes());
296 }
297 for vertex in triangle.vertices {
298 for component in vertex {
299 bytes.extend_from_slice(&component.to_le_bytes());
300 }
301 }
302 bytes.extend_from_slice(&0u16.to_le_bytes());
303 }
304 bytes
305 }
306
307 fn one_triangle() -> StlTriangle {
308 StlTriangle {
309 normal: [0.0, 0.0, 1.0],
310 vertices: [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
311 }
312 }
313
314 #[test]
315 fn test_read_binary_stl() {
316 let data = binary_stl(b"binary", &[one_triangle()]);
317 let triangles = read_stl_bytes(&data).unwrap();
318 assert_eq!(triangles, vec![one_triangle()]);
319
320 let mesh = StlReader::read_from_bytes(&data).unwrap();
321 assert_eq!(mesh.num_points(), 3);
322 assert_eq!(mesh.num_faces(), 1);
323 assert_eq!(
324 mesh.face(FaceIndex(0)),
325 [PointIndex(0), PointIndex(1), PointIndex(2)]
326 );
327 assert!(mesh.named_attribute_id(GeometryAttributeType::Normal) >= 0);
328 }
329
330 #[test]
334 fn test_binary_stl_whose_header_says_solid() {
335 let data = binary_stl(
336 b"solid created by an exporter that means binary",
337 &[one_triangle()],
338 );
339 assert!(is_binary_stl(&data));
340 assert_eq!(read_stl_bytes(&data).unwrap(), vec![one_triangle()]);
341 }
342
343 #[test]
344 fn test_read_ascii_stl() {
345 let text = "solid demo\n\
346 facet normal 0 0 1\n\
347 outer loop\n\
348 vertex 0 0 0\n\
349 vertex 1 0 0\n\
350 vertex 0 1 0\n\
351 endloop\n\
352 endfacet\n\
353 endsolid demo\n";
354 let triangles = read_stl_bytes(text.as_bytes()).unwrap();
355 assert_eq!(triangles, vec![one_triangle()]);
356 }
357
358 #[test]
359 fn test_ascii_stl_rejects_a_truncated_facet() {
360 let text = "solid demo\nfacet normal 0 0 1\nouter loop\nvertex 0 0 0\nendloop\nendfacet\n";
361 let error = read_stl_bytes(text.as_bytes()).unwrap_err();
362 assert_eq!(error.kind(), io::ErrorKind::InvalidData);
363 }
364
365 #[test]
368 fn test_binary_stl_rejects_an_overstated_count() {
369 let mut data = binary_stl(b"binary", &[one_triangle()]);
370 data[80..84].copy_from_slice(&1_000_000u32.to_le_bytes());
371 let error = read_stl_bytes(&data).unwrap_err();
372 assert_eq!(error.kind(), io::ErrorKind::InvalidData);
373 }
374
375 #[test]
376 fn test_empty_stl_reads_as_an_empty_mesh() {
377 let mesh = StlReader::read_from_bytes(&binary_stl(b"binary", &[])).unwrap();
378 assert_eq!(mesh.num_points(), 0);
379 assert_eq!(mesh.num_faces(), 0);
380 }
381}