Files
mslicer/slicer/bin/args.rs
2025-04-04 21:26:42 -04:00

212 lines
6.9 KiB
Rust

use std::{any::Any, path::PathBuf, str::FromStr};
use anyhow::{Context, Ok, Result};
use clap::{ArgMatches, 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,
#[arg(long)]
/// Path to a preview image, will be scaled as needed.
pub preview: Option<PathBuf>,
#[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 {
#[arg(long)]
/// Path to a .stl or .obj file
pub mesh: Vec<PathBuf>,
#[arg(long, 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: Vec<Vector3<f32>>,
#[arg(long, value_parser = vector_value_parser::<f32, 3>)]
/// Rotation of the model in degrees, pitch, roll, yaw.
pub rotation: Vec<Vector3<f32>>,
#[arg(long, value_parser = vector_value_parser::<f32, 3>)]
/// Scale of the model along the X, Y, and Z axes.
pub scale: Vec<Vector3<f32>>,
}
#[derive(Debug)]
pub struct Model {
pub path: PathBuf,
pub position: Vector3<f32>,
pub rotation: Vector3<f32>,
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 mm_to_px(&self) -> Vector3<f32> {
Vector3::new(
self.platform_resolution.x as f32 / self.platform_size.x,
self.platform_resolution.y as f32 / self.platform_size.y,
1.0,
)
}
}
impl Model {
fn new(path: PathBuf) -> Self {
Self {
path,
..Default::default()
}
}
pub fn from_matches(matches: &ArgMatches) -> Vec<Self> {
let mut meshes = matches
.get_many::<PathBuf>("mesh")
.expect("No meshes defined")
.zip(matches.indices_of("mesh").unwrap())
.map(|x| (x.1, Model::new(x.0.to_owned())))
.collect::<Vec<_>>();
fn model_parameter<T: Any + Clone + Send + Sync + 'static>(
matches: &clap::ArgMatches,
meshes: &mut [(usize, Model)],
key: &str,
value: impl Fn(&mut Model) -> &mut T,
) {
let Some(instances) = matches.get_many::<T>(key) else {
return;
};
for (instance, idx) in instances.zip(matches.indices_of(key).unwrap()) {
let mesh = meshes
.iter_mut()
.rfind(|x| idx > x.0)
.expect("Mesh parameter before mesh");
*value(&mut mesh.1) = instance.to_owned();
}
}
model_parameter(matches, &mut meshes, "scale", |mesh| &mut mesh.scale);
model_parameter(matches, &mut meshes, "rotation", |mesh| &mut mesh.rotation);
model_parameter(matches, &mut meshes, "position", |mesh| &mut mesh.position);
meshes.into_iter().map(|x| x.1).collect()
}
}
impl Default for Model {
fn default() -> Self {
Self {
path: PathBuf::default(),
position: Vector3::zeros(),
rotation: Vector3::zeros(),
scale: Vector3::repeat(1.0),
}
}
}
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)
}