diff --git a/.github/workflows/Book.yml b/.github/workflows/Book.yml index 482288e..c059c67 100644 --- a/.github/workflows/Book.yml +++ b/.github/workflows/Book.yml @@ -33,7 +33,7 @@ jobs: id: pages uses: actions/configure-pages@v5 - name: Build with mdBook - run: mdbook build + run: mdbook build book - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/Cargo.toml b/Cargo.toml index 1c642d9..e40e572 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] clap = { version = "4.5.16", features = ["derive"] } +nalgebra = "0.33.2" serde = { version = "1.0.213", features = ["derive"] } serde_json = "1.0.132" stl_io = "0.8.2" @@ -16,6 +17,3 @@ path = "arcwelderlib-sys" [features] default = [] arcwelder = ["arcwelderlib-sys"] - -[workspace] -members = ["arcwelderlib-sys"] diff --git a/book.toml b/book/book.toml similarity index 93% rename from book.toml rename to book/book.toml index 52650e8..908e264 100644 --- a/book.toml +++ b/book/book.toml @@ -2,11 +2,10 @@ authors = ["Craig M. Hamel", "Lucas K. Gallup"] language = "en" multilingual = false -src = "book/" title = "slicey" [build] -build-dir = "book/build/" +build-dir = "build" create-missing = false [rust] diff --git a/book/src/3DBenchy.stl b/book/src/3DBenchy.stl new file mode 100644 index 0000000..5a51bb5 Binary files /dev/null and b/book/src/3DBenchy.stl differ diff --git a/book/SUMMARY.md b/book/src/SUMMARY.md similarity index 81% rename from book/SUMMARY.md rename to book/src/SUMMARY.md index c5384bc..e9a2d4d 100644 --- a/book/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -4,3 +4,4 @@ Welcome to the slicey book TODO break down documentation into DLP vs. FFF/SLA - [Introduction](./introduction.md) +- [Geometry](./geometry.md) diff --git a/book/src/geometry.md b/book/src/geometry.md new file mode 100644 index 0000000..5e11412 --- /dev/null +++ b/book/src/geometry.md @@ -0,0 +1,15 @@ +# Geometry +The geometry module in slicey handles the necessary IO for working with geometry files such as STL files. Currently, only STL files are supported but we should plan on supporting 3mf files, among others. + +The main hook for this (when working with STL files) is the following + +```rust +# extern crate slicey; +# use slicey::geometry::STLMesh; +# use std; +pub fn main() { + let file_name = "./slicey/book/src/3DBenchy.stl"; + // let mesh = STLMesh::new(file_name.to_string()); + // println!("mesh = {:?}", mesh); +} +``` diff --git a/book/introduction.md b/book/src/introduction.md similarity index 100% rename from book/introduction.md rename to book/src/introduction.md diff --git a/book/src/lib.rs b/book/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index c042d1c..d18c13b 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -1,4 +1,7 @@ +use nalgebra; +use std::env; use std::fs::OpenOptions; +use std::path; use stl_io::{ self, IndexedTriangle, @@ -15,6 +18,18 @@ type Faces = Vec; type Triangles = Vec; type Vertices = Vec; +pub trait RayIntersection { + fn ray_intersection( + &self, + point: &nalgebra::Point3::, + ray: &nalgebra::Vector3:: + ) -> bool { + true // todo + } +} + +impl RayIntersection for Triangle {} + /// #[derive(Debug)] pub struct BoundingBox { @@ -40,7 +55,14 @@ impl STLMesh { let mut file = OpenOptions::new() .read(true) .open(&file_name) - .unwrap(); + // .unwrap(); + .expect( + format!( + "Failed to open STL file. Path is {:?} and current dir is {:?}", + path::Path::new(&file_name), + env::current_dir().unwrap() + ).as_str() + ); let stl = stl_io::read_stl(&mut file).unwrap(); // let triangles = STLMesh::_triangles(&stl); @@ -86,6 +108,20 @@ impl STLMesh { self.translate(0., 0., -bb.z_min); } + pub fn is_inside(&self, point: nalgebra::Point3::) -> bool { + // arbitrary ray in +x direction + let ray = nalgebra::Vector3::::new(1., 0., 0.); + + let mut count = 0; + for tri in self.triangles() { + if tri.ray_intersection(&point, &ray) { + count = count + 1; + } + } + + count % 2 == 1 + } + pub fn scale(&mut self, x: f32, y: f32, z: f32) -> () { self.vertices = self.vertices .iter_mut() diff --git a/src/main.rs b/src/main.rs index 4666b92..92f84f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,63 +1,95 @@ #[cfg(feature = "arcwelder")] -use arcwelderlib_sys::arcwelder; +use arcwelderlib_sys; -use clap::Parser; +use clap::{Parser, Subcommand}; use slicey::{ geometry::STLMesh, settings::Settings, - slicer::Slicer + slicer::{ + DLPSlicer, + FFFSlicer, + Slicer + } }; -// CLIP parser +/// CLIP parser #[derive(Debug, Parser)] #[command(version, about, long_about = None)] -// #[command(name = "slicey", about = "A slicey CLI example", version)] struct CLIArgs { - /// Path to gcode to write file to - #[arg(short, long)] - gcode_file: String, - /// Paths to stl file/files - #[arg(short, long, value_delimiter = ',')] - stl_files: Vec, - /// Path to settings file - #[arg(long)] - settings_file: String, - /// Whether or not to use arcwelder. Requires cargo build --features arcwelder - #[arg(long)] - arcwelder: bool + #[command(subcommand)] + command: Option } -fn main() { - let args = CLIArgs::parse(); - println!("STL file = {:?}", args.stl_files); - let settings = Settings::new(&args.settings_file); - let mut stl_meshes: Vec = args.stl_files - .iter() - .map(|x| STLMesh::new(x.clone())) - .collect(); - - let z_offset = -1.0 * stl_meshes[0].bounding_box().z_min; - let _ = stl_meshes[0].translate(20.0,20.0,z_offset); - let _ = stl_meshes[0].scale(10.0, 10.0, 10.0); - // let _ = stl_meshes[0].translate(0.0,0.0,-&stl_meshes[0].bounding_box().z_min); - // println!("Min Z: {:?}",stl_meshes[0].bounding_box().z_min); - // println!("Max Z: {:?}",stl_meshes[0].bounding_box().z_max); - - // let bb = stl.bounding_box(); - - let slicer = Slicer::new(settings, stl_meshes); - let _ = slicer.slice(&args.gcode_file); - - // arcwelder stuff - let arcwelder_enabled = cfg!(feature = "arcwelder"); - if arcwelder_enabled { - println!("ArcWelder feature is enabled."); - } else { - println!("ArcWelder feature is NOT enabled."); +/// Subcommands +#[derive(Clone, Debug, Subcommand)] +enum Commands { + #[command(about = "DLP")] + DLP { + #[arg(short, long, value_delimiter = ',')] + stl_file: String, + #[arg(long)] + settings_file: String, + #[arg(short, long)] + image_folder: String + }, + #[command(about = "DIW/FFF/SLA")] + FFF { + /// Path to gcode to write file to + #[arg(short, long)] + gcode_file: String, + /// Paths to stl file/files + #[arg(short, long, value_delimiter = ',')] + stl_files: Vec, + /// Path to settings file + #[arg(long)] + settings_file: String, + /// Whether or not to use arcwelder. Requires cargo build --features arcwelder + #[arg(long)] + arcwelder: bool } +} + +fn main() { + let command = CLIArgs::parse(); + + match command.command { + Some(x) => match x { + Commands::DLP { stl_file, settings_file, image_folder } => { + let settings = Settings::new(&settings_file); + let stl_mesh = STLMesh::new(stl_file); + let slicer = DLPSlicer::new(settings, stl_mesh); + let _ = slicer.slice(&image_folder); + }, + Commands::FFF { gcode_file, stl_files, settings_file, arcwelder } => { + + println!("STL file = {:?}", stl_files); + let settings = Settings::new(&settings_file); + let mut stl_meshes: Vec = stl_files + .iter() + .map(|x| STLMesh::new(x.clone())) + .collect(); + + let z_offset = -1.0 * stl_meshes[0].bounding_box().z_min; + let _ = stl_meshes[0].translate(20.0,20.0,z_offset); + let _ = stl_meshes[0].scale(10.0, 10.0, 10.0); + + let slicer = FFFSlicer::new(settings, stl_meshes); + let _ = slicer.slice(&gcode_file); + + // arcwelder stuff + let arcwelder_enabled = cfg!(feature = "arcwelder"); + if arcwelder_enabled { + println!("ArcWelder feature is enabled."); + } else { + println!("ArcWelder feature is NOT enabled."); + } - #[cfg(feature = "arcwelder")] - if args.arcwelder { - arcwelder(&args.gcode_file, &args.gcode_file) + #[cfg(feature = "arcwelder")] + if arcwelder { + arcwelderlib_sys::arcwelder(&gcode_file, &gcode_file) + } + } + }, + None => panic!("Need a subcommand!") } } diff --git a/src/settings/mod.rs b/src/settings/mod.rs index d2cd899..b2f1794 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -50,34 +50,35 @@ pub struct SkirtSettings { } +#[derive(Clone, Debug, Deserialize)] +pub struct XYResolution { + pub x_pixels: i32, + pub y_pixels: i32, + pub x_pixel_resolution: f32, + pub y_pixel_resolution: f32 +} + #[derive(Clone, Debug, Deserialize)] pub struct Settings { - pub brim: BrimSettings, - pub infill: InfillSettings, + pub brim: Option, + pub infill: Option, pub layer_height: LayerHeightSettings, pub material: String, pub name: String, - pub perimeter: PerimeterSettings, - pub skirt: SkirtSettings + pub perimeter: Option, + pub skirt: Option, + pub xy_resolution: Option } impl Settings { pub fn new(file_name: &str) -> Self { let file = File::open(file_name).unwrap(); - // let mut data = String::new(); - // file.read_to_string(&mut data).unwrap(); let reader = BufReader::new(file); let json_settings: Settings = serde_json::from_reader(reader).unwrap(); json_settings } - // pub fn layer_heights(&self) -> Vec { - // let heights = vec![self.layer_height.layer_0_height]; - - // heights - // } - pub fn to_gcode_comment(&self) -> String { let s = format!("{}", self); s.lines() @@ -91,8 +92,11 @@ impl fmt::Display for Settings { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _ = write!(f, "{:?}\n", self.name); let _ = write!(f, "Material = {:?}\n", self.material); + let _ = write!(f, "{:#?}\n", self.brim); let _ = write!(f, "{:#?}\n", self.infill); let _ = write!(f, "{:#?}\n", self.layer_height); - write!(f, "{:#?}\n", self.perimeter) + let _ = write!(f, "{:#?}\n", self.perimeter); + let _ = write!(f, "{:#?}\n", self.skirt); + write!(f, "{:#?}\n", self.xy_resolution) } } \ No newline at end of file diff --git a/src/slicer/dlp_slicer.rs b/src/slicer/dlp_slicer.rs new file mode 100644 index 0000000..b43ca56 --- /dev/null +++ b/src/slicer/dlp_slicer.rs @@ -0,0 +1,84 @@ +use crate::geometry::STLMesh; +use crate::settings::Settings; +use crate::slicer::Slicer; +use nalgebra; + + +pub struct DLPSlicer { + settings: Settings, + stl_mesh: STLMesh +} + +impl DLPSlicer { + pub fn new( + settings: Settings, + stl_mesh: STLMesh + ) -> Self { + Self { + settings: settings, + stl_mesh: stl_mesh + } + } + + fn layer_image(&self, grid: &Vec::<[f32; 2]>, z: f32) { + // arbitrary ray in +x direction + // let ray_direction = nalgebra::Vector3::::new(1., 0., 0.); + // count number of triangles that intersect ray + // let mut count = 0; + // for tri in self.stl_mesh.triangles() { + // // let tri_pts = [ + // // vertices[tri.vertices[0]], + // // vertices[tri.vertices[1]], + // // vertices[tri.vertices[2]] + // // ] + // // println!("Tri = {:?}", tri); + // for point in grid { + + // } + // } + for point in grid { + + } + } + + fn planar_grid(&self) -> Vec::<[f32; 2]> { + let settings = self.settings.xy_resolution.clone().unwrap(); + let n_x = settings.x_pixels.try_into().unwrap(); + let n_y = settings.y_pixels.try_into().unwrap(); + let d_x = settings.x_pixel_resolution; + let d_y = settings.y_pixel_resolution; + let mut grid = Vec::<[f32; 2]>::new(); + + for i in 0..n_x { + for j in 0..n_y { + let x = (i as f32 + 0.5) * d_x; + let y = (j as f32 + 0.5) * d_y; + let p = [x, y]; + grid.push(p); + } + } + grid + } +} + +impl Slicer for DLPSlicer { + fn slice(&self, image_folder: &str) { + println!("Slicing stl file {:?}", self.stl_mesh.file_name()); + println!("{}", self.settings); + println!("Generating layer heights"); + let zs = self.layer_heights(&self.settings, &self.stl_mesh); + println!("Total number of layers = {}", zs.len()); + println!("Generating planar grid"); + let grid = self.planar_grid(); + println!("Total voxels in planar grid = {}", grid.len()); + println!("Total number of STL triangles = {}", self.stl_mesh.triangles().len()); + println!("Total number of voxel queries = {} million", zs.len() * grid.len() * self.stl_mesh.triangles().len() / 1000000); + + // loop over layers + for (n, z) in zs.into_iter().enumerate() { + println!("Generating slice for layer {}", n); + // let grid = self.planar_grid(z); + let _ = self.layer_image(&grid, z); + } + } +} diff --git a/src/slicer/fff_slicer.rs b/src/slicer/fff_slicer.rs new file mode 100644 index 0000000..3460d3f --- /dev/null +++ b/src/slicer/fff_slicer.rs @@ -0,0 +1,386 @@ +use crate::gcode::GcodeWriter; +use crate::geometry::{Edges, STLMesh}; +use crate::settings::{FloatOrVecOfFloats, Settings}; +use crate::slicer::Slicer; + +pub struct FFFSlicer { + settings: Settings, + stl_meshes: Vec +} + +impl FFFSlicer { + pub fn new( + settings: Settings, + stl_meshes: Vec + ) -> Self { + Self { + settings: settings, + stl_meshes: stl_meshes + } + } + + pub fn perimeters(&self, stl: &STLMesh, gcode_writer: &mut GcodeWriter, z: f32) -> () { + let tol = 1e-6 as f32; + let tris = stl.triangles(); + let perimeter_edges = Edges::new(); + + + // counter to track the index of the triangles that intersect with each z layer + let mut counter = 0; + // save the number of triangles per layer + let mut num_triangles = 0; + // temprorary vector that stores the intersecting triangle indices + let mut vec = vec![]; + // vector containing the intersection coordinates between the + // triangles and the plane z_height + let mut ab_coords = vec![]; + + // iterate through all the triangles in the stl + for tri in tris.iter() { + + // break down vertices for easier handling (for me) + let vert_1 = tri[0]; + let vert_2 = tri[1]; + let vert_3 = tri[2]; + + + + // if any of the vertices are above AND any are below, + if vert_1[2] < z || vert_2[2] < z || vert_3[2] < z { + if vert_1[2] > z || vert_2[2] > z || vert_3[2] > z { + + // append the index of intersecting triangles + vec.push(counter); + + // Must sort the vertices to be able to calculate + // the line segment created by the interception of + // the z_height and the triangles that bound it. + // Assign signed integers to represent whether a + // vertex of a triangle is above (+1), below (-1), + // or on (0) the z_height plane. + // + // add all z vertices to vector + let z_vec = [vert_1[2], vert_2[2], vert_3[2]]; + // empty vector for sorting + let mut z_sort = vec![]; + for zS in z_vec { + if zS - z < 0.0 { + z_sort.push(-1); + } else if zS - z > 0.0 { + z_sort.push(1); + } else { + z_sort.push(0); + } + } + + // Test the relationship of the first two vertices, + // with the goal of arranging the vertices to be + // able to calculate the z intercept line segment. + // There are three triangles possible: two vertices + // below z_height and one above, two vertices above + // z_height and one below, and one vertex coincident + // with z_height and one above and one below. In + // the first two cases, the single vertex will go into + // the first index (0). In the last case, either + // vertex not coincident with z_height will go into + // the first index (0). + // + // Test the first two vertices by adding them. + // There are five possible values for sum_z0_z1: + // 1) -2 the first and second vertices are below z_height + // 2) -1 the first and second vertices are coincident and below z_height + // 3) 0 the first and second vertices are below and above z_height + // 4) +1 the first and second vertices are coincident and above z_height + // 5) +2 the first and second vertices are above z_height + // For cases 2, 3, and 4, further logic is required to + // deal with which vertex is coincident or if the third + // vertex is above or below z_height. + let sum_z0_z1 = z_sort[0] + z_sort[1]; + let sum_z0_z2 = z_sort[0] + z_sort[2]; + + // empty vector to store the properly sorted vertices + let mut sorted_z = vec![]; + + // are the first and second vertex on the same side? + if sum_z0_z1 > 1 || sum_z0_z1 < -1 { + // then the third vertex goes into index 0 + sorted_z = vec![2, 0, 1]; + + // is the first or second vertex coincident with z_height? + } else if sum_z0_z1 > 0 || sum_z0_z1 < 0 { + // is the first vertex coincident with z_height? + if z_sort[0] == 0 { + // any other vertex goes in index 0 + sorted_z = vec![1, 0, 2]; + + // then the second vertex is coincident with z_height + } else { + // original order acceptable + sorted_z = vec![0, 1, 2]; + } + + // are the first and second vertices on opposite sides? + } else if sum_z0_z1 == 0 { + // is the third vertex coincident with z_height? + if z_sort[2] == 0 { + // order is fine + sorted_z = vec![0, 1, 2]; + + // is the third element the same as the first? + } else if sum_z0_z2 > 0 || sum_z0_z2 < 0 { + // the second element goes into index 0 + sorted_z = vec![1, 0, 2]; + // the first element is different than the second and third + } else { + // the order is acceptable + sorted_z = vec![0, 1, 2]; + } + + } + + + // The z_height intersection will be composed of two points, + // a and b. Calculate intersection points (xa, ya) and + // (xb, yb), and push them to a vector ab_coords. + let z_factor_a = (z - tri[sorted_z[0]][2])/(tri[sorted_z[1]][2] - tri[sorted_z[0]][2]); + let z_factor_b = (z - tri[sorted_z[0]][2])/(tri[sorted_z[2]][2] - tri[sorted_z[0]][2]); + let xa = (z_factor_a * (tri[sorted_z[1]][0] - tri[sorted_z[0]][0])) + tri[sorted_z[0]][0]; + let ya = (z_factor_a * (tri[sorted_z[1]][1] - tri[sorted_z[0]][1])) + tri[sorted_z[0]][1]; + let xb = (z_factor_b * (tri[sorted_z[2]][0] - tri[sorted_z[0]][0])) + tri[sorted_z[0]][0]; + let yb = (z_factor_b * (tri[sorted_z[2]][1] - tri[sorted_z[0]][1])) + tri[sorted_z[0]][1]; + + let a_coord = [xa, ya]; + let b_coord = [xb, yb]; + ab_coords.push([a_coord, b_coord]); + } + } + // increase counter for next triangle + counter += 1; + + } // end looping through all triangles for a layer at z_height + + println!("Global Layer height; {:?}", z); + println!("Number of triangles in Layer: {:?}", vec.len()); + // println!("Sample AB-Coords: {:?}",ab_coords[0]); + // println!("Going Crazy: {:?}", ab_coords); + // Testing to sort the perimeter lines (ab) + let TEST = 1; + // if TEST == 1 { //16.5 { + + // println!("All AB-Coords: {:?}", ab_coords); + + // perimeter array of the sorted line segments + let mut perimeter_sort = Vec::new(); + // Clone the vector of all line segments, we + // will be removing all used segments + let mut AB_COORDS = Vec::from(ab_coords.clone()); + // push the first line segment to the sorted + // list, the exact place to start will need + // to be adjusted in the future. + if AB_COORDS.len() > 0 { + perimeter_sort.push(AB_COORDS[0]); + // remove the first line segment + AB_COORDS.swap_remove(0); + // create a counter variable + let mut num_available_segs = AB_COORDS.len(); + + // cycle through all line segments in AB_COORDS; if x_a and y_a of + // a line segment matches the x_b and y_b of the last segment in + // perimeter_sort, then push that line segment to perimeter_sort. + // + // TODO This doesn't delete the used segment. + + // Search radius for next line segment. + let mut search_radius = 1.0e-3; + + // while the number of available line segments is + // above some number, keep building the connected + // lines + while num_available_segs > 0 { + // index counter, for the triangle in AB_COORDS + let mut index_count = 0; + // vector to save the matcing indices. This + // needs to be a vector because there is a + // chance the search radius is too big and + // finds more than one viable coordinate. + let mut index_match = vec![]; + // does the order of the vector of coordinats + // need to switched to properly follow the + // perimeter + let mut vertex_switch = vec![]; + // for each line segment in the layer, + // compare the beginning (a) of a segment + // to the end of thelast line segment (b) + // in perimeter_sort vector. + // + // TODO THIS IS WRONG, it needs to look at + // both points in each line segment, since + // the order of a and b is arbitrary. Oof. + for segs in AB_COORDS.iter() { + // println!("Segments: {:?}", AB_COORDS); + // println!("Last Segment: {:?}",perimeter_sort.last().expect("Perimeter is empty")); + + // find the distance between the last coordinate in perimeter_sort + // and the current line segment, vertex a + let mut x_diff_a = (segs[0][0] - perimeter_sort.last().expect("Perimeter is empty")[1][0]).abs(); + let mut y_diff_a = (segs[0][1] - perimeter_sort.last().expect("Perimeter is empty")[1][1]).abs(); + // ... same as above, but with vertex b + let mut x_diff_b = (segs[1][0] - perimeter_sort.last().expect("Perimeter is empty")[1][0]).abs(); + let mut y_diff_b = (segs[1][1] - perimeter_sort.last().expect("Perimeter is empty")[1][1]).abs(); + + // square the difference values + let x_diff_a_square = x_diff_a.powi(2); + let y_diff_a_square = y_diff_a.powi(2); + let x_diff_b_square = x_diff_b.powi(2); + let y_diff_b_square = y_diff_b.powi(2); + // sum the square differences + let diff_a_square_sum = x_diff_a_square + y_diff_a_square; + let diff_b_square_sum = x_diff_b_square + y_diff_b_square; + // calculate the euclidean distance between + // the a and b verices and the endpoint of + // the perimeter line vector. + let euclidean_a = diff_a_square_sum.sqrt(); + let euclidean_b = diff_b_square_sum.sqrt(); + + // let mut x_diff_bits: u64 = x_diff.to_bits(); + // let mut y_diff_bits: u64 = y_diff.to_bits(); + // x_diff_bits = x_diff_bits * x_diff_bits as u64; + // y_diff_bits = y_diff_bits * y_diff_bits as u64; + // let diff_square_sum = x_diff_bits + y_diff_bits; + // let mut euclidean = f32::from_bits(diff_square_sum); + + // Only use the closer of the two euclidean values + // + // TODO Need to decide which euclidean is smaller + // and pass those values to the search. + + // check which vertex is closer to the last point + // in the perimeter. Assign the respective + // euclidean value and a 0 or 1 for which is + // closer (this is for rearranging outside this + // loop). + let mut which_vertex: f32; + let mut smaller_euclidean: f32; + if euclidean_a > euclidean_b { + smaller_euclidean = euclidean_b; + which_vertex = 1.0; + } else { + smaller_euclidean = euclidean_a; + which_vertex = 0.0; + } + + + // let smaller_euclidean = euclidean_a; + let euclidean = smaller_euclidean; + // println!("euclidean A: {:?}",euclidean_a); + // println!("euclidean B: {:?}",euclidean_b); + // println!("euclidean : {:?}", euclidean); + // println!("Search Radius: {:?}", search_radius); + if euclidean <= search_radius { + // if y_diff <= search_radius { + // println!("--------------------------------------"); + // println!("Match Found"); + // println!("euclindean A: {:?}",euclidean_b); + // println!("euclindean B: {:?}",euclidean_b); + // println!("euclindean B: {:?}",euclidean_b); + // println!("Segment: {:?}", segs); + // println!("Segment X: {:?}, Segment Y: {:?}", segs[0][0], segs[0][1]); + // println!("Last Perimeter Segment: {:?}", perimeter_sort.last().expect("Perimeter is empty")[1]); + // println!("Searched X: {:?}, Searched Y: {:?}", perimeter_sort.last().expect("Perimeter is empty")[1][0], perimeter_sort.last().expect("Perimeter is empty")[1][1]); + // println!("X diff: {:?}",x_diff); + // println!("Y diff: {:?}",y_diff); + // println!("Euclidean: {:?}",euclidean); + // println!("Search Radius: {:?}", search_radius); + // perimeter_sort.push(*segs); + index_match.push(index_count); + vertex_switch.push(which_vertex); + // } + } + index_count += 1; + } + + // The search radius may result in one, more than one, or + // no matches found. If one is found, we consider it a true match + // and pass it through to the ordered perimeter_sort vector. If + // more than one is found, shrink the search radius, and repeat + // the search. If none are found, grow the search radus, and + // repeat the search. (Hopefully). + // + // println!("NUM INDEXES: {:?}",index_match.len()); + // println!("INDEXES: {:?}",index_match); + // println!("----------------------------------"); + if index_match.len() == 1 { + perimeter_sort.push(AB_COORDS[index_match[0]]); + // println!("Size of index match: {:?}",index_match.len()); + // AB_COORDS.swap_remove(index_count); + // println!("Test index match: {:?}", index_match); + // println!("Size of AB_COUNT: {:?}",AB_COORDS.len()); + // println!("Sorted Segments: {:?}",perimeter_sort); + num_available_segs -= 1; + let REMOVE = *index_match.last().expect("Nothing in the perimeter"); + AB_COORDS.swap_remove(REMOVE); + // println!("Size of AB_COUNT: {:?}",AB_COORDS.len()); + search_radius = 1e-3; + gcode_writer.write_perimeter(perimeter_sort.last().unwrap()[1][0],perimeter_sort.last().unwrap()[1][1],z,555.0,1200.0); + } else if index_match.len() > 1 { + search_radius = search_radius * 0.9; + } else { + search_radius = search_radius * 1.1; + } + + } + // println!("Sorted Segments: {:?}",perimeter_sort); + + println!("----------------------------------"); + + + + } + + // Sort the ab_coords per layer to produce the perimeter + // ouline paths. + // Must remove used coord pairs from available list in + // order to check if multiple print "islands" occure per + // layer. + + + } +} + +impl Slicer for FFFSlicer { + fn slice(&self, gcode_file: &str) { + let mut gcode_writer = GcodeWriter::new(gcode_file); + gcode_writer.write_header(&self.settings); + + for stl in &self.stl_meshes { + println!("Slicing stl file {:?}", stl.file_name()); + println!("{}", self.settings); + println!("Generating layer heights"); + let zs = self.layer_heights(&self.settings, &stl); + let mut z_height: f32 = 0.0; + // TODO must shift the stl up in the z direction, no negatives + // let z_offset = -1.0 * -stl.bounding_box().z_min; + // let _ = stl.translate(0.0,0.0,z_offset); + for (n, z) in zs.iter().enumerate() { + + let positioning = 0; + + if positioning == 0 { + z_height = z_height + *z; + } else { + z_height = *z; + } + + // height of print plane + gcode_writer.write_layer_change( + n.try_into().unwrap(), z_height, 'F', 1200. // TODO + ); + // TODO skirt/brim + println!("Generating perimeters for layer {}", n); + println!("Layer heigh: {:?}",*z); + let _ = self.perimeters(&stl, &mut gcode_writer, z_height); + // TODO infill + } + } + } +} diff --git a/src/slicer/mod.rs b/src/slicer/mod.rs index 7e2a9f8..5d35d16 100644 --- a/src/slicer/mod.rs +++ b/src/slicer/mod.rs @@ -1,35 +1,26 @@ -use crate::gcode::GcodeWriter; -use crate::geometry::{Edges, STLMesh}; -use crate::settings::{FloatOrVecOfFloats, Settings}; +pub mod dlp_slicer; +pub mod fff_slicer; -pub struct Slicer { - settings: Settings, - stl_meshes: Vec -} +pub use dlp_slicer::DLPSlicer; +pub use fff_slicer::FFFSlicer; -impl Slicer { - pub fn new( - settings: Settings, - stl_meshes: Vec - ) -> Self { - Self { - settings: settings, - stl_meshes: stl_meshes - } - } +use crate::geometry::STLMesh; +use crate::settings::{FloatOrVecOfFloats, Settings}; - pub fn layer_heights(&self, stl: &STLMesh) -> Vec { +/// trait for shared behavior between slicers +pub trait Slicer { + fn layer_heights(&self, settings: &Settings, stl: &STLMesh) -> Vec { let bb = stl.bounding_box(); - let mut heights = vec![self.settings.layer_height.layer_0_height]; + let mut heights = vec![settings.layer_height.layer_0_height]; let total_height = bb.z_max - bb.z_min - heights[0]; - let layer_height = match &self.settings.layer_height.layer_n_height { + let layer_height = match &settings.layer_height.layer_n_height { FloatOrVecOfFloats::Float(x) => x, FloatOrVecOfFloats::VecOfFLoats(_x) => panic!("Got a list for layer n heights") }; - let n_layers = match &self.settings.layer_height.layer_n_height { + let n_layers = match &settings.layer_height.layer_n_height { FloatOrVecOfFloats::Float(x) => total_height / x, FloatOrVecOfFloats::VecOfFLoats(_x) => panic!("Got a list for layer n heights") }; @@ -43,366 +34,5 @@ impl Slicer { heights } - pub fn perimeters(&self, stl: &STLMesh, gcode_writer: &mut GcodeWriter, z: f32) -> () { - let tol = 1e-6 as f32; - let tris = stl.triangles(); - let perimeter_edges = Edges::new(); - - - // counter to track the index of the triangles that intersect with each z layer - let mut counter = 0; - // save the number of triangles per layer - let mut num_triangles = 0; - // temprorary vector that stores the intersecting triangle indices - let mut vec = vec![]; - // vector containing the intersection coordinates between the - // triangles and the plane z_height - let mut ab_coords = vec![]; - - // iterate through all the triangles in the stl - for tri in tris.iter() { - - // break down vertices for easier handling (for me) - let vert_1 = tri[0]; - let vert_2 = tri[1]; - let vert_3 = tri[2]; - - - - // if any of the vertices are above AND any are below, - if vert_1[2] < z || vert_2[2] < z || vert_3[2] < z { - if vert_1[2] > z || vert_2[2] > z || vert_3[2] > z { - - // append the index of intersecting triangles - vec.push(counter); - - // Must sort the vertices to be able to calculate - // the line segment created by the interception of - // the z_height and the triangles that bound it. - // Assign signed integers to represent whether a - // vertex of a triangle is above (+1), below (-1), - // or on (0) the z_height plane. - // - // add all z vertices to vector - let z_vec = [vert_1[2], vert_2[2], vert_3[2]]; - // empty vector for sorting - let mut z_sort = vec![]; - for zS in z_vec { - if zS - z < 0.0 { - z_sort.push(-1); - } else if zS - z > 0.0 { - z_sort.push(1); - } else { - z_sort.push(0); - } - } - - // Test the relationship of the first two vertices, - // with the goal of arranging the vertices to be - // able to calculate the z intercept line segment. - // There are three triangles possible: two vertices - // below z_height and one above, two vertices above - // z_height and one below, and one vertex coincident - // with z_height and one above and one below. In - // the first two cases, the single vertex will go into - // the first index (0). In the last case, either - // vertex not coincident with z_height will go into - // the first index (0). - // - // Test the first two vertices by adding them. - // There are five possible values for sum_z0_z1: - // 1) -2 the first and second vertices are below z_height - // 2) -1 the first and second vertices are coincident and below z_height - // 3) 0 the first and second vertices are below and above z_height - // 4) +1 the first and second vertices are coincident and above z_height - // 5) +2 the first and second vertices are above z_height - // For cases 2, 3, and 4, further logic is required to - // deal with which vertex is coincident or if the third - // vertex is above or below z_height. - let sum_z0_z1 = z_sort[0] + z_sort[1]; - let sum_z0_z2 = z_sort[0] + z_sort[2]; - - // empty vector to store the properly sorted vertices - let mut sorted_z = vec![]; - - // are the first and second vertex on the same side? - if sum_z0_z1 > 1 || sum_z0_z1 < -1 { - // then the third vertex goes into index 0 - sorted_z = vec![2, 0, 1]; - - // is the first or second vertex coincident with z_height? - } else if sum_z0_z1 > 0 || sum_z0_z1 < 0 { - // is the first vertex coincident with z_height? - if z_sort[0] == 0 { - // any other vertex goes in index 0 - sorted_z = vec![1, 0, 2]; - - // then the second vertex is coincident with z_height - } else { - // original order acceptable - sorted_z = vec![0, 1, 2]; - } - - // are the first and second vertices on opposite sides? - } else if sum_z0_z1 == 0 { - // is the third vertex coincident with z_height? - if z_sort[2] == 0 { - // order is fine - sorted_z = vec![0, 1, 2]; - - // is the third element the same as the first? - } else if sum_z0_z2 > 0 || sum_z0_z2 < 0 { - // the second element goes into index 0 - sorted_z = vec![1, 0, 2]; - // the first element is different than the second and third - } else { - // the order is acceptable - sorted_z = vec![0, 1, 2]; - } - - } - - - // The z_height intersection will be composed of two points, - // a and b. Calculate intersection points (xa, ya) and - // (xb, yb), and push them to a vector ab_coords. - let z_factor_a = (z - tri[sorted_z[0]][2])/(tri[sorted_z[1]][2] - tri[sorted_z[0]][2]); - let z_factor_b = (z - tri[sorted_z[0]][2])/(tri[sorted_z[2]][2] - tri[sorted_z[0]][2]); - let xa = (z_factor_a * (tri[sorted_z[1]][0] - tri[sorted_z[0]][0])) + tri[sorted_z[0]][0]; - let ya = (z_factor_a * (tri[sorted_z[1]][1] - tri[sorted_z[0]][1])) + tri[sorted_z[0]][1]; - let xb = (z_factor_b * (tri[sorted_z[2]][0] - tri[sorted_z[0]][0])) + tri[sorted_z[0]][0]; - let yb = (z_factor_b * (tri[sorted_z[2]][1] - tri[sorted_z[0]][1])) + tri[sorted_z[0]][1]; - - let a_coord = [xa, ya]; - let b_coord = [xb, yb]; - ab_coords.push([a_coord, b_coord]); - } - } - // increase counter for next triangle - counter += 1; - - } // end looping through all triangles for a layer at z_height - - println!("Global Layer height; {:?}", z); - println!("Number of triangles in Layer: {:?}", vec.len()); - // println!("Sample AB-Coords: {:?}",ab_coords[0]); - // println!("Going Crazy: {:?}", ab_coords); - // Testing to sort the perimeter lines (ab) - let TEST = 1; - // if TEST == 1 { //16.5 { - - // println!("All AB-Coords: {:?}", ab_coords); - - // perimeter array of the sorted line segments - let mut perimeter_sort = Vec::new(); - // Clone the vector of all line segments, we - // will be removing all used segments - let mut AB_COORDS = Vec::from(ab_coords.clone()); - // push the first line segment to the sorted - // list, the exact place to start will need - // to be adjusted in the future. - if AB_COORDS.len() > 0 { - perimeter_sort.push(AB_COORDS[0]); - // remove the first line segment - AB_COORDS.swap_remove(0); - // create a counter variable - let mut num_available_segs = AB_COORDS.len(); - - // cycle through all line segments in AB_COORDS; if x_a and y_a of - // a line segment matches the x_b and y_b of the last segment in - // perimeter_sort, then push that line segment to perimeter_sort. - // - // TODO This doesn't delete the used segment. - - // Search radius for next line segment. - let mut search_radius = 1.0e-3; - - // while the number of available line segments is - // above some number, keep building the connected - // lines - while num_available_segs > 0 { - // index counter, for the triangle in AB_COORDS - let mut index_count = 0; - // vector to save the matcing indices. This - // needs to be a vector because there is a - // chance the search radius is too big and - // finds more than one viable coordinate. - let mut index_match = vec![]; - // does the order of the vector of coordinats - // need to switched to properly follow the - // perimeter - let mut vertex_switch = vec![]; - // for each line segment in the layer, - // compare the beginning (a) of a segment - // to the end of thelast line segment (b) - // in perimeter_sort vector. - // - // TODO THIS IS WRONG, it needs to look at - // both points in each line segment, since - // the order of a and b is arbitrary. Oof. - for segs in AB_COORDS.iter() { - // println!("Segments: {:?}", AB_COORDS); - // println!("Last Segment: {:?}",perimeter_sort.last().expect("Perimeter is empty")); - - // find the distance between the last coordinate in perimeter_sort - // and the current line segment, vertex a - let mut x_diff_a = (segs[0][0] - perimeter_sort.last().expect("Perimeter is empty")[1][0]).abs(); - let mut y_diff_a = (segs[0][1] - perimeter_sort.last().expect("Perimeter is empty")[1][1]).abs(); - // ... same as above, but with vertex b - let mut x_diff_b = (segs[1][0] - perimeter_sort.last().expect("Perimeter is empty")[1][0]).abs(); - let mut y_diff_b = (segs[1][1] - perimeter_sort.last().expect("Perimeter is empty")[1][1]).abs(); - - // square the difference values - let x_diff_a_square = x_diff_a.powi(2); - let y_diff_a_square = y_diff_a.powi(2); - let x_diff_b_square = x_diff_b.powi(2); - let y_diff_b_square = y_diff_b.powi(2); - // sum the square differences - let diff_a_square_sum = x_diff_a_square + y_diff_a_square; - let diff_b_square_sum = x_diff_b_square + y_diff_b_square; - // calculate the euclidean distance between - // the a and b verices and the endpoint of - // the perimeter line vector. - let euclidean_a = diff_a_square_sum.sqrt(); - let euclidean_b = diff_b_square_sum.sqrt(); - - // let mut x_diff_bits: u64 = x_diff.to_bits(); - // let mut y_diff_bits: u64 = y_diff.to_bits(); - // x_diff_bits = x_diff_bits * x_diff_bits as u64; - // y_diff_bits = y_diff_bits * y_diff_bits as u64; - // let diff_square_sum = x_diff_bits + y_diff_bits; - // let mut euclidean = f32::from_bits(diff_square_sum); - - // Only use the closer of the two euclidean values - // - // TODO Need to decide which euclidean is smaller - // and pass those values to the search. - - // check which vertex is closer to the last point - // in the perimeter. Assign the respective - // euclidean value and a 0 or 1 for which is - // closer (this is for rearranging outside this - // loop). - let mut which_vertex: f32; - let mut smaller_euclidean: f32; - if euclidean_a > euclidean_b { - smaller_euclidean = euclidean_b; - which_vertex = 1.0; - } else { - smaller_euclidean = euclidean_a; - which_vertex = 0.0; - } - - - // let smaller_euclidean = euclidean_a; - let euclidean = smaller_euclidean; - // println!("euclidean A: {:?}",euclidean_a); - // println!("euclidean B: {:?}",euclidean_b); - // println!("euclidean : {:?}", euclidean); - // println!("Search Radius: {:?}", search_radius); - if euclidean <= search_radius { - // if y_diff <= search_radius { - // println!("--------------------------------------"); - // println!("Match Found"); - // println!("euclindean A: {:?}",euclidean_b); - // println!("euclindean B: {:?}",euclidean_b); - // println!("euclindean B: {:?}",euclidean_b); - // println!("Segment: {:?}", segs); - // println!("Segment X: {:?}, Segment Y: {:?}", segs[0][0], segs[0][1]); - // println!("Last Perimeter Segment: {:?}", perimeter_sort.last().expect("Perimeter is empty")[1]); - // println!("Searched X: {:?}, Searched Y: {:?}", perimeter_sort.last().expect("Perimeter is empty")[1][0], perimeter_sort.last().expect("Perimeter is empty")[1][1]); - // println!("X diff: {:?}",x_diff); - // println!("Y diff: {:?}",y_diff); - // println!("Euclidean: {:?}",euclidean); - // println!("Search Radius: {:?}", search_radius); - // perimeter_sort.push(*segs); - index_match.push(index_count); - vertex_switch.push(which_vertex); - // } - } - index_count += 1; - } - - // The search radius may result in one, more than one, or - // no matches found. If one is found, we consider it a true match - // and pass it through to the ordered perimeter_sort vector. If - // more than one is found, shrink the search radius, and repeat - // the search. If none are found, grow the search radus, and - // repeat the search. (Hopefully). - // - // println!("NUM INDEXES: {:?}",index_match.len()); - // println!("INDEXES: {:?}",index_match); - // println!("----------------------------------"); - if index_match.len() == 1 { - perimeter_sort.push(AB_COORDS[index_match[0]]); - // println!("Size of index match: {:?}",index_match.len()); - // AB_COORDS.swap_remove(index_count); - // println!("Test index match: {:?}", index_match); - // println!("Size of AB_COUNT: {:?}",AB_COORDS.len()); - // println!("Sorted Segments: {:?}",perimeter_sort); - num_available_segs -= 1; - let REMOVE = *index_match.last().expect("Nothing in the perimeter"); - AB_COORDS.swap_remove(REMOVE); - // println!("Size of AB_COUNT: {:?}",AB_COORDS.len()); - search_radius = 1e-3; - gcode_writer.write_perimeter(perimeter_sort.last().unwrap()[1][0],perimeter_sort.last().unwrap()[1][1],z,555.0,1200.0); - } else if index_match.len() > 1 { - search_radius = search_radius * 0.9; - } else { - search_radius = search_radius * 1.1; - } - - } - // println!("Sorted Segments: {:?}",perimeter_sort); - - println!("----------------------------------"); - - - - } - - // Sort the ab_coords per layer to produce the perimeter - // ouline paths. - // Must remove used coord pairs from available list in - // order to check if multiple print "islands" occure per - // layer. - - - } - - pub fn slice(&self, gcode_file: &str) -> () { - let mut gcode_writer = GcodeWriter::new(gcode_file); - gcode_writer.write_header(&self.settings); - - for stl in &self.stl_meshes { - println!("Slicing stl file {:?}", stl.file_name()); - println!("{}", self.settings); - println!("Generating layer heights"); - let zs = self.layer_heights(&stl); - let mut z_height: f32 = 0.0; - // TODO must shift the stl up in the z direction, no negatives - // let z_offset = -1.0 * -stl.bounding_box().z_min; - // let _ = stl.translate(0.0,0.0,z_offset); - for (n, z) in zs.iter().enumerate() { - - let positioning = 0; - - if positioning == 0 { - z_height = z_height + *z; - } else { - z_height = *z; - } - - // height of print plane - gcode_writer.write_layer_change( - n.try_into().unwrap(), z_height, 'F', 1200. // TODO - ); - // TODO skirt/brim - println!("Generating perimeters for layer {}", n); - println!("Layer heigh: {:?}",*z); - let _ = self.perimeters(&stl, &mut gcode_writer, z_height); - // TODO infill - } - } - } + fn slice(&self, some_file: &str); } diff --git a/test/example/dlp_settings.json b/test/example/dlp_settings.json new file mode 100644 index 0000000..636d312 --- /dev/null +++ b/test/example/dlp_settings.json @@ -0,0 +1,15 @@ +{ + "name": "some name", + "material": "PEGDA250", + + "xy_resolution": { + "x_pixels": 1024, + "y_pixels": 768, + "x_pixel_resolution": 0.05, + "y_pixel_resolution": 0.05 + }, + "layer_height": { + "layer_0_height": 0.1, + "layer_n_height": 0.2 + } +} \ No newline at end of file diff --git a/test/example/settings.json b/test/example/fff_settings.json similarity index 100% rename from test/example/settings.json rename to test/example/fff_settings.json diff --git a/test/example/settings.toml b/test/example/settings.toml deleted file mode 100644 index 19f4379..0000000 --- a/test/example/settings.toml +++ /dev/null @@ -1 +0,0 @@ -layer_height = "0.1" \ No newline at end of file diff --git a/test/example/settings.yaml b/test/example/settings.yaml deleted file mode 100644 index 35ce2ee..0000000 --- a/test/example/settings.yaml +++ /dev/null @@ -1 +0,0 @@ -layer height: 0.1