Initial commit with STL parser

master
Alex Selimov 2 weeks ago
commit b97c6055a1

@ -0,0 +1,7 @@
[package]
name = "roctree"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "*"

@ -0,0 +1,2 @@
pub mod stl;
pub mod utilities;

@ -0,0 +1 @@
pub mod parser;

@ -0,0 +1,295 @@
use anyhow::Result;
use anyhow::{anyhow, Context};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::Path;
/// Representation of an STL mesh
/// Vertices are stored in vec with repeat vertices removed. Each vertex is represented as an
/// [f32; 3]. Triangles are represented as a [usize; 3] where each entry is an index in the
/// vertices Vec
pub struct StlSolid {
vertices: Vec<[f32; 3]>,
triangles: Vec<[usize; 3]>,
normals: Vec<[f32; 3]>,
}
pub fn parse_ascii_stl(file_path: &impl AsRef<Path>) -> Result<StlSolid> {
let file = File::open(file_path.as_ref())
.with_context(|| format!("Failed to open file \'{:?}\'!", file_path.as_ref()))?;
let mut lines = BufReader::new(file).lines();
// the first line starts with solid <name>
match lines.next() {
Some(Ok(line)) => {
if !line.starts_with("solid ") {
return Err(anyhow!("STL file does not start with \'solid\'!"));
}
}
Some(Err(err)) => {
return Err(anyhow!("Failed to read the first line!\nError: {}", err));
}
None => return Err(anyhow!("Failed to read from the file!")),
}
let mut lines = lines
.map_while(Result::ok)
.filter(|line| !line.trim().is_empty());
// facet normal -0.01905 -0.770147 0.637582
// outer loop
// vertex 3.29426 -0.2921 0.067036
// vertex 3.35026 -0.216682 0.159808
// vertex 3.11981 -0.142593 0.242416
// endloop
// endfacet
let mut unique_vertices: HashMap<String, usize> = HashMap::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut triangles: Vec<[usize; 3]> = Vec::new();
// Main reading loop
while let Some(line) = lines.next() {
let line = line.trim();
// Read the facet normal
if line.starts_with("facet normal ") {
let pts = line
.split_whitespace()
.skip(2)
.map(|num| num.parse())
.collect::<Result<Vec<f32>, _>>()
.map_err(|err| anyhow!("Failed to parse normal coordinates! Err: {err:?}"))?;
normals.push([
*pts.first()
.ok_or(anyhow!("facet normal should have 3 positions"))?,
*pts.get(1)
.ok_or(anyhow!("facet normal should have 3 positions"))?,
*pts.get(2)
.ok_or(anyhow!("facet normal should have 3 positions"))?,
]);
// outer loop
lines.next();
let mut indices = [0; 3];
for index in indices.iter_mut() {
if let Some(line) = lines.next() {
if !unique_vertices.contains_key(&line) {
unique_vertices.insert(line.clone(), unique_vertices.len());
}
*index = unique_vertices[&line];
}
}
triangles.push(indices);
// endloop
lines.next();
// endfacet
lines.next();
}
}
// Now convert the unique vertices to a vec
let mut vertices = vec![[0.0; 3]; unique_vertices.len()];
for (k, v) in unique_vertices.iter() {
let pts = k
.split_whitespace()
.skip(1)
.map(|num| num.parse())
.collect::<Result<Vec<f32>, _>>()
.map_err(|err| anyhow!("Failed to parse vertex coordinates! Err: {err:?}"))?;
vertices[*v] = [
*pts.first()
.ok_or(anyhow!("vertex should have 3 positions"))?,
*pts.get(1)
.ok_or(anyhow!("vertex should have 3 positions"))?,
*pts.get(2)
.ok_or(anyhow!("vertex should have 3 positions"))?,
];
}
Ok(StlSolid {
vertices,
triangles,
normals,
})
}
/// Parse a binary stl file
/// The specification for a binary stl file can be found at:
/// https://www.loc.gov/preservation/digital/formats/fdd/fdd000505.shtml
pub fn parse_binary_stl(file_path: &impl AsRef<Path>) -> Result<StlSolid> {
let mut file = File::open(file_path.as_ref())
.with_context(|| format!("Failed to open file \'{:?}\'!", file_path.as_ref()))?;
let mut header = [0u8; 80];
file.read_exact(&mut header)
.with_context(|| "Failed to read header from binary STL")?;
let mut num_triangles_bytes = [0u8; 4];
file.read_exact(&mut num_triangles_bytes)
.with_context(|| "Failed to read number of triangles from binary STL")?;
let num_triangles = u32::from_le_bytes(num_triangles_bytes);
let mut unique_vertices: HashMap<String, usize> = HashMap::new();
let mut normals: Vec<[f32; 3]> = Vec::new();
let mut vertices: Vec<[f32; 3]> = Vec::new();
let mut triangles: Vec<[usize; 3]> = Vec::new();
// Main reading loop
for _i in 0..num_triangles {
let mut buffer = [0u8; 50];
file.read_exact(&mut buffer)
.with_context(|| "Failed to read triangle from binary stl")?;
normals.push([
f32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]),
f32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]),
f32::from_le_bytes([buffer[8], buffer[9], buffer[10], buffer[11]]),
]);
let mut indices = [0; 3];
for (i, index) in indices.iter_mut().enumerate() {
let offset = 12 + i * 12;
let mut vertex = [0.0; 3];
vertex[0] = f32::from_le_bytes([
buffer[offset],
buffer[offset + 1],
buffer[offset + 2],
buffer[offset + 3],
]);
vertex[1] = f32::from_le_bytes([
buffer[offset + 4],
buffer[offset + 5],
buffer[offset + 6],
buffer[offset + 7],
]);
vertex[2] = f32::from_le_bytes([
buffer[offset + 8],
buffer[offset + 9],
buffer[offset + 10],
buffer[offset + 11],
]);
let key = format!("{vertex:?}").to_string();
if !unique_vertices.contains_key(&key) {
vertices.push(vertex);
unique_vertices.insert(key.clone(), unique_vertices.len());
}
//Safe to unwrap because we validate the key is present
*index = *(unique_vertices.get(&key).unwrap());
}
triangles.push(indices);
}
Ok(StlSolid {
vertices,
triangles,
normals,
})
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::assert_vec_tol;
struct VerticesNormalsTriangles(Vec<[f32; 3]>, Vec<[f32; 3]>, Vec<[usize; 3]>);
fn get_real_cube() -> VerticesNormalsTriangles {
let unique_vertices = vec![
[13.0, -22.0, 20.0],
[13.0, -22.0, 0.0],
[13.0, -2.0, 0.0],
[13.0, -2.0, 20.0],
[-7.0, -2.0, 20.0],
[-7.0, -2.0, 0.0],
[-7.0, -22.0, 0.0],
[-7.0, -22.0, 20.0],
];
let normals = vec![
[1.0, -0.0, 0.0],
[1.0, -0.0, 0.0],
[-1.0, -0.0, -0.0],
[-1.0, -0.0, 0.0],
[0.0, 0.0, 1.0],
[-0.0, 0.0, 1.0],
[0.0, 0.0, -1.0],
[0.0, 0.0, -1.0],
[0.0, 1.0, 0.0],
[0.0, 1.0, -0.0],
[0.0, -1.0, 0.0],
[0.0, -1.0, 0.0],
];
let triangles = vec![
[0, 1, 2],
[0, 2, 3],
[4, 5, 6],
[4, 6, 7],
[3, 4, 7],
[3, 7, 0],
[1, 6, 5],
[1, 5, 2],
[3, 2, 5],
[3, 5, 4],
[7, 6, 1],
[7, 1, 0],
];
VerticesNormalsTriangles(unique_vertices, normals, triangles)
}
#[test]
pub fn test_parse_ascii_stl() {
let stl_solid = parse_ascii_stl(&"test_files/ascii_cube.stl").unwrap();
let VerticesNormalsTriangles(unique_vertices, normals, triangles) = get_real_cube();
// First check vertices
if stl_solid.vertices.len() != unique_vertices.len() {
panic!("Parsed incorrect number of vertices")
}
for (read, real) in stl_solid.vertices.iter().zip(unique_vertices.iter()) {
assert_vec_tol!(read, real, 1e-7);
}
// Now check the normals
for (read, real) in stl_solid.normals.iter().zip(normals.iter()) {
assert_vec_tol!(read, real, 1e-7);
}
// Now check the triangle indices
for (read, real) in stl_solid.triangles.iter().zip(triangles.iter()) {
for (idx1, idx2) in read.iter().zip(real.iter()) {
assert_eq!(idx1, idx2);
}
}
}
#[test]
pub fn test_parse_binary_stl() {
let stl_solid = parse_binary_stl(&"test_files/binary_cube.stl").unwrap();
let VerticesNormalsTriangles(unique_vertices, normals, triangles) = get_real_cube();
// First check vertices
if stl_solid.vertices.len() != unique_vertices.len() {
panic!("Parsed incorrect number of vertices")
}
for (read, real) in stl_solid.vertices.iter().zip(unique_vertices.iter()) {
assert_vec_tol!(read, real, 1e-7);
}
// Now check the normals
for (read, real) in stl_solid.normals.iter().zip(normals.iter()) {
assert_vec_tol!(read, real, 1e-7);
}
// Now check the triangle indices
for (read, real) in stl_solid.triangles.iter().zip(triangles.iter()) {
for (idx1, idx2) in read.iter().zip(real.iter()) {
assert_eq!(idx1, idx2);
}
}
}
}

