diff --git a/bampy/Cargo.toml b/bampy/Cargo.toml index a6499f0..95dd3ad 100644 --- a/bampy/Cargo.toml +++ b/bampy/Cargo.toml @@ -23,6 +23,8 @@ bvh = "0.8.0" nalgebra = "0.32.4" num = "0.4.1" approx = "0.5.1" +serde = "1.0.197" +tsify = { version = "0.4.5", features = ["js"] } [dependencies.getrandom] features = ["js"] diff --git a/bampy/src/lib.rs b/bampy/src/lib.rs index fa0c29c..2d2281a 100644 --- a/bampy/src/lib.rs +++ b/bampy/src/lib.rs @@ -1,8 +1,10 @@ use nalgebra::{vector, Vector3}; +use serde::{Deserialize, Serialize}; +use tsify::Tsify; use wasm_bindgen::prelude::wasm_bindgen; use crate::slicer::{ - base_slices::create_base_slices, mesh::Mesh, split_surface::split_surface, triangle::Triangle, + base_slices::create_slices, mesh::Mesh, split_surface::split_surface, triangle::Triangle, SlicerOptions, }; @@ -11,8 +13,32 @@ mod util; const BED_NORMAL: Vector3 = vector![0f32, 0f32, 1f32]; +#[derive(Tsify, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[tsify(from_wasm_abi)] +pub struct SliceOptions { + #[tsify(type = "Float32Array")] + positions: Vec, + layer_height: f32, + max_angle: f32, +} + +#[derive(Tsify, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[tsify(into_wasm_abi)] +pub struct SliceResult { + #[tsify(type = "Array")] + rings: Vec>, +} + #[wasm_bindgen] -pub fn slice(positions: &[f32], layer_height: f32, max_angle: f32) { +pub fn slice( + SliceOptions { + positions, + layer_height, + max_angle, + }: SliceOptions, +) -> SliceResult { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); assert_eq!(positions.len() % 9, 0); @@ -29,21 +55,34 @@ pub fn slice(positions: &[f32], layer_height: f32, max_angle: f32) { if triangle.normal.angle(&BED_NORMAL) > max_angle { slicable_triangles.push(triangle); } else { + slicable_triangles.push(triangle); surface_triangles.push(triangle); } } slicable_triangles.shrink_to_fit(); surface_triangles.shrink_to_fit(); - console_log!("Computing BVH"); - let slicer_options = SlicerOptions { layer_height }; console_log!("Creating Surfaces"); let surfaces = split_surface(surface_triangles); - console_log!("Creating Slices"); + console_log!("Computing BVH"); let slicable = Mesh::from(slicable_triangles); - let base_slices = create_base_slices(&slicer_options, &slicable); + console_log!("Creating Slices"); + let slices = create_slices(&slicer_options, &slicable); console_log!("Done"); + + SliceResult { + rings: slices + .into_iter() + .map(|slice| { + slice + .points + .into_iter() + .flat_map(|point| [point.x, point.y, point.z]) + .collect() + }) + .collect(), + } } diff --git a/bampy/src/slicer/base_slices.rs b/bampy/src/slicer/base_slices.rs index dc0e97b..263969b 100644 --- a/bampy/src/slicer/base_slices.rs +++ b/bampy/src/slicer/base_slices.rs @@ -1,6 +1,13 @@ use bvh::bvh::BvhNode; -use super::{line::Line3, mesh::Mesh, SlicerOptions}; +use crate::console_log; + +use super::{ + line::Line3, + mesh::Mesh, + slice_rings::{find_slice_rings, SliceRing}, + SlicerOptions, +}; #[derive(Debug)] pub struct BaseSlice { @@ -10,11 +17,12 @@ pub struct BaseSlice { /// Creates base slices from the geometry, excluding surfaces. /// The slicse are not sorted or separated into rings. -pub fn create_base_slices(options: &SlicerOptions, slicable: &Mesh) -> Vec { +pub fn create_slices(options: &SlicerOptions, slicable: &Mesh) -> Vec { let layer_count = f32::floor(slicable.aabb.max.z / options.layer_height) as usize; - let mut base_slices = Vec::::with_capacity(layer_count); + let mut rings = vec![]; for i in 0..layer_count { + console_log!("Layer {}", i); let layer = i as f32 * options.layer_height; let mut base_slice = BaseSlice { z: layer, @@ -33,10 +41,10 @@ pub fn create_base_slices(options: &SlicerOptions, slicable: &Mesh) -> Vec< child_r_aabb, } => { if layer >= child_l_aabb.min.z && layer <= child_l_aabb.max.z { - stack.push(child_r_index); + stack.push(child_l_index); } if layer >= child_r_aabb.min.z && layer <= child_r_aabb.max.z { - stack.push(child_l_index); + stack.push(child_r_index); } } BvhNode::Leaf { @@ -52,8 +60,8 @@ pub fn create_base_slices(options: &SlicerOptions, slicable: &Mesh) -> Vec< } } - base_slices.push(base_slice); + rings.append(&mut find_slice_rings(base_slice)); } - base_slices + rings } diff --git a/bampy/src/slicer/mod.rs b/bampy/src/slicer/mod.rs index a362a3c..bcb198d 100644 --- a/bampy/src/slicer/mod.rs +++ b/bampy/src/slicer/mod.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; pub mod base_slices; pub mod line; pub mod mesh; +pub mod slice_rings; pub mod split_surface; pub mod triangle; diff --git a/bampy/src/slicer/slice_rings.rs b/bampy/src/slicer/slice_rings.rs new file mode 100644 index 0000000..23ba0db --- /dev/null +++ b/bampy/src/slicer/slice_rings.rs @@ -0,0 +1,54 @@ +use approx::{relative_eq, relative_ne}; +use nalgebra::Vector3; + +use crate::console_log; + +use super::base_slices::BaseSlice; + +#[derive(Debug)] +pub struct SliceRing { + pub z: f32, + pub points: Vec>, +} + +pub fn find_slice_rings(mut slice: BaseSlice) -> Vec { + let mut rings = vec![]; + while let Some(line) = slice.lines.pop() { + let mut ring = SliceRing { + z: slice.z, + points: vec![line.start, line.end], + }; + let mut right = ring.points[1]; + + let mut previous_len = usize::MAX; + while relative_ne!(ring.points[0], right) { + if previous_len == ring.points.len() { + console_log!( + "Error: Could not find a ring for slice at z = {}, {} items left.", + slice.z, + ring.points.len() + ); + break; + } + previous_len = ring.points.len(); + + slice.lines.retain_mut(|line| { + if relative_eq!(line.start, right, epsilon = 0.001) { + ring.points.push(line.end); + right = line.end; + false + } else if relative_eq!(line.end, right, epsilon = 0.001) { + ring.points.push(line.start); + right = line.start; + false + } else { + true + } + }) + } + + rings.push(ring) + } + + rings +} diff --git a/bampy/src/slicer/triangle.rs b/bampy/src/slicer/triangle.rs index b650407..afd5bd8 100644 --- a/bampy/src/slicer/triangle.rs +++ b/bampy/src/slicer/triangle.rs @@ -23,16 +23,24 @@ fn vec_inside_aabb( aabb: &Aabb, ) -> bool { vec.x >= aabb.min.x - && vec.x <= aabb.max.x && vec.y >= aabb.min.y - && vec.y <= aabb.max.y && vec.z >= aabb.min.z + && vec.x <= aabb.max.x + && vec.y <= aabb.max.y && vec.z <= aabb.max.z } impl Triangle where - T: SimdPartialOrd + Scalar + Copy + ClosedMul + ClosedAdd + ClosedSub + Float + FromPrimitive, + T: SimdPartialOrd + + RelativeEq + + Scalar + + Copy + + ClosedMul + + ClosedAdd + + ClosedSub + + Float + + FromPrimitive, { pub fn new(a: Vector3, b: Vector3, c: Vector3) -> Self { let normal = (b - a).cross(&(c - a)); @@ -70,15 +78,11 @@ where self.has_vec(other.a) || self.has_vec(other.b) || self.has_vec(other.c) } - pub fn iter(&self) -> impl Iterator> { - vec![&self.a, &self.b, &self.c].into_iter() - } - pub fn intersect_z(&self, z: T) -> Option> { let mut intersection = Vec::with_capacity(3); let mut last = self.c; - for point in self.iter() { - if point.z == z { + for point in [self.a, self.b, self.c].iter() { + if relative_eq!(point.z, z) { intersection.push(*point); } else if last.z < z && point.z > z || last.z > z && point.z < z { intersection.push(last.lerp(&point, (z - last.z) / (point.z - last.z))); diff --git a/src/lib/components/Scene.svelte b/src/lib/components/Scene.svelte index ae04b2f..587555d 100644 --- a/src/lib/components/Scene.svelte +++ b/src/lib/components/Scene.svelte @@ -101,9 +101,9 @@ {@const visible = showSlices >= i / $layers.length} {@const color = new Color(0, i / $layers.length, 0.2)} {#if type === LayerType.Line} - + - + {:else if type === LayerType.Surface} diff --git a/src/lib/slicer/newFile.ts b/src/lib/slicer/newFile.ts new file mode 100644 index 0000000..900ab74 --- /dev/null +++ b/src/lib/slicer/newFile.ts @@ -0,0 +1,27 @@ +import { BufferGeometry, BufferGeometryLoader, Float32BufferAttribute } from 'three'; +import { type WorkerEvent } from './worker-data'; +import init, { slice } from 'bampy'; + +addEventListener('message', async (event: MessageEvent) => { + if (event.data.type === 'slice') { + const geometry = new BufferGeometryLoader().parse(event.data.data.stl); + if (geometry.index !== null) { + geometry.toNonIndexed(); + } + await init(); + const result = slice({ + positions: geometry.attributes.position.array as Float32Array, + layerHeight: event.data.data.layerHeight, + maxAngle: event.data.data.maxNonPlanarAngle + }); + for (const layer of result.rings) { + const geometry = new BufferGeometry(); + geometry.setAttribute('position', new Float32BufferAttribute(layer, 3)); + + self.postMessage({ + type: 'layer', + data: { type: LayerType.Line, geometry: layerGeometry.toJSON() } + } satisfies LayerMessage); + } + } +}); diff --git a/src/lib/slicer/worker.ts b/src/lib/slicer/worker.ts index f43212c..277eef4 100644 --- a/src/lib/slicer/worker.ts +++ b/src/lib/slicer/worker.ts @@ -1,5 +1,11 @@ -import { BufferGeometryLoader } from 'three'; -import { type SliceArguments, type ProgressMessage, type WorkerEvent } from './worker-data'; +import { BufferGeometry, BufferGeometryLoader, Float32BufferAttribute } from 'three'; +import { + type SliceArguments, + type ProgressMessage, + type WorkerEvent, + type LayerMessage, + LayerType +} from './worker-data'; import init, { slice } from 'bampy'; addEventListener('message', async (event: MessageEvent) => { @@ -9,11 +15,20 @@ addEventListener('message', async (event: MessageEvent) => { geometry.toNonIndexed(); } await init(); - slice( - geometry.attributes.position.array as Float32Array, - event.data.data.layerHeight, - event.data.data.maxNonPlanarAngle - ); + const result = slice({ + positions: geometry.attributes.position.array as Float32Array, + layerHeight: event.data.data.layerHeight, + maxAngle: event.data.data.maxNonPlanarAngle + }); + for (const layer of result.rings) { + const geometry = new BufferGeometry(); + geometry.setAttribute('position', new Float32BufferAttribute(layer, 3)); + + self.postMessage({ + type: 'layer', + data: { type: LayerType.Line, geometry: geometry.toJSON() } + } satisfies LayerMessage); + } } });