Working slicer
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
@@ -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;
|
||||
|
@@ -93,6 +93,11 @@ impl LayerEncoder {
|
||||
|
||||
self.last_value = value;
|
||||
}
|
||||
|
||||
pub fn finish(self) -> (Vec<u8>, u8) {
|
||||
let checksum = calculate_checksum(&self.data);
|
||||
(self.data, checksum)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LayerDecoder<'a> {
|
||||
|
@@ -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);
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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<f32>;
|
||||
|
||||
mod mesh;
|
||||
mod tmp_image;
|
||||
|
||||
struct SliceConfig {
|
||||
platform_resolution: Vector2<u32>,
|
||||
@@ -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 layers = (max.z / slice_config.slice_height).ceil() as u32;
|
||||
|
||||
let layers = (0..layers)
|
||||
.par_bridge()
|
||||
.map(|layer| {
|
||||
let height = layer as f32 * slice_config.slice_height;
|
||||
|
||||
let intersections = mesh.intersect_plane(height);
|
||||
println!("Height: {}, Intersections: {}", height, intersections.len());
|
||||
|
||||
let mut segments = intersections
|
||||
let segments = intersections
|
||||
.chunks(2)
|
||||
.map(|x| (x[0], x[1]))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if segments.is_empty() {
|
||||
height += slice_config.slice_height;
|
||||
i += 1;
|
||||
continue;
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
draw_polygon_with_mut(
|
||||
&mut image,
|
||||
&polygons,
|
||||
image::Rgb([255, 255, 255]),
|
||||
draw_line_segment_invert_mut,
|
||||
intersections.sort_by_key(|&x| OrderedFloat(x));
|
||||
intersections.dedup();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
let goo = GooFile::new(
|
||||
HeaderInfo {
|
||||
layer_count: layers.len() as u32,
|
||||
..Default::default()
|
||||
},
|
||||
layers,
|
||||
);
|
||||
}
|
||||
|
||||
let filename = format!("slice_output/{i}.png");
|
||||
image.save(filename)?;
|
||||
image.fill(0);
|
||||
|
||||
height += slice_config.slice_height;
|
||||
i += 1;
|
||||
}
|
||||
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());
|
||||
|
||||
|
@@ -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<L>(
|
||||
canvas: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
|
||||
poly: &[Point<i32>],
|
||||
color: Rgb<u8>,
|
||||
plotter: L,
|
||||
) where
|
||||
L: Fn(&mut ImageBuffer<Rgb<u8>, Vec<u8>>, (f32, f32), (f32, f32), Rgb<u8>),
|
||||
{
|
||||
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<Point<i32>> = poly.to_vec();
|
||||
closed.push(poly[0]);
|
||||
|
||||
let edges: Vec<&[Point<i32>]> = 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<Rgb<u8>, Vec<u8>>,
|
||||
start: (f32, f32),
|
||||
end: (f32, f32),
|
||||
color: Rgb<u8>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user