#![allow(clippy::manual_strip)]
#[allow(unused_imports)]
use super::functions::*;
#[allow(unused_imports)]
use super::functions_2::*;
use std::collections::HashMap;
pub struct StlTriangle {
pub normal: [f32; 3],
pub v0: [f32; 3],
pub v1: [f32; 3],
pub v2: [f32; 3],
}
pub struct StlMesh {
pub triangles: Vec<StlTriangle>,
pub name: String,
}
impl StlMesh {
pub fn new(name: &str) -> Self {
StlMesh {
triangles: Vec::new(),
name: name.to_string(),
}
}
pub fn add_triangle(&mut self, v0: [f32; 3], v1: [f32; 3], v2: [f32; 3]) {
let normal = compute_normal(v0, v1, v2);
self.triangles.push(StlTriangle { normal, v0, v1, v2 });
}
pub fn to_binary_bytes(&self) -> Vec<u8> {
let n = self.triangles.len();
let mut buf = Vec::with_capacity(84 + n * 50);
let mut header = [0u8; 80];
let name_bytes = self.name.as_bytes();
let copy_len = name_bytes.len().min(80);
header[..copy_len].copy_from_slice(&name_bytes[..copy_len]);
buf.extend_from_slice(&header);
buf.extend_from_slice(&(n as u32).to_le_bytes());
for tri in &self.triangles {
for &f in &tri.normal {
buf.extend_from_slice(&f.to_le_bytes());
}
for &f in &tri.v0 {
buf.extend_from_slice(&f.to_le_bytes());
}
for &f in &tri.v1 {
buf.extend_from_slice(&f.to_le_bytes());
}
for &f in &tri.v2 {
buf.extend_from_slice(&f.to_le_bytes());
}
buf.extend_from_slice(&0u16.to_le_bytes());
}
buf
}
pub fn from_binary_bytes(data: &[u8]) -> Result<Self, String> {
if data.len() < 84 {
return Err(format!(
"Binary STL too short: {} bytes (need at least 84)",
data.len()
));
}
let header_bytes = &data[..80];
let name = String::from_utf8_lossy(header_bytes)
.trim_end_matches('\0')
.trim()
.to_string();
let n = u32::from_le_bytes([data[80], data[81], data[82], data[83]]) as usize;
let expected_len = 84 + n * 50;
if data.len() < expected_len {
return Err(format!(
"Binary STL too short for {} triangles: got {} bytes, need {}",
n,
data.len(),
expected_len
));
}
let mut triangles = Vec::with_capacity(n);
for i in 0..n {
let base = 84 + i * 50;
let normal = read_f32x3(data, base)?;
let v0 = read_f32x3(data, base + 12)?;
let v1 = read_f32x3(data, base + 24)?;
let v2 = read_f32x3(data, base + 36)?;
triangles.push(StlTriangle { normal, v0, v1, v2 });
}
Ok(StlMesh { triangles, name })
}
pub fn to_ascii(&self) -> String {
let mut s = String::new();
s.push_str(&format!("solid {}\n", self.name));
for tri in &self.triangles {
s.push_str(&format!(
" facet normal {:.6e} {:.6e} {:.6e}\n",
tri.normal[0], tri.normal[1], tri.normal[2]
));
s.push_str(" outer loop\n");
s.push_str(&format!(
" vertex {:.6e} {:.6e} {:.6e}\n",
tri.v0[0], tri.v0[1], tri.v0[2]
));
s.push_str(&format!(
" vertex {:.6e} {:.6e} {:.6e}\n",
tri.v1[0], tri.v1[1], tri.v1[2]
));
s.push_str(&format!(
" vertex {:.6e} {:.6e} {:.6e}\n",
tri.v2[0], tri.v2[1], tri.v2[2]
));
s.push_str(" endloop\n");
s.push_str(" endfacet\n");
}
s.push_str(&format!("endsolid {}\n", self.name));
s
}
pub fn from_ascii(s: &str) -> Result<Self, String> {
let mut lines = s.lines().peekable();
let first = lines
.next()
.ok_or_else(|| "Empty STL string".to_string())?
.trim();
let name = if first.starts_with("solid") {
first[5..].trim().to_string()
} else {
return Err(format!("Expected 'solid', got: {first}"));
};
let mut triangles = Vec::new();
while let Some(l) = lines.next() {
let line = l.trim().to_string();
if line.starts_with("endsolid") {
break;
}
if line.is_empty() {
continue;
}
if !line.starts_with("facet normal") {
return Err(format!("Expected 'facet normal', got: {line}"));
}
let normal = parse_vec3_from_line(&line, "facet normal")?;
let outer = lines
.next()
.ok_or_else(|| "Unexpected EOF after facet normal".to_string())?
.trim()
.to_string();
if !outer.starts_with("outer loop") {
return Err(format!("Expected 'outer loop', got: {outer}"));
}
let v0 = parse_vertex_line(
lines
.next()
.ok_or_else(|| "Unexpected EOF reading vertex 0".to_string())?
.trim(),
)?;
let v1 = parse_vertex_line(
lines
.next()
.ok_or_else(|| "Unexpected EOF reading vertex 1".to_string())?
.trim(),
)?;
let v2 = parse_vertex_line(
lines
.next()
.ok_or_else(|| "Unexpected EOF reading vertex 2".to_string())?
.trim(),
)?;
let _endloop = lines
.next()
.ok_or_else(|| "Unexpected EOF reading endloop".to_string())?;
let _endfacet = lines
.next()
.ok_or_else(|| "Unexpected EOF reading endfacet".to_string())?;
triangles.push(StlTriangle { normal, v0, v1, v2 });
}
Ok(StlMesh { triangles, name })
}
pub fn surface_area(&self) -> f32 {
self.triangles.iter().map(triangle_area).sum()
}
pub fn bounding_box(&self) -> ([f32; 3], [f32; 3]) {
if self.triangles.is_empty() {
return ([0.0; 3], [0.0; 3]);
}
let mut lo = [f32::MAX; 3];
let mut hi = [f32::MIN; 3];
for tri in &self.triangles {
for v in [&tri.v0, &tri.v1, &tri.v2] {
for k in 0..3 {
if v[k] < lo[k] {
lo[k] = v[k];
}
if v[k] > hi[k] {
hi[k] = v[k];
}
}
}
}
(lo, hi)
}
pub fn is_watertight(&self) -> bool {
let mut edge_counts: HashMap<EdgeKey, usize> = HashMap::new();
for tri in &self.triangles {
for &(a, b) in &[(tri.v0, tri.v1), (tri.v1, tri.v2), (tri.v2, tri.v0)] {
let key = EdgeKey::new(a, b);
*edge_counts.entry(key).or_insert(0) += 1;
}
}
edge_counts.values().all(|&c| c == 2)
}
}
#[derive(Debug, Clone, Default)]
pub struct StlValidationReport {
pub boundary_edge_count: usize,
pub is_watertight: bool,
pub is_manifold: bool,
pub degenerate_count: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TriangleMesh {
pub positions: Vec<[f32; 3]>,
pub normals: Vec<[f32; 3]>,
pub indices: Vec<[usize; 3]>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct StlColor {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl StlColor {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self {
r: r & 0x1F,
g: g & 0x1F,
b: b & 0x1F,
}
}
pub fn encode(&self) -> u16 {
0x8000 | ((self.r as u16) << 10) | ((self.g as u16) << 5) | (self.b as u16)
}
pub fn decode(attr: u16) -> Option<Self> {
if attr & 0x8000 == 0 {
return None;
}
Some(Self {
r: ((attr >> 10) & 0x1F) as u8,
g: ((attr >> 5) & 0x1F) as u8,
b: (attr & 0x1F) as u8,
})
}
pub fn to_normalized(&self) -> [f32; 3] {
[
self.r as f32 / 31.0,
self.g as f32 / 31.0,
self.b as f32 / 31.0,
]
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct StlQualityMetrics {
pub max_aspect_ratio: f32,
pub min_area: f32,
pub max_area: f32,
pub degenerate_fraction: f32,
pub has_bad_geometry: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct StlValidation {
pub degenerate_count: usize,
pub non_manifold_edges: usize,
pub is_watertight: bool,
pub nan_inf_count: usize,
pub normals_valid: bool,
}
#[derive(PartialEq, Eq, Hash)]
pub(super) struct EdgeKey {
pub(super) a: [u32; 3],
pub(super) b: [u32; 3],
}
impl EdgeKey {
pub(super) fn new(p: [f32; 3], q: [f32; 3]) -> Self {
let pa = p.map(f32::to_bits);
let qa = q.map(f32::to_bits);
if pa <= qa {
EdgeKey { a: pa, b: qa }
} else {
EdgeKey { a: qa, b: pa }
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct StlStatistics {
pub triangle_count: usize,
pub surface_area: f32,
pub bb_min: [f32; 3],
pub bb_max: [f32; 3],
pub bb_size: [f32; 3],
pub avg_triangle_area: f32,
pub min_triangle_area: f32,
pub max_triangle_area: f32,
pub avg_edge_length: f32,
pub approx_unique_vertices: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct WeldedMesh {
pub vertices: Vec<[f32; 3]>,
pub triangles: Vec<[usize; 3]>,
}