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…
Reference in new issue