diff --git a/README.md b/README.md index 3c1474c..d16be52 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ A work in progress FOSS slicer for resin printers. -![mslicer_BOg0d3xB6g](https://github.com/user-attachments/assets/aecc299c-07e9-4449-82d2-60777378f870) +![mslicer_BOg0d3xB6g](https://github.com/user-attachments/assets/ce2f1d99-cce8-4006-8c4f-43aecb63e53b) diff --git a/TODO.md b/TODO.md index f10f5ac..94a86bb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,9 @@ # Todo - [ ] Verify slicer on more models -- [ ] Preprocess mesh to remove self-intersections? -- [ ] Separate each surface from a mesh before slicing to fix the self intersection problem? -- [ ] Integrate remote send into ui +- [x] Preprocess mesh to remove self-intersections? +- [x] Separate each surface from a mesh before slicing to fix the self intersection problem? +- [x] Integrate remote send into ui - [x] Allow slicing multiple modals at once - [x] Fix translation in slicer (its currently moved by pixels not mm) - [x] Allow rotating modals @@ -26,7 +26,7 @@ - [x] Clamp camera rotations - [x] Fix slicing rotated objects - [ ] 0-pad slice preview layer display so its width doesn't change as you move the slider -- [ ] Preload layer 1 in slice preview +- [x] Preload layer 1 in slice preview - [x] Better camera movement (left click to orbit, right click to change orbit point) - [ ] Don't crash when slicing an object outside of bounds - [x] Add support for loading .obj files @@ -48,7 +48,6 @@ - [x] Use egui_dock to get a more clean look - [x] Align to bed button - [x] Define all crate versions in workspace toml -- [ ] MQTT commands use refs to strings - [ ] Ask for filename when sending to printer - [ ] Printer scanning (UDP broadcast) - [ ] Verify printer capabilities before uploading / printing diff --git a/slicer/src/mesh.rs b/slicer/src/mesh.rs index dcaee7f..bedfb3d 100644 --- a/slicer/src/mesh.rs +++ b/slicer/src/mesh.rs @@ -62,6 +62,10 @@ impl Mesh { self.faces().get(index).unwrap() } + pub fn normal(&self, index: usize) -> &Pos { + self.normals().get(index).unwrap() + } + pub fn vertex_count(&self) -> usize { self.vertices().len() } @@ -138,6 +142,11 @@ impl Mesh { (self.transformation_matrix * pos.push(1.0)).xyz() } + /// Transforms a normal according to the models scale and rotation. + pub fn transform_normal(&self, normal: &Pos) -> Pos { + (self.transformation_matrix * normal.to_homogeneous()).xyz() + } + /// Undoes the transformation of a point from the models translation, scale, and rotation. pub fn inv_transform(&self, pos: &Pos) -> Pos { (self.inv_transformation_matrix * pos.push(1.0)).xyz() diff --git a/slicer/src/segments.rs b/slicer/src/segments.rs index 3631cd6..66b7993 100644 --- a/slicer/src/segments.rs +++ b/slicer/src/segments.rs @@ -53,7 +53,8 @@ impl Segments { } /// Intersects a plane with the mesh this Segments instance was built with. - pub fn intersect_plane(&self, mesh: &Mesh, height: f32) -> Vec> { + /// Returns a list of line segments along with the direction of the face. + pub fn intersect_plane(&self, mesh: &Mesh, height: f32) -> Vec<([Vector3; 2], bool)> { let mut out = Vec::new(); let layer = (height - self.start_height) / self.layer_height; @@ -62,7 +63,10 @@ impl Segments { } for &face in self.layers[layer as usize].iter() { - intersect_triangle(mesh, &self.transformed_points, face, height, &mut out); + let segment = intersect_triangle(mesh, &self.transformed_points, face, height); + if let Some(segment) = segment { + out.push((segment, mesh.transform_normal(mesh.normal(face)).x > 0.0)); + } } out @@ -90,8 +94,7 @@ fn intersect_triangle( points: &[Vector3], face: usize, height: f32, - out: &mut Vec>, -) { +) -> Option<[Vector3; 2]> { // Get all the vertices of the face let face = mesh.face(face); let v0 = points[face[0] as usize]; @@ -104,13 +107,17 @@ fn intersect_triangle( let (a, b, c) = (v0.z - height, v1.z - height, v2.z - height); let (a_pos, b_pos, c_pos) = (a > 0.0, b > 0.0, c > 0.0); + let mut out = [Vector3::zeros(); 2]; + let mut n = 0; + // Closure called when the line segment from v0 to v1 is intersecting the // plane. t is how far along the line the intersection is and intersections, // it well the point that is intersecting with the plane. let mut push_intersection = |a: f32, b: f32, v0: Vector3, v1: Vector3| { let t = a / (a - b); let intersection = v0 + t * (v1 - v0); - out.push(intersection); + out[n] = intersection; + n += 1; }; // And as you can see my aversion to else blocks now includes if blocks... @@ -119,4 +126,6 @@ fn intersect_triangle( (a_pos ^ b_pos).then(|| push_intersection(a, b, v0, v1)); (b_pos ^ c_pos).then(|| push_intersection(b, c, v1, v2)); (c_pos ^ a_pos).then(|| push_intersection(c, a, v2, v0)); + + (n == 2).then_some(out) } diff --git a/slicer/src/slicer.rs b/slicer/src/slicer.rs index 70d9fa7..bae1b8d 100644 --- a/slicer/src/slicer.rs +++ b/slicer/src/slicer.rs @@ -98,15 +98,11 @@ impl Slicer { // model. Because all the faces are triangles, every triangle // intersection will return two points. These can then be // interpreted as line segments making up a polygon. - let intersections = self + let segments = self .models .iter() .enumerate() - .flat_map(|(idx, mesh)| segments[idx].intersect_plane(mesh, height)); - let segments = intersections - .chunks(2) - .into_iter() - .map(|mut x| (x.next().unwrap(), x.next().unwrap())) + .flat_map(|(idx, mesh)| segments[idx].intersect_plane(mesh, height)) .collect::>(); // Creates a new encoded for this layer. Because printers can @@ -126,26 +122,49 @@ impl Slicer { let yf = y as f32; let mut intersections = segments .iter() + .map(|x| (x.0[0], x.0[1], x.1)) // Filtering to only consider segments with one point // above the current row and one point below. - .filter(|&(a, b)| ((a.y > yf) ^ (b.y > yf))) - .map(|(a, b)| { + .filter(|&(a, b, _)| ((a.y > yf) ^ (b.y > yf))) + .map(|(a, b, facing)| { // Get the x position of the line segment at this y let t = (yf - a.y) / (b.y - a.y); - a.x + t * (b.x - a.x) + (a.x + t * (b.x - a.x), facing) }) .collect::>(); // Sort all these intersections for run-length encoding - intersections.sort_by_key(|&x| OrderedFloat(x)); + intersections.sort_by_key(|&(x, _)| OrderedFloat(x)); + + // In order to avoid creating a cavity in the model when + // there is an intersection either by the same mesh or + // another mesh, these intersections are removed. This is + // done by looking at the direction each line segment is + // facing. For example, <- <- -> -> would be reduced to <- ->. + let mut i = 1; + let mut ignore = 0; + while i < intersections.len() { + let (_, last_facing) = intersections[i - 1]; + let (_, facing) = intersections[i]; + + if facing == last_facing { + intersections.remove(i); + ignore += 1; + } else if ignore > 0 { + intersections.remove(i); + ignore -= 1; + } else { + i += 1; + } + } // Convert the intersections into runs of white pixels to be // encoded into the layer for span in intersections.chunks_exact(2) { let y_offset = (self.slice_config.platform_resolution.x * y) as u64; - let a = span[0].round() as u64; - let b = span[1].round() as u64; + let a = span[0].0.round() as u64; + let b = span[1].0.round() as u64; let start = a + y_offset; let end = b + y_offset;