Compare commits
9 Commits
bd5a1aaae0
...
fe08ef5e1f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fe08ef5e1f | ||
![]() |
478023da66 | ||
![]() |
5fc58dc767 | ||
![]() |
9daca647cc | ||
![]() |
bcd76a40ea | ||
![]() |
85c4eae0e1 | ||
![]() |
00d82313b7 | ||
![]() |
7a16dfb196 | ||
![]() |
ccc7424992 |
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Build
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -23,6 +23,9 @@ jobs:
|
||||
with:
|
||||
name: mslicer-${{ matrix.os }}
|
||||
path: |
|
||||
target/release/goo_format*
|
||||
target/release/mslicer*
|
||||
!target/release/mslicer.d
|
||||
!target/release/mslicer.pdb
|
||||
target/release/remote_send*
|
||||
target/release/slicer*
|
||||
!target/release/*.d
|
||||
!target/release/*.pdb
|
||||
|
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"
|
||||
|
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## v0.2.1 — Coming Soon?
|
||||
|
||||
- Don't produce invalid results when models extend beyond build volume
|
||||
|
||||
## v0.2.0 — Feb 19, 2025
|
||||
|
||||
- Convert slice operation window to a dockable panel
|
||||
|
@@ -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
|
||||
@@ -20,6 +22,10 @@ goo_format = { path = "../goo_format" }
|
||||
[dev-dependencies]
|
||||
criterion.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "slicer"
|
||||
path = "bin/main.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
|
70
slicer/README.md
Normal file
70
slicer/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# `slicer`
|
||||
|
||||
This crate contains the types and algorithms to efficiently slice a mesh and some other stuff for post processing and support generation.
|
||||
|
||||
## Command Line interface
|
||||
|
||||
This crate also exposes a CLI for slicing models, open the dropdown below to view the help page.
|
||||
|
||||
Multiple meshes can be added by using the `--mesh` argument more than once.
|
||||
If you want to change any properties of the mesh like position, rotation, or scale, you can use the flag followed by a 3D vector (`x,y,z`).
|
||||
These flags will modify the mesh defined most recently.
|
||||
See the example below.
|
||||
|
||||
```bash
|
||||
$ slicer --mesh teapot.stl --position 0,0,-0.05 --scale 2,2,2 --mesh frog.stl --position 100,0,0 output.goo
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>CLI Help</summary>
|
||||
|
||||
```plain
|
||||
mslicer command line interface
|
||||
|
||||
Usage: slicer [OPTIONS] <--mesh <MESH>|--position <POSITION>|--rotation <ROTATION>|--scale <SCALE>> <OUTPUT>
|
||||
|
||||
Arguments:
|
||||
<OUTPUT> File to save sliced result to. Currently only .goo files can be generated
|
||||
|
||||
Options:
|
||||
--platform-resolution <PLATFORM_RESOLUTION>
|
||||
Resolution of the printer mask display in pixels [default: "11520, 5120"]
|
||||
--platform-size <PLATFORM_SIZE>
|
||||
Size of the printer display / platform in mm [default: "218.88, 122.904, 260.0"]
|
||||
--layer-height <LAYER_HEIGHT>
|
||||
Layer height in mm [default: 0.05]
|
||||
--first-layers <FIRST_LAYERS>
|
||||
Number of 'first layers'. These are layers that obey the --first- exposure config flags [default: 3]
|
||||
--transition-layers <TRANSITION_LAYERS>
|
||||
Number of transition layers. These are layers that interpolate from the first layer config to the default config [default: 10]
|
||||
--exposure-time <EXPOSURE_TIME>
|
||||
Layer exposure time in seconds [default: 3]
|
||||
--lift-distance <LIFT_DISTANCE>
|
||||
Distance to lift the platform after exposing each regular layer, in mm [default: 5]
|
||||
--lift-speed <LIFT_SPEED>
|
||||
The speed to lift the platform after exposing each regular layer, in mm/min [default: 65]
|
||||
--retract-speed <RETRACT_SPEED>
|
||||
The speed to retract (move down) the platform after exposing each regular layer, in mm/min [default: 150]
|
||||
--first-exposure-time <FIRST_EXPOSURE_TIME>
|
||||
First layer exposure time in seconds [default: 30]
|
||||
--first-lift-distance <FIRST_LIFT_DISTANCE>
|
||||
Distance to lift the platform after exposing each first layer, in mm [default: 5]
|
||||
--first-lift-speed <FIRST_LIFT_SPEED>
|
||||
The speed to lift the platform after exposing each first layer, in mm/min [default: 65]
|
||||
--first-retract-speed <FIRST_RETRACT_SPEED>
|
||||
The speed to retract (move down) the platform after exposing each first layer, in mm/min [default: 150]
|
||||
--preview <PREVIEW>
|
||||
Path to a preview image, will be scaled as needed
|
||||
--mesh <MESH>
|
||||
Path to a .stl or .obj file
|
||||
--position <POSITION>
|
||||
Location of the bottom center of model bounding box. The origin is the center of the build plate
|
||||
--rotation <ROTATION>
|
||||
Rotation of the model in degrees, pitch, roll, yaw
|
||||
--scale <SCALE>
|
||||
Scale of the model along the X, Y, and Z axes
|
||||
-h, --help
|
||||
Print help
|
||||
```
|
||||
|
||||
</details>
|
211
slicer/bin/args.rs
Normal file
211
slicer/bin/args.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
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)
|
||||
}
|
105
slicer/bin/main.rs
Normal file
105
slicer/bin/main.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{stdout, BufReader, Write},
|
||||
thread,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use args::{Args, Model};
|
||||
use clap::{CommandFactory, FromArgMatches};
|
||||
use image::{imageops::FilterType, io::Reader as ImageReader};
|
||||
|
||||
use common::serde::DynamicSerializer;
|
||||
use goo_format::{File as GooFile, LayerEncoder, PreviewImage};
|
||||
use slicer::{mesh::load_mesh, slicer::Slicer};
|
||||
|
||||
mod args;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let matches = Args::command().get_matches();
|
||||
let args = Args::from_arg_matches(&matches)?;
|
||||
let models = Model::from_matches(&matches);
|
||||
|
||||
let slice_config = args.slice_config();
|
||||
let mm_to_px = args.mm_to_px();
|
||||
|
||||
let mut meshes = Vec::new();
|
||||
|
||||
for model in models {
|
||||
let ext = model.path.extension().unwrap().to_string_lossy();
|
||||
let file = File::open(&model.path)?;
|
||||
|
||||
let mut buf = BufReader::new(file);
|
||||
let mut mesh = load_mesh(&mut buf, &ext)?;
|
||||
|
||||
mesh.set_scale(model.scale);
|
||||
mesh.set_rotation(model.rotation);
|
||||
|
||||
// Center the model
|
||||
let (min, max) = mesh.bounds();
|
||||
let mesh_center = (min + max) / 2.0;
|
||||
let center = (slice_config.platform_resolution / 2).map(|x| x as f32);
|
||||
mesh.set_position((center - mesh_center.xy()).to_homogeneous() + model.position);
|
||||
|
||||
// Scale the model into printer-space (mm => px)
|
||||
mesh.set_scale(model.scale.component_mul(&mm_to_px));
|
||||
|
||||
println!(
|
||||
"Loaded `{}`. {{ vert: {}, face: {} }}",
|
||||
model.path.file_name().unwrap().to_string_lossy(),
|
||||
mesh.vertex_count(),
|
||||
mesh.face_count()
|
||||
);
|
||||
|
||||
let (min, max) = mesh.bounds();
|
||||
if min.x < 0.0
|
||||
|| min.y < 0.0
|
||||
|| min.z < 0.0
|
||||
|| max.x > slice_config.platform_resolution.x as f32
|
||||
|| max.y > slice_config.platform_resolution.y as f32
|
||||
|| max.z > slice_config.platform_size.z
|
||||
{
|
||||
println!(" \\ Model extends outsize of print volume and will be cut off.",);
|
||||
}
|
||||
|
||||
meshes.push(mesh);
|
||||
}
|
||||
|
||||
// Actually slice it on another thread (the slicing is multithreaded)
|
||||
let now = Instant::now();
|
||||
|
||||
let slicer = Slicer::new(slice_config.clone(), meshes);
|
||||
let progress = slicer.progress();
|
||||
|
||||
let goo = thread::spawn(move || GooFile::from_slice_result(slicer.slice::<LayerEncoder>()));
|
||||
|
||||
let mut completed = 0;
|
||||
while completed < progress.total() {
|
||||
completed = progress.wait();
|
||||
print!(
|
||||
"\rLayer: {}/{}, {:.1}%",
|
||||
completed,
|
||||
progress.total(),
|
||||
completed as f32 / progress.total() as f32 * 100.0
|
||||
);
|
||||
stdout().flush()?;
|
||||
}
|
||||
|
||||
// Once slicing is complete write to a .goo file
|
||||
let mut goo = goo.join().unwrap();
|
||||
|
||||
if let Some(path) = args.preview {
|
||||
let image = ImageReader::open(path)?.decode()?.to_rgba8();
|
||||
goo.header.small_preview = PreviewImage::from_image_scaled(&image, FilterType::Triangle);
|
||||
goo.header.big_preview = PreviewImage::from_image_scaled(&image, FilterType::Triangle);
|
||||
}
|
||||
|
||||
let mut serializer = DynamicSerializer::new();
|
||||
goo.serialize(&mut serializer);
|
||||
fs::write(args.output, serializer.into_inner())?;
|
||||
|
||||
println!("\nDone. Elapsed: {:.1}s", now.elapsed().as_secs_f32());
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,98 +0,0 @@
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{stdout, BufReader, Write},
|
||||
thread,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use nalgebra::{Vector2, Vector3};
|
||||
|
||||
use common::{
|
||||
config::{ExposureConfig, SliceConfig},
|
||||
format::Format,
|
||||
serde::DynamicSerializer,
|
||||
};
|
||||
use goo_format::{File as GooFile, LayerEncoder};
|
||||
use slicer::{mesh::load_mesh, slicer::Slicer, Pos};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
const FILE_PATH: &str = "teapot.stl";
|
||||
const OUTPUT_PATH: &str = "output.goo";
|
||||
|
||||
let slice_config = SliceConfig {
|
||||
format: Format::Goo,
|
||||
|
||||
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();
|
||||
|
||||
// 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,
|
||||
));
|
||||
|
||||
// Center the model
|
||||
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,
|
||||
));
|
||||
|
||||
println!(
|
||||
"Loaded mesh. {{ vert: {}, face: {} }}",
|
||||
mesh.vertex_count(),
|
||||
mesh.face_count()
|
||||
);
|
||||
|
||||
// Actually slice it on another thread (the slicing is multithreaded)
|
||||
let now = Instant::now();
|
||||
|
||||
let slicer = Slicer::new(slice_config.clone(), vec![mesh]);
|
||||
let progress = slicer.progress();
|
||||
|
||||
let goo = thread::spawn(move || GooFile::from_slice_result(slicer.slice::<LayerEncoder>()));
|
||||
|
||||
let mut completed = 0;
|
||||
while completed < progress.total() {
|
||||
completed = progress.wait();
|
||||
print!(
|
||||
"\rLayer: {}/{}, {:.1}%",
|
||||
completed,
|
||||
progress.total(),
|
||||
completed as f32 / progress.total() as f32 * 100.0
|
||||
);
|
||||
stdout().flush()?;
|
||||
}
|
||||
|
||||
// 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())?;
|
||||
|
||||
println!("\nDone. Elapsed: {:.1}s", now.elapsed().as_secs_f32());
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -340,9 +340,9 @@ impl Default for Mesh {
|
||||
transformation_matrix: Matrix4::identity(),
|
||||
inv_transformation_matrix: Matrix4::identity(),
|
||||
|
||||
position: Pos::new(0.0, 0.0, 0.0),
|
||||
scale: Pos::new(1.0, 1.0, 1.0),
|
||||
rotation: Pos::new(0.0, 0.0, 0.0),
|
||||
position: Pos::repeat(0.0),
|
||||
scale: Pos::repeat(1.0),
|
||||
rotation: Pos::repeat(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ use common::{
|
||||
use ordered_float::OrderedFloat;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
use crate::{format::FormatSliceResult, mesh::Mesh, segments::Segments1D, Pos};
|
||||
use crate::{format::FormatSliceResult, mesh::Mesh, segments::Segments1D};
|
||||
|
||||
/// Used to slice a mesh.
|
||||
pub struct Slicer {
|
||||
@@ -40,14 +40,14 @@ pub struct ProgressInner {
|
||||
impl Slicer {
|
||||
/// Creates a new slicer given a slice config and list of models.
|
||||
pub fn new(slice_config: SliceConfig, models: Vec<Mesh>) -> Self {
|
||||
let max = models.iter().fold(Pos::zeros(), |max, model| {
|
||||
let f = model.vertices().iter().fold(Pos::zeros(), |max, &f| {
|
||||
let f = model.transform(&f);
|
||||
Pos::new(max.x.max(f.x), max.y.max(f.y), max.z.max(f.z))
|
||||
});
|
||||
Pos::new(max.x.max(f.x), max.y.max(f.y), max.z.max(f.z))
|
||||
let max_z = models.iter().fold(0_f32, |max, model| {
|
||||
let verts = model.vertices().iter();
|
||||
let z = verts.fold(0_f32, |max, &f| max.max(model.transform(&f).z));
|
||||
max.max(z)
|
||||
});
|
||||
let layers = (max.z / slice_config.slice_height).ceil() as u32;
|
||||
|
||||
let layers = (max_z / slice_config.slice_height).ceil() as u32;
|
||||
let max_layers = (slice_config.platform_size.z / slice_config.slice_height).ceil() as u32;
|
||||
|
||||
Self {
|
||||
slice_config,
|
||||
@@ -55,7 +55,7 @@ impl Slicer {
|
||||
progress: Progress {
|
||||
inner: Arc::new(ProgressInner {
|
||||
completed: AtomicU32::new(0),
|
||||
total: layers,
|
||||
total: layers.min(max_layers),
|
||||
|
||||
notify: Condvar::new(),
|
||||
last_completed: Mutex::new(0),
|
||||
@@ -77,8 +77,8 @@ impl Slicer {
|
||||
|
||||
/// Actually runs the slicing operation, it is multithreaded.
|
||||
pub fn slice<Layer: EncodableLayer>(&self) -> SliceResult<Layer::Output> {
|
||||
let pixels = (self.slice_config.platform_resolution.x
|
||||
* self.slice_config.platform_resolution.y) as u64;
|
||||
let platform_resolution = self.slice_config.platform_resolution;
|
||||
let pixels = (platform_resolution.x * platform_resolution.y) as u64;
|
||||
|
||||
// A segment contains a reference to all of the triangles it contains. By
|
||||
// splitting the mesh into segments, not all triangles need to be tested
|
||||
@@ -124,7 +124,7 @@ impl Slicer {
|
||||
// across and mark that as an intersection to then be run-length
|
||||
// encoded. There is probably a better polygon filling algo, but
|
||||
// this one works surprisingly fast.
|
||||
for y in 0..self.slice_config.platform_resolution.y {
|
||||
for y in 0..platform_resolution.y {
|
||||
let yf = y as f32;
|
||||
let mut intersections = segments
|
||||
.iter()
|
||||
@@ -155,14 +155,14 @@ impl Slicer {
|
||||
depth += (dir as i32) * 2 - 1;
|
||||
|
||||
if (depth == 0) ^ (prev_depth == 0) {
|
||||
filtered.push(pos);
|
||||
filtered.push(pos.clamp(0.0, platform_resolution.x as f32));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the intersections into runs of white pixels to be
|
||||
// encoded into the layer
|
||||
for span in filtered.chunks_exact(2) {
|
||||
let y_offset = (self.slice_config.platform_resolution.x * y) as u64;
|
||||
let y_offset = (platform_resolution.x * y) as u64;
|
||||
|
||||
let a = span[0].round() as u64;
|
||||
let b = span[1].round() as u64;
|
||||
|
Reference in New Issue
Block a user