From c906c8d9928fca4ba5e281727c3ab2e1a53bfdb3 Mon Sep 17 00:00:00 2001 From: Gijs de Jong Date: Sat, 17 May 2025 21:20:58 +0100 Subject: [PATCH 1/5] Add optional mesh name --- src/ascii_reader.rs | 15 +++++++++++++-- src/lib.rs | 34 ++++++++++++++++++++++++++++++---- src/types.rs | 4 +++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/ascii_reader.rs b/src/ascii_reader.rs index e3a23fa..267494d 100644 --- a/src/ascii_reader.rs +++ b/src/ascii_reader.rs @@ -4,6 +4,7 @@ use std::io::{BufRead, BufReader, Result}; /// Struct for ascii STL reader. pub struct AsciiStlReader<'a> { + name: Option, lines: Box>> + 'a>, } @@ -40,6 +41,7 @@ impl<'a> AsciiStlReader<'a> { read: &'a mut dyn (::std::io::Read), ) -> Result> + 'a>> { let mut lines = BufReader::new(read).lines(); + let mut name: Option = None; match lines.next() { Some(Err(e)) => return Err(e), Some(Ok(ref line)) if !line.starts_with("solid ") => { @@ -54,7 +56,11 @@ impl<'a> AsciiStlReader<'a> { "empty file?", )) } - _ => {} + Some(Ok(ref line)) => { + if line.trim().len() > 5 { + name = Some((line["solid".len()..].trim()).to_string()); + } + } } let lines = lines .map(|result| { @@ -68,6 +74,7 @@ impl<'a> AsciiStlReader<'a> { // filter empty lines. .filter(|result| result.is_err() || (!result.as_ref().unwrap().is_empty())); Ok(Box::new(AsciiStlReader { + name, lines: Box::new(lines), }) as Box>>) @@ -154,4 +161,8 @@ impl<'a> AsciiStlReader<'a> { } } -impl<'a> TriangleIterator for AsciiStlReader<'a> {} \ No newline at end of file +impl<'a> TriangleIterator for AsciiStlReader<'a> { + fn name(&self) -> Option<&String> { + self.name.as_ref() + } +} diff --git a/src/lib.rs b/src/lib.rs index b6e0098..f057655 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ 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}; @@ -48,6 +48,26 @@ pub use writer::write_stl; /// Iterates over all Triangles in a STL. pub trait TriangleIterator: ::std::iter::Iterator> { + /// 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). /// /// ``` @@ -70,6 +90,7 @@ pub trait TriangleIterator: ::std::iter::Iterator> { // 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() { @@ -93,6 +114,7 @@ pub trait TriangleIterator: ::std::iter::Iterator> { vertices.shrink_to_fit(); triangles.shrink_to_fit(); Ok(IndexedMesh { + name, vertices, faces: triangles, }) @@ -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]), @@ -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]), @@ -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.]), @@ -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] @@ -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() @@ -453,4 +479,4 @@ mod test { assert!(total_area.approx_eq(blender_area, F32Margin::default())); } -} \ No newline at end of file +} diff --git a/src/types.rs b/src/types.rs index e993b4f..8e872ea 100644 --- a/src/types.rs +++ b/src/types.rs @@ -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, /// List of vertices. pub vertices: Vec, /// List of triangles.. @@ -122,4 +124,4 @@ impl IndexedMesh { Ok(()) } } -} \ No newline at end of file +} From 45e9a26abf7e40faada15efba97452bdd9dd20de Mon Sep 17 00:00:00 2001 From: Gijs de Jong Date: Sat, 17 May 2025 21:21:51 +0100 Subject: [PATCH 2/5] misc cleanup + clippy suggestions --- src/ascii_reader.rs | 4 ++-- src/binary_reader.rs | 6 +++--- src/lib.rs | 2 +- src/types.rs | 4 ++-- src/utils.rs | 4 ++-- src/writer.rs | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ascii_reader.rs b/src/ascii_reader.rs index 267494d..c254913 100644 --- a/src/ascii_reader.rs +++ b/src/ascii_reader.rs @@ -8,7 +8,7 @@ pub struct AsciiStlReader<'a> { lines: Box>> + 'a>, } -impl<'a> ::std::iter::Iterator for AsciiStlReader<'a> { +impl ::std::iter::Iterator for AsciiStlReader<'_> { type Item = Result; fn next(&mut self) -> Option { match self.next_face() { @@ -161,7 +161,7 @@ impl<'a> AsciiStlReader<'a> { } } -impl<'a> TriangleIterator for AsciiStlReader<'a> { +impl TriangleIterator for AsciiStlReader<'_> { fn name(&self) -> Option<&String> { self.name.as_ref() } diff --git a/src/binary_reader.rs b/src/binary_reader.rs index 250241b..1e6669f 100644 --- a/src/binary_reader.rs +++ b/src/binary_reader.rs @@ -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> { @@ -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; fn next(&mut self) -> Option { if self.index < self.size { @@ -60,4 +60,4 @@ impl<'a> ::std::iter::Iterator for BinaryStlReader<'a> { } } -impl<'a> TriangleIterator for BinaryStlReader<'a> {} \ No newline at end of file +impl TriangleIterator for BinaryStlReader<'_> {} diff --git a/src/lib.rs b/src/lib.rs index f057655..f2ad81e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -471,7 +471,7 @@ 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 diff --git a/src/types.rs b/src/types.rs index 8e872ea..999213b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -27,7 +27,7 @@ impl std::ops::Index for Vector { } } -impl<'a, M: Copy + Default, F: Copy + ApproxEq> ApproxEq for &'a Vector { +impl> ApproxEq for &Vector { type Margin = M; fn approx_eq>(self, other: Self, margin: T) -> bool { @@ -112,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!( diff --git a/src/utils.rs b/src/utils.rs index a0c6020..82b98dd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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 { @@ -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 -} \ No newline at end of file +} diff --git a/src/writer.rs b/src/writer.rs index 014ad9a..6b5c085 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -28,15 +28,15 @@ where for t in mesh { let t = t.borrow(); for f in &t.normal.0 { - writer.write_f32::(*f as f32)?; + writer.write_f32::(*f)?; } for &p in &t.vertices { for c in &p.0 { - writer.write_f32::(*c as f32)?; + writer.write_f32::(*c)?; } } // Attribute byte count writer.write_u16::(0)?; } writer.flush() -} \ No newline at end of file +} From 63403a77dc77ea606af3a458f3ff315ba110efc7 Mon Sep 17 00:00:00 2001 From: Gijs de Jong Date: Sat, 17 May 2025 21:22:42 +0100 Subject: [PATCH 3/5] increment version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7cf9867..b56b411 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stl_io" -version = "0.8.5" +version = "0.9.0" authors = ["Henning Meyer "] edition = "2021" From 52801f68fe7a92c951d5952f498404f6958306bd Mon Sep 17 00:00:00 2001 From: Gijs de Jong Date: Mon, 19 May 2025 18:23:26 +0200 Subject: [PATCH 4/5] Use `strip_prefix --- src/ascii_reader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_reader.rs b/src/ascii_reader.rs index c254913..b879113 100644 --- a/src/ascii_reader.rs +++ b/src/ascii_reader.rs @@ -58,7 +58,7 @@ impl<'a> AsciiStlReader<'a> { } Some(Ok(ref line)) => { if line.trim().len() > 5 { - name = Some((line["solid".len()..].trim()).to_string()); + name = line.strip_prefix("solid ").map(|n| n.to_string()); } } } From b4325a398f16ed7b3ce9c35f21d49d7269687e6e Mon Sep 17 00:00:00 2001 From: Gijs de Jong Date: Tue, 20 May 2025 09:49:22 +0200 Subject: [PATCH 5/5] Clean up factory method --- src/ascii_reader.rs | 57 +++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/ascii_reader.rs b/src/ascii_reader.rs index b879113..1002364 100644 --- a/src/ascii_reader.rs +++ b/src/ascii_reader.rs @@ -4,7 +4,7 @@ use std::io::{BufRead, BufReader, Result}; /// Struct for ascii STL reader. pub struct AsciiStlReader<'a> { - name: Option, + name: String, lines: Box>> + 'a>, } @@ -36,49 +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> + 'a>> { let mut lines = BufReader::new(read).lines(); - let mut name: Option = None; - 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?", - )) - } - Some(Ok(ref line)) => { - if line.trim().len() > 5 { - name = line.strip_prefix("solid ").map(|n| n.to_string()); - } - } - } + ) + })?; + 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::>() }) }) - // 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>>) + })) } + // Tries to read a triangle. fn next_face(&mut self) -> Result> { let face_header: Option>> = self.lines.next(); @@ -163,6 +158,6 @@ impl<'a> AsciiStlReader<'a> { impl TriangleIterator for AsciiStlReader<'_> { fn name(&self) -> Option<&String> { - self.name.as_ref() + Some(&self.name) } }