Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stl_io"
version = "0.8.5"
version = "0.9.0"
authors = ["Henning Meyer <tutmann@gmail.com>"]
edition = "2021"

Expand Down
58 changes: 32 additions & 26 deletions src/ascii_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use std::io::{BufRead, BufReader, Result};

/// Struct for ascii STL reader.
pub struct AsciiStlReader<'a> {
name: String,
lines: Box<dyn ::std::iter::Iterator<Item = Result<Vec<String>>> + 'a>,
}

impl<'a> ::std::iter::Iterator for AsciiStlReader<'a> {
impl ::std::iter::Iterator for AsciiStlReader<'_> {
type Item = Result<Triangle>;
fn next(&mut self) -> Option<Self::Item> {
match self.next_face() {
Expand Down Expand Up @@ -35,43 +36,44 @@ impl<'a> AsciiStlReader<'a> {
Ok(())
}
}

/// Factory to create a new ascii STL Reader from read.
pub fn create_triangle_iterator(
read: &'a mut dyn (::std::io::Read),
) -> Result<Box<dyn TriangleIterator<Item = Result<Triangle>> + 'a>> {
let mut lines = BufReader::new(read).lines();
match lines.next() {
Some(Err(e)) => return Err(e),
Some(Ok(ref line)) if !line.starts_with("solid ") => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,

let first = lines
.next()
.transpose()?
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "empty file?"))?;

let name = first
.strip_prefix("solid ")
.map(str::to_string)
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"ascii STL does not start with \"solid \"",
))
}
None => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::UnexpectedEof,
"empty file?",
))
}
_ => {}
}
)
})?;

let lines = lines
.map(|result| {
result.map(|l| {
// Make lines into iterator over vectors of tokens
l.split_whitespace()
.map(|t| t.to_string())
.map(|res| {
res.map(|line| {
line.split_whitespace()
.map(str::to_string)
.collect::<Vec<_>>()
})
})
// filter empty lines.
.filter(|result| result.is_err() || (!result.as_ref().unwrap().is_empty()));
.filter(|res| res.as_ref().map_or(true, |v| !v.is_empty()));

Ok(Box::new(AsciiStlReader {
name,
lines: Box::new(lines),
})
as Box<dyn TriangleIterator<Item = Result<Triangle>>>)
}))
}

// Tries to read a triangle.
fn next_face(&mut self) -> Result<Option<Triangle>> {
let face_header: Option<Result<Vec<String>>> = self.lines.next();
Expand Down Expand Up @@ -154,4 +156,8 @@ impl<'a> AsciiStlReader<'a> {
}
}

impl<'a> TriangleIterator for AsciiStlReader<'a> {}
impl TriangleIterator for AsciiStlReader<'_> {
fn name(&self) -> Option<&String> {
Some(&self.name)
}
}
6 changes: 3 additions & 3 deletions src/binary_reader.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::types::{Normal, Triangle, Vertex};
use crate::TriangleIterator;
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{BufReader, Result, Read};
use std::io::{BufReader, Read, Result};

/// Struct for binary STL reader.
pub struct BinaryStlReader<'a> {
Expand Down Expand Up @@ -46,7 +46,7 @@ impl<'a> BinaryStlReader<'a> {
}
}

