[go: up one dir, main page]

tinystl/
lib.rs

1// All based on the code from microstl.h - STL file format parser
2// https://github.com/cry-inc/microstl/blob/master/include/microstl.h
3
4//! # `TinySTL` - A small loader for STL files.
5//! This project is heavily inspired by, and adapted from, [cry-inc's microstl library](https://github.com/cry-inc/microstl).
6//! The goal is to provide a zero-dependency way to easily load and write STL files.
7//! It is assumed that all binary files are little endian.
8//!
9//! # Example
10//! ```rust,ignore
11//! use tinystl::StlData;
12//! let data = StlData::read_from_file("my_file.stl")?;
13//! data.write_binary_file("my_binary_file.stl")?;
14//! ```
15//! # Features
16//! ### Bytemuck
17//! Derives ``Pod`` for ``Triangle``.
18//! ### Serde
19//! Derives ``Serialize`` and ``Deserialize`` for all types.
20
21use std::io::{BufRead, BufReader, Read, Write};
22
23#[cfg(feature = "bytemuck")]
24use bytemuck::{Pod, Zeroable};
25
26#[cfg(feature = "serde")]
27use serde::{Deserialize, Deserializer, Serialize, Serializer};
28
29const F32_SIZE: usize = std::mem::size_of::<f32>();
30
31/// In binary STL files, a triangle will always consist of 50 bytes.
32/// A triangle consists of:
33///
34/// ```text
35/// Normal: [f32; 3] - 12 bytes
36/// Vertex 1: [f32; 3] - 12 bytes
37/// Vertex 2: [f32; 3] - 12 bytes
38/// Vertex 3: [f32; 3] - 12 bytes
39/// Attirbute byte count: [u8; 2] - 2 bytes (generally [0, 0])
40/// ```
41/// For more information see the [Wikipedia page][https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL] on the format.
42const TRIANGLE_BINARY_SIZE: usize = 50;
43
44/// The binary STL format contains an 80 byte header.
45/// It should *never* start with `b"solid"`.
46/// For more information see the [Wikipedia page][https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL] on the format.
47const HEADER_BINARY_SIZE: usize = 80;
48
49/// Each facet contains a copy of all three vertex coordinates and a normal
50#[repr(C)]
51#[derive(Default, Debug, Copy, Clone, PartialEq)]
52#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
53pub struct Triangle {
54    pub v1: [f32; 3],
55    pub v2: [f32; 3],
56    pub v3: [f32; 3],
57}
58
59#[cfg(feature = "bytemuck")]
60unsafe impl Pod for Triangle {}
61#[cfg(feature = "bytemuck")]
62unsafe impl Zeroable for Triangle {}
63
64pub type Result<T> = std::result::Result<T, Error>;
65
66impl From<&[u8]> for Triangle {
67    fn from(buffer: &[u8]) -> Self {
68        const N_FLOAT_VALUES: usize = 9; // [[f32; 3]; 3];
69        let mut values = [0.0; N_FLOAT_VALUES];
70        for (value, bytes) in values
71            .iter_mut()
72            .zip(buffer[0..(N_FLOAT_VALUES * F32_SIZE)].chunks_exact(F32_SIZE))
73        {
74            let mut buf = [0; F32_SIZE];
75            buf.copy_from_slice(bytes);
76            *value = f32::from_le_bytes(buf);
77        }
78
79        let mut facet = Triangle::default();
80        facet.v1.copy_from_slice(&values[0..3]);
81        facet.v2.copy_from_slice(&values[3..6]);
82        facet.v3.copy_from_slice(&values[6..9]);
83        facet
84    }
85}
86
87impl Triangle {
88    /// Set the facet normal based on the vertices.
89    #[must_use]
90    fn calculate_normals(&self) -> [f32; 3] {
91        let u = [
92            self.v2[0] - self.v1[0],
93            self.v2[1] - self.v1[1],
94            self.v2[2] - self.v1[2],
95        ];
96        let v = [
97            self.v3[0] - self.v1[0],
98            self.v3[1] - self.v1[1],
99            self.v3[2] - self.v1[2],
100        ];
101
102        let mut normal = [
103            u[1] * v[2] - u[2] * v[1],
104            u[2] * v[0] - u[0] * v[2],
105            u[0] * v[1] - u[1] * v[0],
106        ];
107
108        let len = (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
109        normal[0] /= len;
110        normal[1] /= len;
111        normal[2] /= len;
112        normal
113    }
114
115    /// Fix normals on the facet beneath a certain epsilon;
116    #[must_use]
117    fn check_and_fix_normals(&self, normal: [f32; 3]) -> [f32; 3] {
118        const NORMAL_LENGTH_DEVIATION_LIMIT: f32 = 0.001;
119
120        let normal = if normal.iter().all(|i| *i == 0.0) {
121            self.calculate_normals()
122        } else {
123            normal
124        };
125
126        let len = (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
127        if (len - 1.0).abs() > NORMAL_LENGTH_DEVIATION_LIMIT {
128            return self.calculate_normals();
129        }
130        normal
131    }
132}
133
134/// Possible errors that come from loading a file
135#[derive(Debug)]
136pub enum Error {
137    /// Generally used when a binary buffer fails to parse
138    /// but could also mean that an ASCII STL is missing data.
139    MissingData,
140    /// Represents unexpected data when parsing an ASCII STL file.
141    Unexpected(usize),
142    /// Represents a failure to parse a line, usually due to a malformed vertex.
143    Parse(usize),
144    /// Represents a failure to convert the number of triangles to a u32 (as required by the STL specification).
145    TooManyFacets(<u32 as std::convert::TryFrom<usize>>::Error),
146    /// Represents a failure to convert an int.
147    TryFromInt(std::num::TryFromIntError),
148    /// Represents any std::io result error.
149    Io(std::io::Error),
150}
151
152impl From<std::io::Error> for Error {
153    fn from(e: std::io::Error) -> Self {
154        Self::Io(e)
155    }
156}
157
158impl From<std::num::TryFromIntError> for Error {
159    fn from(e: std::num::TryFromIntError) -> Self {
160        Self::TryFromInt(e)
161    }
162}
163
164impl std::fmt::Display for Error {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        match self {
167            Error::MissingData => write!(
168                f,
169                "STL data ended unexpectely and is incomplete or otherwise broken."
170            ),
171            Error::Unexpected(line) => {
172                write!(
173                    f,
174                    "Found an unexpected keyword or token in an ASCII STL file on line {line}."
175                )
176            }
177            Error::Parse(line) => write!(f, "Bad Vertex or Normal on line {line}."),
178            Error::TooManyFacets(e) => {
179                write!(f, "{e:?}")
180            }
181            Error::Io(e) => write!(f, "{e:?}"),
182            Error::TryFromInt(e) => write!(f, "{e:?}"),
183        }
184    }
185}
186
187/// Attempts to parse a line formated `f0 f1 f2` into an `[f32; 3]`.
188/// Returns an `Err` if the number of elements does not match three,
189/// or if it fails to parse any of the floats.
190fn parse_triplet(str: &str, line: usize) -> Result<[f32; 3]> {
191    let mut result = [0.0; 3];
192    let mut count = 0;
193    for (r, v) in result.iter_mut().zip(str.split_whitespace()) {
194        if let Ok(v) = v.parse() {
195            *r = v;
196        } else {
197            return Err(Error::Parse(line));
198        }
199        count += 1;
200    }
201    if count != 3 {
202        return Err(Error::Parse(line));
203    }
204    Ok(result)
205}
206
207#[derive(Debug, Copy, Clone, PartialEq, Eq)]
208#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
209/// The encoding type that populated a ``StlData``.
210pub enum Encoding {
211    Binary,
212    Ascii,
213}
214
215#[cfg(feature = "serde")]
216fn header_serialize<S: Serializer>(
217    header: &Option<[u8; 80]>,
218    s: S,
219) -> std::result::Result<S::Ok, S::Error> {
220    s.serialize_bytes(header.unwrap_or([0; 80]).as_slice())
221}
222
223#[cfg(feature = "serde")]
224fn header_deserialize<'de, D>(d: D) -> std::result::Result<Option<[u8; 80]>, D::Error>
225where
226    D: Deserializer<'de>,
227{
228    let mut res = [0; 80];
229    res.copy_from_slice(<&[u8]>::deserialize(d)?);
230    Ok(Some(res))
231}
232
233/// The container for all STL data.
234#[derive(Default, Debug, Clone, PartialEq)]
235#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
236pub struct StlData {
237    // data
238    pub triangles: Vec<Triangle>,
239    pub normals: Vec<[f32; 3]>,
240    pub name: String,
241    #[cfg_attr(feature = "serde", serde(serialize_with = "header_serialize"))]
242    #[cfg_attr(feature = "serde", serde(deserialize_with = "header_deserialize"))]
243    pub header: Option<[u8; HEADER_BINARY_SIZE]>,
244    pub encoding: Option<Encoding>,
245    // input settings
246    /// Set to true to force recalculatian normals using vertex data.
247    /// By default, recalculation is only done for zero normal vectors
248    /// or normal vectors with invalid length.
249    pub force_normals: bool,
250    /// Set to true to disable all reculation of normal vectors.
251    /// By default, recalculation is only done for zero normal vectors
252    /// or normal vectors with invalid length.
253    pub disable_normals: bool,
254    // output settings
255    /// Set to true to write zero normals in the output.
256    pub nullify_normals: bool,
257}
258
259impl StlData {
260    /// Creates and populates a ``StlData`` from a file path.
261    pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
262        // Optimization trick for reducing generic bloat
263        // See https://www.possiblerust.com/pattern/non-generic-inner-functions
264        fn read_file_path(path: &std::path::Path) -> Result<StlData> {
265            let mut res = StlData::default();
266            res.set_from_file(path)?;
267            Ok(res)
268        }
269        read_file_path(path.as_ref())
270    }
271
272    /// Creates and populates a ``StlData`` from a buffer.
273    pub fn read_buffer(reader: impl BufRead) -> Result<Self> {
274        let mut res = Self::default();
275        res.set_from_buffer(reader)?;
276        Ok(res)
277    }
278
279    /// Sets contents of a ``StlData`` from a file path.
280    /// If the method returns an `Err` then the state of the reader
281    /// will be empty.
282    pub fn set_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
283        self.set_from_file_path(path.as_ref())
284    }
285
286    // Optimization trick for reducing generic bloat
287    // See https://www.possiblerust.com/pattern/non-generic-inner-functions
288    fn set_from_file_path(&mut self, path: &std::path::Path) -> Result<()> {
289        let file = std::fs::File::open(path)?;
290        let reader = BufReader::new(file);
291        self.set_from_buffer(reader)?;
292        Ok(())
293    }
294
295    /// Sets the contents of a ``StlData`` from a buffer.
296    /// If the method returns an `Err` then the state of the reader
297    /// will be empty.
298    pub fn set_from_buffer(&mut self, mut reader: impl BufRead) -> Result<()> {
299        self.clear();
300        let buffer = reader.fill_buf()?;
301        if buffer.len() < 5 {
302            return Err(Error::MissingData);
303        }
304        if buffer[0..5] == *b"solid" {
305            let set = self.read_ascii_buffer(reader);
306            if set.is_err() {
307                self.clear();
308                return set;
309            }
310            self.encoding = Some(Encoding::Ascii);
311        } else {
312            let set = self.read_binary_buffer(reader);
313            if set.is_err() {
314                self.clear();
315                return set;
316            }
317            self.encoding = Some(Encoding::Binary);
318        }
319        Ok(())
320    }
321
322    /// Reset all data within the reader.
323    pub fn clear(&mut self) {
324        self.triangles.clear();
325        self.name.clear();
326        self.header = None;
327        self.encoding = None;
328    }
329
330    /// For internal use.
331    /// Sets the contents ``StlData`` from an ASCII buffer.
332    fn read_ascii_buffer(&mut self, reader: impl BufRead) -> Result<()> {
333        // State machine variables
334        let mut active_solid = false;
335        let mut active_facet = false;
336        let mut active_loop = false;
337        let mut solid_count = 0;
338        let mut loop_count = 0;
339        let mut vertex_count = 0;
340
341        let mut n = [0.0; 3];
342        let mut v = [0.0; 9];
343
344        // Line reader with loop to work the state machine
345        for (line_number, line) in reader.lines().enumerate() {
346            let line_number = line_number + 1; // offset from 0 start
347            let line = line?;
348            if line.trim().starts_with("solid") {
349                if active_solid || solid_count != 0 {
350                    return Err(Error::Unexpected(line_number));
351                }
352                active_solid = true;
353                if line.trim().len() > 5 {
354                    self.name = (line["solid".len()..].trim()).to_string();
355                }
356            }
357            if line.trim().starts_with("endsolid") {
358                if !active_solid || active_facet || active_loop {
359                    return Err(Error::Unexpected(line_number));
360                }
361                active_solid = false;
362                solid_count += 1;
363            }
364            if line.trim().starts_with("facet normal") {
365                if !active_solid || active_loop || active_facet {
366                    return Err(Error::Unexpected(line_number));
367                }
368                active_facet = true;
369                n = parse_triplet(line.trim()["facet normal".len()..].trim(), line_number)?;
370            }
371            if line.trim().starts_with("endfacet") {
372                if !active_solid || active_loop || !active_facet || loop_count != 1 {
373                    return Err(Error::Unexpected(line_number));
374                }
375                active_facet = false;
376                loop_count = 0;
377                let mut facet = Triangle::default();
378                facet.v1.copy_from_slice(&v[0..3]);
379                facet.v2.copy_from_slice(&v[3..6]);
380                facet.v3.copy_from_slice(&v[6..9]);
381
382                let normal = if self.force_normals && !self.disable_normals {
383                    facet.calculate_normals()
384                } else if !self.disable_normals {
385                    facet.check_and_fix_normals(n)
386                } else {
387                    n
388                };
389
390                self.normals.push(normal);
391                self.triangles.push(facet);
392            }
393            if line.trim().starts_with("outer loop") {
394                if !active_solid || !active_facet || active_loop {
395                    return Err(Error::Unexpected(line_number));
396                }
397                active_loop = true;
398            }
399            if line.trim().starts_with("endloop") {
400                if !active_solid || !active_facet || !active_loop || vertex_count != 3 {
401                    return Err(Error::Unexpected(line_number));
402                }
403                active_loop = false;
404                loop_count += 1;
405                vertex_count = 0;
406            }
407            if line.trim().starts_with("vertex") {
408                if !active_solid || !active_facet || !active_loop || vertex_count >= 3 {
409                    return Err(Error::Unexpected(line_number));
410                }
411                let triplet = parse_triplet(line.trim()["vertex".len()..].trim(), line_number)?;
412                v[vertex_count * 3] = triplet[0];
413                v[vertex_count * 3 + 1] = triplet[1];
414                v[vertex_count * 3 + 2] = triplet[2];
415
416                vertex_count += 1;
417            }
418        }
419
420        if active_solid || active_facet || active_loop || solid_count == 0 {
421            return Err(Error::MissingData);
422        }
423
424        Ok(())
425    }
426
427    /// For internal use.
428    /// Sets the contents ``StlData`` from a binary buffer.
429    fn read_binary_buffer(&mut self, mut reader: impl BufRead) -> Result<()> {
430        let mut buffer = vec![0; HEADER_BINARY_SIZE];
431
432        let mut header_reader = (&mut reader).take(u64::try_from(HEADER_BINARY_SIZE)?);
433        let header_bytes_read = header_reader.read_to_end(&mut buffer)?;
434        if header_bytes_read != HEADER_BINARY_SIZE {
435            return Err(Error::MissingData);
436        }
437
438        let mut header_buffer = [0; HEADER_BINARY_SIZE];
439        header_buffer.copy_from_slice(&buffer[0..HEADER_BINARY_SIZE]);
440        self.header = Some(header_buffer);
441        buffer.clear();
442
443        let mut facet_count_reader = (&mut reader).take(4);
444        let facet_count_bytes_read = facet_count_reader.read_to_end(&mut buffer)?;
445        if facet_count_bytes_read != 4 {
446            return Err(Error::MissingData);
447        }
448        let mut facet_count_buf = [0; 4];
449        facet_count_buf.copy_from_slice(&buffer[0..4]);
450
451        let facet_count = u32::from_le_bytes(facet_count_buf);
452        if facet_count == 0 {
453            return Err(Error::MissingData);
454        }
455        buffer.clear();
456
457        for _ in 0..facet_count {
458            let mut facet_reader = (&mut reader).take(u64::try_from(TRIANGLE_BINARY_SIZE)?);
459            let facet_buffer_bytes_read = facet_reader.read_to_end(&mut buffer)?;
460            if facet_buffer_bytes_read != TRIANGLE_BINARY_SIZE {
461                return Err(Error::MissingData);
462            }
463            let (normal_buffer, vertex_buffer) = buffer.split_at(F32_SIZE * 3);
464            let facet = Triangle::from(vertex_buffer);
465            let mut n = [0.0; 3];
466            for (n, chunk) in n.iter_mut().zip(normal_buffer.chunks_exact(F32_SIZE)) {
467                let mut bytes = [0; 4];
468                bytes.copy_from_slice(chunk);
469                *n = f32::from_le_bytes(bytes);
470            }
471            let normal = if self.force_normals && !self.disable_normals {
472                facet.calculate_normals()
473            } else if !self.disable_normals {
474                facet.check_and_fix_normals(n)
475            } else {
476                n
477            };
478            self.normals.push(normal);
479            self.triangles.push(facet);
480            buffer.clear();
481        }
482        Ok(())
483    }
484
485    /// Write the contents of a ``StlData`` to a buffer using the binary specification.
486    pub fn write_binary_buffer(&self, mut writer: impl Write) -> Result<()> {
487        writer.write_all(self.header.unwrap_or([0; HEADER_BINARY_SIZE]).as_slice())?;
488        let n_triangles = u32::try_from(self.triangles.len())?;
489        writer.write_all(n_triangles.to_le_bytes().as_slice())?;
490        let null_bytes = [0; 12];
491
492        for (&Triangle { v1, v2, v3 }, &normal) in self.triangles.iter().zip(self.normals.iter()) {
493            if self.nullify_normals {
494                writer.write_all(&null_bytes)?;
495            } else {
496                for n in normal {
497                    writer.write_all(n.to_le_bytes().as_slice())?;
498                }
499            }
500
501            for vertex in [v1, v2, v3] {
502                for v in vertex {
503                    writer.write_all(v.to_le_bytes().as_slice())?;
504                }
505            }
506
507            writer.write_all(&[0; 2])?;
508        }
509
510        Ok(())
511    }
512
513    /// Write the contents of a ``StlData`` to a buffer using the ASCII specification.
514    pub fn write_ascii_buffer(&self, mut writer: impl Write) -> Result<()> {
515        writeln!(writer, "solid {}", self.name)?;
516        for (&Triangle { v1, v2, v3 }, &normal) in self.triangles.iter().zip(self.normals.iter()) {
517            if self.nullify_normals {
518                writeln!(writer, "  facet normal 0 0 0")?;
519            } else {
520                let [n0, n1, n2] = normal;
521                writeln!(writer, "  facet normal {n0} {n1} {n2}")?;
522            };
523            writeln!(writer, "    outer loop")?;
524            for v in [v1, v2, v3] {
525                let [v0, v1, v2] = v;
526                writeln!(writer, "      vertex {v0} {v1} {v2}")?;
527            }
528            writeln!(writer, "    endloop")?;
529            writeln!(writer, "  endfacet")?;
530        }
531        writeln!(writer, "endsolid")?;
532        Ok(())
533    }
534
535    /// Writes the data from a ``StlData`` to a given path conforming to the ASCII STL specification.
536    ///
537    /// **If the file exists at the given path, it will be overwritten**.
538    pub fn write_ascii_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
539        self.write_ascii_file_path(path.as_ref())
540    }
541
542    // Optimization trick for reducing generic bloat
543    // See https://www.possiblerust.com/pattern/non-generic-inner-functions
544    fn write_ascii_file_path(&self, path: &std::path::Path) -> Result<()> {
545        let file = std::fs::OpenOptions::new()
546            .write(true)
547            .create(true)
548            .open(path)?;
549        let writer = std::io::BufWriter::new(file);
550        self.write_ascii_buffer(writer)?;
551        Ok(())
552    }
553
554    /// Writes the data from a ``StlData`` to a given path conforming to the binary STL specification.
555    ///
556    /// **If the file exists at the given path, it will be overwritten**.
557    pub fn write_binary_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
558        self.write_binary_file_path(path.as_ref())
559    }
560
561    // Optimization trick for reducing generic bloat
562    // See https://www.possiblerust.com/pattern/non-generic-inner-functions
563    fn write_binary_file_path(&self, path: &std::path::Path) -> Result<()> {
564        let file = std::fs::OpenOptions::new()
565            .write(true)
566            .create(true)
567            .open(path)?;
568        let writer = std::io::BufWriter::new(file);
569        self.write_binary_buffer(writer)?;
570        Ok(())
571    }
572}