Start on slicer CLI
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
/*output
|
||||
*.goo
|
||||
*.stl
|
||||
codebook.toml
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4491,11 +4491,13 @@ name = "slicer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"common",
|
||||
"criterion",
|
||||
"goo_format",
|
||||
"image 0.25.1",
|
||||
"nalgebra 0.32.6",
|
||||
"num-traits",
|
||||
"obj-rs",
|
||||
"ordered-float",
|
||||
"parking_lot",
|
||||
|
@@ -29,6 +29,7 @@ markdown = "0.3.0"
|
||||
md5 = "0.7.0"
|
||||
nalgebra = { version = "0.32.6", features = ["serde-serialize"] }
|
||||
notify-rust = "4.11.0"
|
||||
num-traits = "0.2.19"
|
||||
obj-rs = "0.7.1"
|
||||
open = "5.3.0"
|
||||
ordered-float = "4.2.0"
|
||||
|
@@ -5,8 +5,10 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
image.workspace = true
|
||||
nalgebra.workspace = true
|
||||
num-traits.workspace = true
|
||||
obj-rs.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
136
slicer/src/args.rs
Normal file
136
slicer/src/args.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use anyhow::{Context, Ok, Result};
|
||||
use clap::Parser;
|
||||
use common::{
|
||||
config::{ExposureConfig, SliceConfig},
|
||||
format::Format,
|
||||
};
|
||||
use nalgebra::{ArrayStorage, Const, Matrix, Scalar, Vector2, Vector3, U1};
|
||||
use num_traits::Zero;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
/// mslicer command line interface.
|
||||
pub struct Args {
|
||||
#[arg(long, default_value = "11520, 5120", value_parser = vector_value_parser::<u32, 2>, )]
|
||||
/// Resolution of the printer mask display in pixels.
|
||||
pub platform_resolution: Vector2<u32>,
|
||||
#[arg(long, default_value = "218.88, 122.904, 260.0", value_parser = vector_value_parser::<f32, 3>)]
|
||||
/// Size of the printer display / platform in mm.
|
||||
pub platform_size: Vector3<f32>,
|
||||
#[arg(long, default_value_t = 0.05)]
|
||||
/// Layer height in mm.
|
||||
pub layer_height: f32,
|
||||
#[arg(long, default_value_t = 3)]
|
||||
/// Number of 'first layers'. These are layers that obey the --first-
|
||||
/// exposure config flags.
|
||||
pub first_layers: u32,
|
||||
#[arg(long, default_value_t = 10)]
|
||||
/// Number of transition layers. These are layers that interpolate from the
|
||||
/// first layer config to the default config.
|
||||
pub transition_layers: u32,
|
||||
|
||||
#[arg(long, default_value_t = 3.0)]
|
||||
/// Layer exposure time in seconds.
|
||||
pub exposure_time: f32,
|
||||
#[arg(long, default_value_t = 5.0)]
|
||||
/// Distance to lift the platform after exposing each regular layer, in mm.
|
||||
pub lift_distance: f32,
|
||||
#[arg(long, default_value_t = 65.0)]
|
||||
/// The speed to lift the platform after exposing each regular layer, in
|
||||
/// mm/min.
|
||||
pub lift_speed: f32,
|
||||
#[arg(long, default_value_t = 150.0)]
|
||||
/// The speed to retract (move down) the platform after exposing each
|
||||
/// regular layer, in mm/min.
|
||||
pub retract_speed: f32,
|
||||
|
||||
#[arg(long, default_value_t = 30.0)]
|
||||
/// First layer exposure time in seconds.
|
||||
pub first_exposure_time: f32,
|
||||
#[arg(long, default_value_t = 5.0)]
|
||||
/// Distance to lift the platform after exposing each first layer, in mm.
|
||||
pub first_lift_distance: f32,
|
||||
#[arg(long, default_value_t = 65.0)]
|
||||
/// The speed to lift the platform after exposing each first layer, in
|
||||
/// mm/min.
|
||||
pub first_lift_speed: f32,
|
||||
#[arg(long, default_value_t = 150.0)]
|
||||
/// The speed to retract (move down) the platform after exposing each first
|
||||
/// layer, in mm/min.
|
||||
pub first_retract_speed: f32,
|
||||
|
||||
#[command(flatten)]
|
||||
pub model: ModelArgs,
|
||||
|
||||
/// File to save sliced result to. Currently only .goo files can be
|
||||
/// generated.
|
||||
pub output: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
#[group(required = true)]
|
||||
pub struct ModelArgs {
|
||||
/// Path to a .stl or .obj file
|
||||
pub mesh: PathBuf,
|
||||
|
||||
#[arg(long, default_value = "0, 0, 0", value_parser = vector_value_parser::<f32, 3>)]
|
||||
/// Location of the bottom center of model bounding box. The origin is the
|
||||
/// center of the build plate.
|
||||
pub position: Vector3<f32>,
|
||||
|
||||
#[arg(long, default_value = "0, 0, 0", value_parser = vector_value_parser::<f32, 3>)]
|
||||
/// Rotation of the model in degrees, pitch, roll, yaw.
|
||||
pub rotation: Vector3<f32>,
|
||||
|
||||
#[arg(long, default_value = "1, 1, 1", value_parser = vector_value_parser::<f32, 3>)]
|
||||
/// Scale of the model along the X, Y, and Z axes.
|
||||
pub scale: Vector3<f32>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn slice_config(&self) -> SliceConfig {
|
||||
SliceConfig {
|
||||
format: Format::Goo,
|
||||
platform_resolution: self.platform_resolution,
|
||||
platform_size: self.platform_size,
|
||||
slice_height: self.layer_height,
|
||||
exposure_config: ExposureConfig {
|
||||
exposure_time: self.exposure_time,
|
||||
lift_distance: self.lift_distance,
|
||||
lift_speed: self.lift_speed,
|
||||
retract_distance: self.lift_distance,
|
||||
retract_speed: self.retract_speed,
|
||||
},
|
||||
first_exposure_config: ExposureConfig {
|
||||
exposure_time: self.first_exposure_time,
|
||||
lift_distance: self.first_lift_distance,
|
||||
lift_speed: self.first_lift_speed,
|
||||
retract_distance: self.first_lift_distance,
|
||||
retract_speed: self.first_retract_speed,
|
||||
},
|
||||
first_layers: self.first_layers,
|
||||
transition_layers: self.transition_layers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vector_value_parser<T, const N: usize>(
|
||||
raw: &str,
|
||||
) -> Result<Matrix<T, Const<N>, U1, ArrayStorage<T, N, 1>>>
|
||||
where
|
||||
T: FromStr + Scalar + Zero,
|
||||
T::Err: Send + Sync + std::error::Error,
|
||||
{
|
||||
let mut vec = Matrix::<T, Const<N>, U1, ArrayStorage<T, N, 1>>::zeros();
|
||||
|
||||
let mut parts = raw.splitn(N, ',');
|
||||
for i in 0..N {
|
||||
let element = parts.next().context("Missing vector element")?.trim();
|
||||
vec[i] = element
|
||||
.parse()
|
||||
.context("Can't convert element from string")?;
|
||||
}
|
||||
|
||||
Ok(vec)
|
||||
}
|
@@ -6,60 +6,46 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use nalgebra::{Vector2, Vector3};
|
||||
use args::Args;
|
||||
use clap::Parser;
|
||||
use nalgebra::Vector3;
|
||||
|
||||
use common::{
|
||||
config::{ExposureConfig, SliceConfig},
|
||||
format::Format,
|
||||
serde::DynamicSerializer,
|
||||
};
|
||||
use common::serde::DynamicSerializer;
|
||||
use goo_format::{File as GooFile, LayerEncoder};
|
||||
use slicer::{mesh::load_mesh, slicer::Slicer, Pos};
|
||||
use slicer::{mesh::load_mesh, slicer::Slicer};
|
||||
|
||||
mod args;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
const FILE_PATH: &str = "teapot.stl";
|
||||
const OUTPUT_PATH: &str = "output.goo";
|
||||
let args = Args::parse();
|
||||
let slice_config = args.slice_config();
|
||||
|
||||
let slice_config = SliceConfig {
|
||||
format: Format::Goo,
|
||||
let ext = args.model.mesh.extension().unwrap().to_string_lossy();
|
||||
let file = File::open(&args.model.mesh)?;
|
||||
|
||||
platform_resolution: Vector2::new(11_520, 5_120),
|
||||
platform_size: Vector3::new(218.88, 122.904, 260.0),
|
||||
slice_height: 0.05,
|
||||
|
||||
exposure_config: ExposureConfig {
|
||||
exposure_time: 3.0,
|
||||
..Default::default()
|
||||
},
|
||||
first_exposure_config: ExposureConfig {
|
||||
exposure_time: 50.0,
|
||||
..Default::default()
|
||||
},
|
||||
first_layers: 10,
|
||||
transition_layers: 10,
|
||||
};
|
||||
|
||||
let file = File::open(FILE_PATH)?;
|
||||
let mut buf = BufReader::new(file);
|
||||
let mut mesh = load_mesh(&mut buf, "stl")?;
|
||||
let (min, max) = mesh.bounds();
|
||||
let mut mesh = load_mesh(&mut buf, &ext)?;
|
||||
|
||||
// Scale the model into printer-space (mm => px)
|
||||
let real_scale = 1.0;
|
||||
mesh.set_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,
|
||||
));
|
||||
mesh.set_scale(args.model.scale.component_div(&Vector3::new(
|
||||
slice_config.platform_size.x * slice_config.platform_resolution.x as f32,
|
||||
slice_config.platform_size.y * slice_config.platform_resolution.y as f32,
|
||||
1.0,
|
||||
)));
|
||||
|
||||
mesh.set_rotation(args.model.rotation);
|
||||
|
||||
// Center the model
|
||||
let (min, max) = mesh.bounds();
|
||||
let center = slice_config.platform_resolution / 2;
|
||||
let mesh_center = (min + max) / 2.0;
|
||||
mesh.set_position(Vector3::new(
|
||||
center.x as f32 - mesh_center.x,
|
||||
center.y as f32 - mesh_center.y,
|
||||
mesh.position().z - 0.05,
|
||||
));
|
||||
mesh.set_position(
|
||||
Vector3::new(
|
||||
center.x as f32 - mesh_center.x,
|
||||
center.y as f32 - mesh_center.y,
|
||||
mesh.position().z - 0.05,
|
||||
) + args.model.position,
|
||||
);
|
||||
|
||||
println!(
|
||||
"Loaded mesh. {{ vert: {}, face: {} }}",
|
||||
@@ -90,7 +76,7 @@ fn main() -> Result<()> {
|
||||
// Once slicing is complete write to a .goo file
|
||||
let mut serializer = DynamicSerializer::new();
|
||||
goo.join().unwrap().serialize(&mut serializer);
|
||||
fs::write(OUTPUT_PATH, serializer.into_inner())?;
|
||||
fs::write(args.output, serializer.into_inner())?;
|
||||
|
||||
println!("\nDone. Elapsed: {:.1}s", now.elapsed().as_secs_f32());
|
||||
|
||||
|
Reference in New Issue
Block a user