impl<'a> ::std::iter::Iterator for BinaryStlReader<'a> {
impl ::std::iter::Iterator for BinaryStlReader<'_> {
type Item = Result<Triangle>;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.size {
Expand All @@ -60,4 +60,4 @@ impl<'a> ::std::iter::Iterator for BinaryStlReader<'a> {
}
}

impl<'a> TriangleIterator for BinaryStlReader<'a> {}
impl TriangleIterator for BinaryStlReader<'_> {}
36 changes: 31 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,34 @@ mod types;
mod utils;
mod writer;

use std::io::{Result};
use std::io::Result;
use std::iter::Iterator;

pub use types::{IndexedMesh, IndexedTriangle, Normal, Triangle, Vector, Vertex};
pub use writer::write_stl;

/// Iterates over all Triangles in a STL.
pub trait TriangleIterator: ::std::iter::Iterator<Item = Result<Triangle>> {
/// Get the optional name of the mesh in this iterator.
///
/// ```
/// let mut reader = ::std::io::Cursor::new(b"solid foobar
/// facet normal 1 2 3
/// outer loop
/// vertex 7 8 9
/// vertex 4 5 6
/// vertex 7 8 9
/// endloop
/// endfacet
/// endsolid foobar".to_vec());
/// let mut stl = stl_io::create_stl_reader(&mut reader).unwrap();
///
/// assert_eq!(stl.name(), Some(&"foobar".to_string()));
/// ```
fn name(&self) -> Option<&String> {
None
}

/// Consumes this iterator and generates an [indexed Mesh](struct.IndexedMesh.html).
///
/// ```
Expand All @@ -70,6 +90,7 @@ pub trait TriangleIterator: ::std::iter::Iterator<Item = Result<Triangle>> {
// Do not reserve memory in those structures based on size_hint, because we might have just
// read bogus data.
let mut vertex_indices = [0; 3];
let name = self.name().cloned();
for t in self {
let t = t?;
for (i, vertex) in t.vertices.iter().enumerate() {
Expand All @@ -93,6 +114,7 @@ pub trait TriangleIterator: ::std::iter::Iterator<Item = Result<Triangle>> {
vertices.shrink_to_fit();
triangles.shrink_to_fit();
Ok(IndexedMesh {
name,
vertices,
faces: triangles,
})
Expand Down Expand Up @@ -193,6 +215,7 @@ mod test {
.as_indexed_triangles()
.unwrap(),
super::IndexedMesh {
name: Some("foobar".to_string()),
vertices: vec![
Vertex::new([1., 2., 3.]),
Vertex::new([4., 5., 6e-15]),
Expand Down Expand Up @@ -226,6 +249,7 @@ mod test {
.as_indexed_triangles()
.unwrap(),
super::IndexedMesh {
name: Some("foo bar".to_string()),
vertices: vec![
Vertex::new([1., 2., 3.]),
Vertex::new([4., 5., 6e-15]),
Expand Down Expand Up @@ -260,6 +284,7 @@ mod test {
assert_eq!(
sort_vertices(stl),
super::IndexedMesh {
name: Some("foobar".to_string()),
vertices: vec![Vertex::new([4., 5., 6.]), Vertex::new([7., 8., 9.])],
faces: vec![IndexedTriangle {
normal: Normal::new([27., 28., 29.]),
Expand Down Expand Up @@ -383,7 +408,8 @@ mod test {
.unwrap();
let ascii_mesh = sort_vertices(ascii_mesh);
let binary_mesh = sort_vertices(binary_mesh);
assert_eq!(ascii_mesh, binary_mesh);
assert_eq!(ascii_mesh.faces, binary_mesh.faces);
assert_eq!(ascii_mesh.vertices, binary_mesh.vertices);
}

#[test]
Expand Down Expand Up @@ -433,7 +459,7 @@ mod test {
#[test]
fn bunny_tri_area() {
use float_cmp::ApproxEq;

let mut reader = ::std::io::Cursor::new(BUNNY_99);
let stl = binary_reader::BinaryStlReader::create_triangle_iterator(&mut reader)
.unwrap()
Expand All @@ -445,12 +471,12 @@ mod test {
let a = stl.vertices[face.vertices[0]];
let b = stl.vertices[face.vertices[1]];
let c = stl.vertices[face.vertices[2]];
total_area = total_area + utils::tri_area(a, b, c);
total_area += utils::tri_area(a, b, c);
}

// area of bunny model according to blender
let blender_area: f32 = 0.04998364;

assert!(total_area.approx_eq(blender_area, F32Margin::default()));
}
}
}
8 changes: 5 additions & 3 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl<F> std::ops::Index<usize> for Vector<F> {
}
}

impl<'a, M: Copy + Default, F: Copy + ApproxEq<Margin = M>> ApproxEq for &'a Vector<F> {
impl<M: Copy + Default, F: Copy + ApproxEq<Margin = M>> ApproxEq for &Vector<F> {
type Margin = M;

fn approx_eq<T: Into<Self::Margin>>(self, other: Self, margin: T) -> bool {
Expand Down Expand Up @@ -70,6 +70,8 @@ pub struct IndexedTriangle {
/// [indexed Triangles](struct.IndexedTriangle.html).
#[derive(Clone, Debug, PartialEq)]
pub struct IndexedMesh {
/// Optional name of the mesh (ASCII only).
pub name: Option<String>,
/// List of vertices.
pub vertices: Vec<Vertex>,
/// List of triangles..
Expand Down Expand Up @@ -110,7 +112,7 @@ impl IndexedMesh {
}
}

if let Option::Some((fi, i1, i2)) = unconnected_edges.values().into_iter().next() {
if let Option::Some((fi, i1, i2)) = unconnected_edges.values().next() {
Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
format!(
Expand All @@ -122,4 +124,4 @@ impl IndexedMesh {
Ok(())
}
}
}
}
4 changes: 2 additions & 2 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::types::{Vertex};
use crate::types::Vertex;

/// Calculate the area of a triangle
pub fn tri_area(a: Vertex, b: Vertex, c: Vertex) -> f32 {
Expand All @@ -19,4 +19,4 @@ pub fn tri_area(a: Vertex, b: Vertex, c: Vertex) -> f32 {
}

length(cross(sub(c, b), sub(a, b))) * 0.5
}
}
6 changes: 3 additions & 3 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ where
for t in mesh {
let t = t.borrow();
for f in &t.normal.0 {
writer.write_f32::<LittleEndian>(*f as f32)?;
writer.write_f32::<LittleEndian>(*f)?;
}
for &p in &t.vertices {
for c in &p.0 {
writer.write_f32::<LittleEndian>(*c as f32)?;
writer.write_f32::<LittleEndian>(*c)?;
}
}
// Attribute byte count
writer.write_u16::<LittleEndian>(0)?;
}
writer.flush()
}
}