use std::io::{Read, Seek, SeekFrom};
use byteorder::{LittleEndian, ReadBytesExt};
use crate::error::{DxfError, Result};
use super::{StlMesh, StlTriangle};
pub fn parse_binary_stl<R: Read + Seek>(reader: &mut R) -> Result<StlMesh> {
let mut header = [0u8; 80];
reader
.read_exact(&mut header)
.map_err(|e| DxfError::ImportError(format!("Failed to read STL header: {}", e)))?;
let name = extract_header_name(&header);
let num_triangles = reader
.read_u32::<LittleEndian>()
.map_err(|e| DxfError::ImportError(format!("Failed to read triangle count: {}", e)))?
as usize;
if let Ok(file_len) = reader.seek(SeekFrom::End(0)) {
let expected = 84 + 50 * num_triangles as u64;
if file_len < expected {
return Err(DxfError::ImportError(format!(
"Binary STL file too small: expected at least {} bytes for {} triangles, got {}",
expected, num_triangles, file_len
)));
}
reader.seek(SeekFrom::Start(84)).map_err(|e| {
DxfError::ImportError(format!("Failed to seek past header: {}", e))
})?;
}
let mut triangles = Vec::with_capacity(num_triangles);
for i in 0..num_triangles {
let tri = read_triangle(reader).map_err(|e| {
DxfError::ImportError(format!("Failed to read triangle {}: {}", i, e))
})?;
triangles.push(tri);
}
Ok(StlMesh { name, triangles })
}
fn read_triangle<R: Read>(reader: &mut R) -> Result<StlTriangle> {
let normal = [
reader.read_f32::<LittleEndian>().map_err(io_err)? as f64,
reader.read_f32::<LittleEndian>().map_err(io_err)? as f64,
reader.read_f32::<LittleEndian>().map_err(io_err)? as f64,
];
let mut vertices = [[0.0f64; 3]; 3];
for v in &mut vertices {
v[0] = reader.read_f32::<LittleEndian>().map_err(io_err)? as f64;
v[1] = reader.read_f32::<LittleEndian>().map_err(io_err)? as f64;
v[2] = reader.read_f32::<LittleEndian>().map_err(io_err)? as f64;
}
let attribute = reader.read_u16::<LittleEndian>().map_err(io_err)?;
let color = if attribute & 0x8000 != 0 {
let b = ((attribute & 0x001F) as f32 / 31.0 * 255.0) as u8;
let g = (((attribute >> 5) & 0x001F) as f32 / 31.0 * 255.0) as u8;
let r = (((attribute >> 10) & 0x001F) as f32 / 31.0 * 255.0) as u8;
Some((r, g, b))
} else {
None
};
Ok(StlTriangle {
normal,
vertices,
color,
})
}
fn io_err(e: std::io::Error) -> DxfError {
DxfError::ImportError(format!("STL binary read error: {}", e))
}
fn extract_header_name(header: &[u8; 80]) -> String {
let end = header
.iter()
.position(|&b| b == 0 || b < 0x20)
.unwrap_or(80);
let s = String::from_utf8_lossy(&header[..end]);
let trimmed = s.trim();
if trimmed.is_empty() {
"stl_import".to_string()
} else {
trimmed.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn make_binary_stl(triangles: &[StlTriangle]) -> Vec<u8> {
let mut buf = Vec::new();
let header = b"test binary stl";
buf.extend_from_slice(header);
buf.extend_from_slice(&vec![0u8; 80 - header.len()]);
buf.extend_from_slice(&(triangles.len() as u32).to_le_bytes());
for tri in triangles {
for &n in &tri.normal {
buf.extend_from_slice(&(n as f32).to_le_bytes());
}
for v in &tri.vertices {
for &c in v {
buf.extend_from_slice(&(c as f32).to_le_bytes());
}
}
buf.extend_from_slice(&0u16.to_le_bytes());
}
buf
}
#[test]
fn test_parse_single_triangle() {
let tri = StlTriangle {
normal: [0.0, 0.0, 1.0],
vertices: [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
color: None,
};
let data = make_binary_stl(&[tri]);
let mut cursor = Cursor::new(data);
let mesh = parse_binary_stl(&mut cursor).unwrap();
assert_eq!(mesh.triangles.len(), 1);
assert_eq!(mesh.triangles[0].vertices[1], [1.0, 0.0, 0.0]);
}
#[test]
fn test_parse_empty() {
let data = make_binary_stl(&[]);
let mut cursor = Cursor::new(data);
let mesh = parse_binary_stl(&mut cursor).unwrap();
assert_eq!(mesh.triangles.len(), 0);
}
#[test]
fn test_color_attribute() {
let mut data = Vec::new();
data.extend_from_slice(&[0u8; 80]);
data.extend_from_slice(&1u32.to_le_bytes());
for _ in 0..3 {
data.extend_from_slice(&0.0f32.to_le_bytes());
}
for _ in 0..9 {
data.extend_from_slice(&1.0f32.to_le_bytes());
}
let attr: u16 = 0x8000 | (31 << 10);
data.extend_from_slice(&attr.to_le_bytes());
let mut cursor = Cursor::new(data);
let mesh = parse_binary_stl(&mut cursor).unwrap();
assert_eq!(mesh.triangles[0].color, Some((255, 0, 0)));
}
}