diff --git a/Cargo.lock b/Cargo.lock index f3672af..4706134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -790,6 +790,15 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ordered-float" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.21.0" @@ -1089,6 +1098,8 @@ dependencies = [ "image", "imageproc", "nalgebra", + "ordered-float", + "rand", "rayon", "stl_io", ] diff --git a/common/src/serde/mod.rs b/common/src/serde/mod.rs index cbfad25..d7c7e79 100644 --- a/common/src/serde/mod.rs +++ b/common/src/serde/mod.rs @@ -3,5 +3,5 @@ mod serializer; mod types; pub use deserializer::Deserializer; -pub use serializer::Serializer; +pub use serializer::{DynamicSerializer, Serializer, SizedSerializer}; pub use types::SizedString; diff --git a/goo_format/src/encoded_layer.rs b/goo_format/src/encoded_layer.rs index d12320a..17a726d 100644 --- a/goo_format/src/encoded_layer.rs +++ b/goo_format/src/encoded_layer.rs @@ -93,6 +93,11 @@ impl LayerEncoder { self.last_value = value; } + + pub fn finish(self) -> (Vec, u8) { + let checksum = calculate_checksum(&self.data); + (self.data, checksum) + } } impl<'a> LayerDecoder<'a> { diff --git a/goo_format/src/layer_content.rs b/goo_format/src/layer_content.rs index 9ee4581..75907c8 100644 --- a/goo_format/src/layer_content.rs +++ b/goo_format/src/layer_content.rs @@ -45,6 +45,7 @@ impl LayerContent { ser.write_f32(self.second_retract_distance); ser.write_f32(self.second_retract_speed); ser.write_u16(self.light_pwm); + ser.write_bytes(DELIMITER); ser.write_u32(self.data.len() as u32 + 2); ser.write_bytes(&[0x55]); ser.write_bytes(&self.data); diff --git a/slicer/Cargo.toml b/slicer/Cargo.toml index eb58145..0b7c013 100644 --- a/slicer/Cargo.toml +++ b/slicer/Cargo.toml @@ -10,6 +10,8 @@ anyhow = "1.0.86" image = "0.25.1" imageproc = "0.25.0" nalgebra = "0.32.6" +ordered-float = "4.2.0" +rand = "0.8.5" rayon = "1.10.0" stl_io = "0.7.0" diff --git a/slicer/src/main.rs b/slicer/src/main.rs index 4290cc5..448a6f5 100644 --- a/slicer/src/main.rs +++ b/slicer/src/main.rs @@ -1,16 +1,20 @@ -use std::{fs::File, time::Instant}; +use std::{ + fs::{self, File}, + time::Instant, +}; use anyhow::Result; -use image::RgbImage; -use imageproc::point::Point; +use common::serde::DynamicSerializer; +use goo_format::{File as GooFile, HeaderInfo, LayerContent, LayerEncoder}; +use image::{Rgb, RgbImage}; use mesh::load_mesh; use nalgebra::{Vector2, Vector3}; -use tmp_image::{draw_line_segment_invert_mut, draw_polygon_with_mut}; +use ordered_float::OrderedFloat; +use rayon::iter::{ParallelBridge, ParallelIterator}; type Pos = Vector3; mod mesh; -mod tmp_image; struct SliceConfig { platform_resolution: Vector2, @@ -20,6 +24,7 @@ struct SliceConfig { fn main() -> Result<()> { const FILE_PATH: &str = "teapot.stl"; + const OUTPUT_PATH: &str = "output.goo"; let slice_config = SliceConfig { // platform_resolution: Vector2::new(1920, 1080), @@ -32,8 +37,12 @@ fn main() -> Result<()> { let mut mesh = load_mesh(&mut file, "stl")?; let (min, max) = mesh.minmax_point(); - let real_scale = 100.0; - mesh.scale = Pos::new(real_scale, real_scale, 1.0); + let real_scale = 1.0; + mesh.scale = Pos::new( + real_scale / slice_config.platform_size.x * slice_config.platform_resolution.x as f32, + real_scale / slice_config.platform_size.y * slice_config.platform_resolution.y as f32, + real_scale, + ); let center = slice_config.platform_resolution / 2; let mesh_center = (min + max) / 2.0; @@ -51,104 +60,83 @@ fn main() -> Result<()> { let now = Instant::now(); - let mut height = 0.0; - let mut i = 0; - - let mut image = RgbImage::new( - slice_config.platform_resolution.x, - slice_config.platform_resolution.y, - ); - let max = mesh.transform(&max); - while height < max.z { - let intersections = mesh.intersect_plane(height); - println!("Height: {}, Intersections: {}", height, intersections.len()); + let layers = (max.z / slice_config.slice_height).ceil() as u32; - let mut segments = intersections - .chunks(2) - .map(|x| (x[0], x[1])) - .collect::>(); + let layers = (0..layers) + .par_bridge() + .map(|layer| { + let height = layer as f32 * slice_config.slice_height; - if segments.is_empty() { - height += slice_config.slice_height; - i += 1; - continue; - } + let intersections = mesh.intersect_plane(height); + println!("Height: {}, Intersections: {}", height, intersections.len()); - fn points_equal(a: &Pos, b: &Pos) -> bool { - (a.x - b.x).abs() < 0.0001 && (a.y - b.y).abs() < 0.0001 - } - - fn points_equal_int(a: &Pos, b: &Pos) -> bool { - (a.x as i32 == b.x as i32) && (a.y as i32 == b.y as i32) - } - - let mut polygons = Vec::new(); - let mut polygon = Vec::new(); - - 'outer: loop { - if polygon.is_empty() { - if segments.is_empty() { - break; - } - - let first = segments.remove(0); - polygon.push(first.0); - polygon.push(first.1); - } - - for j in 0..segments.len() { - let (a, b) = segments[j]; - let last = polygon.last().unwrap(); - - if points_equal(&last, &a) { - polygon.push(b); - segments.remove(j); - continue 'outer; - } else if points_equal(&last, &b) { - polygon.push(a); - segments.remove(j); - continue 'outer; - } - } - - polygons.push(polygon.clone()); - polygon.clear(); - } - - for mut polygon in polygons { - while !polygon.is_empty() && points_equal(&polygon[0], polygon.last().unwrap()) { - polygon.pop(); - } - - while points_equal_int(&polygon[0], polygon.last().unwrap()) { - polygon[0].x -= 1.0; - } - - if polygon.len() < 3 { - continue; - } - - let polygons = polygon - .into_iter() - .map(|x| Point::new(x.x as i32, x.y as i32)) + let segments = intersections + .chunks(2) + .map(|x| (x[0], x[1])) .collect::>(); - draw_polygon_with_mut( - &mut image, - &polygons, - image::Rgb([255, 255, 255]), - draw_line_segment_invert_mut, - ); - } + let mut out = Vec::new(); + for y in 0..slice_config.platform_resolution.y { + let mut intersections = segments + .iter() + .filter_map(|(a, b)| { + let y = y as f32; + if a.y <= y && b.y >= y { + let t = (y - a.y) / (b.y - a.y); + let x = a.x + t * (b.x - a.x); + Some(x) + } else if b.y <= y && a.y >= y { + let t = (y - b.y) / (a.y - b.y); + let x = b.x + t * (a.x - b.x); + Some(x) + } else { + None + } + }) + .collect::>(); - let filename = format!("slice_output/{i}.png"); - image.save(filename)?; - image.fill(0); + intersections.sort_by_key(|&x| OrderedFloat(x)); + intersections.dedup(); - height += slice_config.slice_height; - i += 1; - } + for span in intersections.chunks_exact(2) { + out.push((span[0] as u64, span[1] as u64)); + } + } + + let mut encoder = LayerEncoder::new(); + + let mut last = 0; + for (start, end) in out { + if start > last { + encoder.add_run(start - last, 0); + } + + encoder.add_run(end - start, 1); + last = end; + } + + let (data, checksum) = encoder.finish(); + println!("#{layer} Data Size: {}", data.len()); + LayerContent { + data, + checksum, + ..Default::default() + } + }) + .collect::>(); + + let goo = GooFile::new( + HeaderInfo { + layer_count: layers.len() as u32, + ..Default::default() + }, + layers, + ); + + let mut serializer = DynamicSerializer::new(); + goo.serialize(&mut serializer); + fs::write(OUTPUT_PATH, serializer.into_inner())?; println!("Done. Elapsed: {:.1}s", now.elapsed().as_secs_f32()); diff --git a/slicer/src/tmp_image.rs b/slicer/src/tmp_image.rs deleted file mode 100644 index 502bafc..0000000 --- a/slicer/src/tmp_image.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::cmp::{max, min}; - -use image::{ImageBuffer, Pixel, Rgb}; -use imageproc::{ - drawing::{BresenhamLineIter, Canvas}, - point::Point, -}; - -pub fn draw_polygon_with_mut( - canvas: &mut ImageBuffer, Vec>, - poly: &[Point], - color: Rgb, - plotter: L, -) where - L: Fn(&mut ImageBuffer, Vec>, (f32, f32), (f32, f32), Rgb), -{ - if poly.is_empty() { - return; - } - if poly[0] == poly[poly.len() - 1] { - panic!( - "First point {:?} == last point {:?}", - poly[0], - poly[poly.len() - 1] - ); - } - - let mut y_min = i32::MAX; - let mut y_max = i32::MIN; - for p in poly { - y_min = min(y_min, p.y); - y_max = max(y_max, p.y); - } - - let (width, height) = canvas.dimensions(); - - // Intersect polygon vertical range with image bounds - y_min = max(0, min(y_min, height as i32 - 1)); - y_max = max(0, min(y_max, height as i32 - 1)); - - let mut closed: Vec> = poly.to_vec(); - closed.push(poly[0]); - - let edges: Vec<&[Point]> = closed.windows(2).collect(); - let mut intersections = Vec::new(); - - for y in y_min..y_max + 1 { - for edge in &edges { - let p0 = edge[0]; - let p1 = edge[1]; - - if p0.y <= y && p1.y >= y || p1.y <= y && p0.y >= y { - if p0.y == p1.y { - // Need to handle horizontal lines specially - intersections.push(p0.x); - intersections.push(p1.x); - } else if p0.y == y || p1.y == y { - if p1.y > y { - intersections.push(p0.x); - } - if p0.y > y { - intersections.push(p1.x); - } - } else { - let fraction = (y - p0.y) as f32 / (p1.y - p0.y) as f32; - let inter = p0.x as f32 + fraction * (p1.x - p0.x) as f32; - intersections.push(inter.round() as i32); - } - } - } - - intersections.sort_unstable(); - intersections.chunks(2).for_each(|range| { - let mut from = min(range[0], width as i32); - let mut to = min(range[1], width as i32 - 1); - if from < width as i32 && to >= 0 { - // draw only if range appears on the canvas - from = max(0, from); - to = max(0, to); - - for x in from..to + 1 { - let current = canvas.get_pixel(x as u32, y as u32); - if *current == color { - canvas.draw_pixel(x as u32, y as u32, Rgb([0, 0, 0])); - } else { - canvas.draw_pixel(x as u32, y as u32, color); - } - } - } - }); - - intersections.clear(); - } - - for edge in &edges { - let start = (edge[0].x as f32, edge[0].y as f32); - let end = (edge[1].x as f32, edge[1].y as f32); - plotter(canvas, start, end, color); - } -} - -pub fn draw_line_segment_invert_mut( - canvas: &mut ImageBuffer, Vec>, - start: (f32, f32), - end: (f32, f32), - color: Rgb, -) { - let (width, height) = canvas.dimensions(); - let in_bounds = |x, y| x >= 0 && x < width as i32 && y >= 0 && y < height as i32; - - let line_iterator = BresenhamLineIter::new(start, end); - - for point in line_iterator { - let x = point.0; - let y = point.1; - - if in_bounds(x, y) { - let current = canvas.get_pixel(x as u32, y as u32); - if *current == color { - canvas.draw_pixel(x as u32, y as u32, Rgb([0, 0, 0])); - } else { - canvas.draw_pixel(x as u32, y as u32, color); - } - } - } -}