use crate::linalg::Vec3;
use crate::manifold::Manifold;
use crate::types::{BooleanEngine, Error, MeshGL};
fn parse_binary_stl(data: &[u8]) -> Vec<f32> {
assert!(data.len() >= 84, "STL too short");
let n_faces = u32::from_le_bytes(data[80..84].try_into().unwrap()) as usize;
assert!(
84 + n_faces * 50 <= data.len(),
"STL truncated: header declares {n_faces} faces"
);
let mut positions = Vec::with_capacity(n_faces * 9);
for f in 0..n_faces {
let rec = 84 + f * 50;
for i in 0..9 {
let off = rec + 12 + i * 4;
positions.push(f32::from_le_bytes(data[off..off + 4].try_into().unwrap()));
}
}
positions
}
fn parse_ascii_stl(data: &[u8]) -> Vec<f32> {
let text = std::str::from_utf8(data).expect("ASCII STL is not UTF-8");
let mut positions = Vec::new();
for line in text.lines() {
let mut it = line.split_whitespace();
if it.next() == Some("vertex") {
for _ in 0..3 {
let v: f64 = it
.next()
.expect("vertex line missing coordinate")
.parse()
.expect("bad coordinate");
positions.push(v as f32);
}
}
}
positions
}
fn is_ascii_stl(data: &[u8]) -> bool {
let head = &data[..data.len().min(512)];
let head = String::from_utf8_lossy(head);
head.trim_start().starts_with("solid") && head.contains("facet")
}
fn normalize_positions(positions: &mut [f32]) {
let n_verts = positions.len() / 3;
let (mut min, mut max) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
for i in 0..n_verts {
for k in 0..3 {
let x = positions[i * 3 + k] as f64;
if x < min[k] {
min[k] = x;
}
if x > max[k] {
max[k] = x;
}
}
}
let center = [
(min[0] + max[0]) / 2.0,
(min[1] + max[1]) / 2.0,
(min[2] + max[2]) / 2.0,
];
let max_side = (max[0] - min[0]).max(max[1] - min[1]).max(max[2] - min[2]);
let scale = if max_side > 0.0 { 2.0 / max_side } else { 1.0 };
for i in 0..n_verts {
for k in 0..3 {
positions[i * 3 + k] = ((positions[i * 3 + k] as f64 - center[k]) * scale) as f32;
}
}
}
fn import_stl_like_demo(stl: &[u8]) -> Manifold {
let mut positions = if is_ascii_stl(stl) {
parse_ascii_stl(stl)
} else {
parse_binary_stl(stl)
};
normalize_positions(&mut positions);
let n_verts = (positions.len() / 3) as u32;
let mut mesh = MeshGL::default();
mesh.num_prop = 3;
mesh.vert_properties = positions;
mesh.tri_verts = (0..n_verts).collect();
mesh.merge();
Manifold::from_mesh_gl_robust(&mesh)
}
const TENTACLE_939888: &[u8] = include_bytes!("testdata/939888.stl");
const PICKAXE_93557: &[u8] = include_bytes!("testdata/93557.stl");
const MODEL_92068: &[u8] = include_bytes!("testdata/92068.stl");
const MODEL_39926: &[u8] = include_bytes!("testdata/39926.stl");
const FRAME_1075458: &[u8] = include_bytes!("testdata/1075458.stl");
const TOWER_91115: &[u8] = include_bytes!("testdata/91115.stl");
#[test]
fn thingi_92068_union_39926_is_closed() {
let a = import_stl_like_demo(MODEL_92068);
assert_eq!(a.status(), Error::NoError, "operand A import");
assert!(!a.as_impl().is_soup, "operand A should weld to a manifold");
let b = import_stl_like_demo(MODEL_39926).translate(Vec3::new(0.3, 0.0, 0.0));
assert_eq!(b.status(), Error::NoError, "operand B import");
assert!(!b.as_impl().is_soup, "operand B should weld to a manifold");
let result = a.union_with_engine(&b, BooleanEngine::Robust);
assert_eq!(result.status(), Error::NoError, "robust union status");
assert!(!result.is_empty(), "robust union should be non-empty");
assert!(result.volume() > 0.0, "union volume must be positive");
}
#[test]
fn thingi_1075458_minus_91115_is_valid() {
let a = import_stl_like_demo(FRAME_1075458);
assert_eq!(a.status(), Error::NoError, "operand A import");
let b = import_stl_like_demo(TOWER_91115)
.rotate(311.0, 55.0, 345.0)
.translate(Vec3::new(0.7, -0.2, 0.4));
assert_eq!(b.status(), Error::NoError, "operand B import");
let result = a.boolean_with_engine(&b, crate::types::OpType::Subtract, BooleanEngine::Robust);
assert_eq!(result.status(), Error::NoError, "robust difference status");
let volume = result.volume();
assert!(volume.is_finite(), "difference volume must be finite");
assert!(volume > 0.0, "difference volume must be positive");
}
#[test]
fn thingi_939888_union_93557_is_closed() {
let a = import_stl_like_demo(TENTACLE_939888);
assert_eq!(a.status(), Error::NoError, "operand A import");
assert!(!a.as_impl().is_soup, "operand A should weld to a manifold");
let b = import_stl_like_demo(PICKAXE_93557)
.rotate(356.0, 140.0, 322.0)
.translate(Vec3::new(0.3, 0.0, 0.0));
assert_eq!(b.status(), Error::NoError, "operand B import");
assert!(!b.as_impl().is_soup, "operand B should weld to a manifold");
let result = a.union_with_engine(&b, BooleanEngine::Robust);
assert_eq!(result.status(), Error::NoError, "robust union status");
assert!(!result.is_empty(), "robust union should be non-empty");
assert!(result.volume() > 0.0, "union volume must be positive");
}