Files
OneCAD-Draft/src/io/stl.rs
T
Hakan SevenandClaude Opus 4.8 7fe7e0230d refactor(scene): group modules into role-based subfolders
Move the 30 flat modules under src/scene/ into six folders by role:
convert (entity→geometry), text (fonts/shaping), model (render models),
pick (hit-test/selection/grips/xclip), view (camera/transform/render),
cache (block + property caches). pipeline/ is unchanged.

Full path migration — all `scene::X` references become
`scene::<group>::X`, no compatibility re-exports. render.rs items that
were `pub(super)` (parent was scene) become `pub(in crate::scene)` to
keep their original scene-wide visibility.

No behavior change; build and tests are green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 11:33:46 +03:00

94 lines
2.7 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// STL binary export — converts all tessellated MeshModels in the scene to a
// single binary STL file.
//
// Binary STL format:
// 80-byte header
// 4-byte triangle count (u32 LE)
// Per triangle (50 bytes):
// 3 × f32 normal
// 3 × 3 × f32 vertices
// 2-byte attribute (0)
use std::io::Write;
use crate::scene::model::mesh_model::MeshModel;
/// Build a binary STL byte buffer from a slice of mesh models.
/// Returns `None` if there are no triangles to export.
pub fn build_stl(meshes: &[&MeshModel]) -> Option<Vec<u8>> {
// Collect all triangles.
struct Tri {
normal: [f32; 3],
v: [[f32; 3]; 3],
}
let mut tris: Vec<Tri> = Vec::new();
for mesh in meshes {
let verts = &mesh.verts;
let idx = &mesh.indices;
let n_tri = idx.len() / 3;
for t in 0..n_tri {
let i0 = idx[t * 3] as usize;
let i1 = idx[t * 3 + 1] as usize;
let i2 = idx[t * 3 + 2] as usize;
if i0 >= verts.len() || i1 >= verts.len() || i2 >= verts.len() {
continue;
}
let a = verts[i0];
let b = verts[i1];
let c = verts[i2];
// Compute face normal.
let normal = if !mesh.normals.is_empty() && i0 < mesh.normals.len() {
mesh.normals[i0]
} else {
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let nx = ab[1] * ac[2] - ab[2] * ac[1];
let ny = ab[2] * ac[0] - ab[0] * ac[2];
let nz = ab[0] * ac[1] - ab[1] * ac[0];
let len = (nx * nx + ny * ny + nz * nz).sqrt().max(f32::EPSILON);
[nx / len, ny / len, nz / len]
};
tris.push(Tri {
normal,
v: [a, b, c],
});
}
}
if tris.is_empty() {
return None;
}
let mut buf: Vec<u8> = Vec::with_capacity(84 + tris.len() * 50);
// 80-byte header.
let mut header = [0u8; 80];
let title = b"Open CAD Studio STL export";
header[..title.len()].copy_from_slice(title);
buf.extend_from_slice(&header);
// Triangle count.
buf.extend_from_slice(&(tris.len() as u32).to_le_bytes());
for tri in &tris {
// Normal.
for &f in &tri.normal {
buf.write_all(&f.to_le_bytes()).ok()?;
}
// Vertices.
for v in &tri.v {
for &f in v {
buf.write_all(&f.to_le_bytes()).ok()?;
}
}
// Attribute byte count = 0.
buf.extend_from_slice(&0u16.to_le_bytes());
}
Some(buf)
}