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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
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]]
|
[[package]]
|
||||||
name = "owned_ttf_parser"
|
name = "owned_ttf_parser"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
@@ -1089,6 +1098,8 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"imageproc",
|
"imageproc",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
|
"ordered-float",
|
||||||
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
"stl_io",
|
"stl_io",
|
||||||
]
|
]
|
||||||
|
@@ -3,5 +3,5 @@ mod serializer;
|
|||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
pub use deserializer::Deserializer;
|
pub use deserializer::Deserializer;
|
||||||
pub use serializer::Serializer;
|
pub use serializer::{DynamicSerializer, Serializer, SizedSerializer};
|
||||||
pub use types::SizedString;
|
pub use types::SizedString;
|
||||||
|
@@ -93,6 +93,11 @@ impl LayerEncoder {
|
|||||||
|
|
||||||
self.last_value = value;
|
self.last_value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> (Vec<u8>, u8) {
|
||||||
|
let checksum = calculate_checksum(&self.data);
|
||||||
|
(self.data, checksum)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LayerDecoder<'a> {
|
impl<'a> LayerDecoder<'a> {
|
||||||
|
@@ -45,6 +45,7 @@ impl LayerContent {
|
|||||||
ser.write_f32(self.second_retract_distance);
|
ser.write_f32(self.second_retract_distance);
|
||||||
ser.write_f32(self.second_retract_speed);
|
ser.write_f32(self.second_retract_speed);
|
||||||
ser.write_u16(self.light_pwm);
|
ser.write_u16(self.light_pwm);
|
||||||
|
ser.write_bytes(DELIMITER);
|
||||||
ser.write_u32(self.data.len() as u32 + 2);
|
ser.write_u32(self.data.len() as u32 + 2);
|
||||||
ser.write_bytes(&[0x55]);
|
ser.write_bytes(&[0x55]);
|
||||||
ser.write_bytes(&self.data);
|
ser.write_bytes(&self.data);
|
||||||
|
@@ -10,6 +10,8 @@ anyhow = "1.0.86"
|
|||||||
image = "0.25.1"
|
image = "0.25.1"
|
||||||
imageproc = "0.25.0"
|
imageproc = "0.25.0"
|
||||||
nalgebra = "0.32.6"
|
nalgebra = "0.32.6"
|
||||||
|
ordered-float = "4.2.0"
|
||||||
|
rand = "0.8.5"
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
stl_io = "0.7.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 anyhow::Result;
|
||||||
use image::RgbImage;
|
use common::serde::DynamicSerializer;
|
||||||
use imageproc::point::Point;
|
use goo_format::{File as GooFile, HeaderInfo, LayerContent, LayerEncoder};
|
||||||
|
use image::{Rgb, RgbImage};
|
||||||
use mesh::load_mesh;
|
use mesh::load_mesh;
|
||||||
use nalgebra::{Vector2, Vector3};
|
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>;
|
type Pos = Vector3<f32>;
|
||||||
|
|
||||||
mod mesh;
|
mod mesh;
|
||||||
mod tmp_image;
|
|
||||||
|
|
||||||
struct SliceConfig {
|
struct SliceConfig {
|
||||||
platform_resolution: Vector2<u32>,
|
platform_resolution: Vector2<u32>,
|
||||||
@@ -20,6 +24,7 @@ struct SliceConfig {
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
const FILE_PATH: &str = "teapot.stl";
|
const FILE_PATH: &str = "teapot.stl";
|
||||||
|
const OUTPUT_PATH: &str = "output.goo";
|
||||||
|
|
||||||
let slice_config = SliceConfig {
|
let slice_config = SliceConfig {
|
||||||
// platform_resolution: Vector2::new(1920, 1080),
|
// platform_resolution: Vector2::new(1920, 1080),
|
||||||
@@ -32,8 +37,12 @@ fn main() -> Result<()> {
|
|||||||
let mut mesh = load_mesh(&mut file, "stl")?;
|
let mut mesh = load_mesh(&mut file, "stl")?;
|
||||||
let (min, max) = mesh.minmax_point();
|
let (min, max) = mesh.minmax_point();
|
||||||
|
|
||||||
let real_scale = 100.0;
|
let real_scale = 1.0;
|
||||||
mesh.scale = Pos::new(real_scale, 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 center = slice_config.platform_resolution / 2;
|
||||||
let mesh_center = (min + max) / 2.0;
|
let mesh_center = (min + max) / 2.0;
|
||||||
@@ -51,104 +60,83 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let now = Instant::now();
|
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);
|
let max = mesh.transform(&max);
|
||||||
while height < max.z {
|
let layers = (max.z / slice_config.slice_height).ceil() as u32;
|
||||||
let intersections = mesh.intersect_plane(height);
|
|
||||||
println!("Height: {}, Intersections: {}", height, intersections.len());
|
|
||||||
|
|
||||||
let mut segments = intersections
|
let layers = (0..layers)
|
||||||
.chunks(2)
|
.par_bridge()
|
||||||
.map(|x| (x[0], x[1]))
|
.map(|layer| {
|
||||||
.collect::<Vec<_>>();
|
let height = layer as f32 * slice_config.slice_height;
|
||||||
|
|
||||||
if segments.is_empty() {
|
let intersections = mesh.intersect_plane(height);
|
||||||
height += slice_config.slice_height;
|
println!("Height: {}, Intersections: {}", height, intersections.len());
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn points_equal(a: &Pos, b: &Pos) -> bool {
|
let segments = intersections
|
||||||
(a.x - b.x).abs() < 0.0001 && (a.y - b.y).abs() < 0.0001
|
.chunks(2)
|
||||||
}
|
.map(|x| (x[0], x[1]))
|
||||||
|
|
||||||
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<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
draw_polygon_with_mut(
|
let mut out = Vec::new();
|
||||||
&mut image,
|
for y in 0..slice_config.platform_resolution.y {
|
||||||
&polygons,
|
let mut intersections = segments
|
||||||
image::Rgb([255, 255, 255]),
|
.iter()
|
||||||
draw_line_segment_invert_mut,
|
.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::<Vec<_>>();
|
||||||
|
|
||||||
let filename = format!("slice_output/{i}.png");
|
intersections.sort_by_key(|&x| OrderedFloat(x));
|
||||||
image.save(filename)?;
|
intersections.dedup();
|
||||||
image.fill(0);
|
|
||||||
|
|
||||||
height += slice_config.slice_height;
|
for span in intersections.chunks_exact(2) {
|
||||||
i += 1;
|
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 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());
|
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