@ -0,0 +1,2 @@
#[cfg(test)]
pub mod test_macros;

@ -0,0 +1,21 @@
#[macro_export]
macro_rules! assert_vec_tol {
($a:expr, $b:expr, $tolerance:expr) => {{
let a = $a;
let b = $b;
let tolerance = $tolerance;
if a.len() != b.len() {
panic!("Vectors have different lengths: {} != {}", a.len(), b.len());
}
for (i, (&val_a, &val_b)) in a.iter().zip(b.iter()).enumerate() {
if (val_a - val_b).abs() >= tolerance {
panic!(
"Values at index {} differ by more than the tolerance: {} != {} (tolerance = {})",
i, val_a, val_b, tolerance
);
}
}
}};
}

@ -0,0 +1,86 @@
solid STL generated by MeshLab
facet normal 1.000000e+00 0.000000e+00 0.000000e+00
outer loop
vertex 1.300000e+01 -2.200000e+01 2.000000e+01
vertex 1.300000e+01 -2.200000e+01 0.000000e+00
vertex 1.300000e+01 -2.000000e+00 0.000000e+00
endloop
endfacet
facet normal 1.000000e+00 -0.000000e+00 0.000000e+00
outer loop
vertex 1.300000e+01 -2.200000e+01 2.000000e+01
vertex 1.300000e+01 -2.000000e+00 0.000000e+00
vertex 1.300000e+01 -2.000000e+00 2.000000e+01
endloop
endfacet
facet normal -1.000000e+00 0.000000e+00 -0.000000e+00
outer loop
vertex -7.000000e+00 -2.000000e+00 2.000000e+01
vertex -7.000000e+00 -2.000000e+00 0.000000e+00
vertex -7.000000e+00 -2.200000e+01 0.000000e+00
endloop
endfacet
facet normal -1.000000e+00 -0.000000e+00 0.000000e+00
outer loop
vertex -7.000000e+00 -2.000000e+00 2.000000e+01
vertex -7.000000e+00 -2.200000e+01 0.000000e+00
vertex -7.000000e+00 -2.200000e+01 2.000000e+01
endloop
endfacet
facet normal 0.000000e+00 0.000000e+00 1.000000e+00
outer loop
vertex 1.300000e+01 -2.000000e+00 2.000000e+01
vertex -7.000000e+00 -2.000000e+00 2.000000e+01
vertex -7.000000e+00 -2.200000e+01 2.000000e+01
endloop
endfacet
facet normal 0.000000e+00 0.000000e+00 1.000000e+00
outer loop
vertex 1.300000e+01 -2.000000e+00 2.000000e+01
vertex -7.000000e+00 -2.200000e+01 2.000000e+01
vertex 1.300000e+01 -2.200000e+01 2.000000e+01
endloop
endfacet
facet normal 0.000000e+00 0.000000e+00 -1.000000e+00
outer loop
vertex 1.300000e+01 -2.200000e+01 0.000000e+00
vertex -7.000000e+00 -2.200000e+01 0.000000e+00
vertex -7.000000e+00 -2.000000e+00 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 0.000000e+00 -1.000000e+00
outer loop
vertex 1.300000e+01 -2.200000e+01 0.000000e+00
vertex -7.000000e+00 -2.000000e+00 0.000000e+00
vertex 1.300000e+01 -2.000000e+00 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 1.000000e+00 0.000000e+00
outer loop
vertex 1.300000e+01 -2.000000e+00 2.000000e+01
vertex 1.300000e+01 -2.000000e+00 0.000000e+00
vertex -7.000000e+00 -2.000000e+00 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 1.000000e+00 0.000000e+00
outer loop
vertex 1.300000e+01 -2.000000e+00 2.000000e+01
vertex -7.000000e+00 -2.000000e+00 0.000000e+00
vertex -7.000000e+00 -2.000000e+00 2.000000e+01
endloop
endfacet
facet normal 0.000000e+00 -1.000000e+00 0.000000e+00
outer loop
vertex -7.000000e+00 -2.200000e+01 2.000000e+01
vertex -7.000000e+00 -2.200000e+01 0.000000e+00
vertex 1.300000e+01 -2.200000e+01 0.000000e+00
endloop
endfacet
facet normal 0.000000e+00 -1.000000e+00 0.000000e+00
outer loop
vertex -7.000000e+00 -2.200000e+01 2.000000e+01
vertex 1.300000e+01 -2.200000e+01 0.000000e+00
vertex 1.300000e+01 -2.200000e+01 2.000000e+01
endloop
endfacet
endsolid vcg

Binary file not shown.
Loading…
Cancel
Save