Multithread slicer

This commit is contained in:
Connor Slade
2024-06-26 17:11:49 -04:00
parent 3bcf83d8fa
commit 62811e0985
14 changed files with 366 additions and 277 deletions

8
Cargo.lock generated
View File

@@ -931,6 +931,12 @@ dependencies = [
"error-code",
]
[[package]]
name = "clone-macro"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11b52aa11e0a83701c2f8a2b683c254889d8a957e7f58a5a06c5f98ebb4fc3c1"
[[package]]
name = "cocoa"
version = "0.25.0"
@@ -3487,6 +3493,7 @@ dependencies = [
"imageproc",
"nalgebra",
"ordered-float",
"rayon",
"stl_io",
]
@@ -3869,6 +3876,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bytemuck",
"clone-macro",
"common",
"eframe",
"egui",

View File

@@ -6,10 +6,14 @@
- [ ] Allow slicing multiple modals at once
- [ ] Fix translation in slicer (its currently moved by pixels not mm)
- [ ] Allow rotating modals
- [ ] Optimize slicer (BVH?)
- [x] Optimize slicer (BVH?)
- [x] ehh just throw rayon on it
- [ ] Allow saving / loading projects
- [ ] Allow deleting objects
- [ ] Use instancing both for object mesh storage and rendering
- [ ] Less internal dependence on GOO format
- [ ] Anti-aliasing
- [ ] Cache transformed points
- [ ] Rename `ui` module to `mslicer`
- [ ] Proper slice preview scaling
- [ ] Preview image generation

View File

@@ -14,3 +14,4 @@ stl_io = "0.7.0"
common = { path = "../common" }
goo_format = { path = "../goo_format" }
rayon = "1.10.0"

33
slicer/src/config.rs Normal file
View File

@@ -0,0 +1,33 @@
use nalgebra::{Vector2, Vector3};
#[derive(Clone, Debug)]
pub struct SliceConfig {
pub platform_resolution: Vector2<u32>,
pub platform_size: Vector3<f32>,
pub slice_height: f32,
pub exposure_config: ExposureConfig,
pub first_exposure_config: ExposureConfig,
pub first_layers: u32,
}
#[derive(Clone, Debug)]
pub struct ExposureConfig {
pub exposure_time: f32,
pub lift_distance: f32,
pub lift_speed: f32,
pub retract_distance: f32,
pub retract_speed: f32,
}
impl Default for ExposureConfig {
fn default() -> Self {
Self {
exposure_time: 3.0,
lift_distance: 5.0,
lift_speed: 65.0,
retract_distance: 5.0,
retract_speed: 150.0,
}
}
}

View File

@@ -1,5 +1,6 @@
use nalgebra::Vector3;
pub mod config;
pub mod mesh;
pub mod slicer;

View File

@@ -1,5 +1,7 @@
use std::{
fs::{self, File},
io::{stdout, Write},
thread,
time::Instant,
};
@@ -8,8 +10,9 @@ use common::serde::DynamicSerializer;
use nalgebra::{Vector2, Vector3};
use slicer::{
config::{ExposureConfig, SliceConfig},
mesh::load_mesh,
slicer::{slice_goo, ExposureConfig, SliceConfig},
slicer::Slicer,
Pos,
};
@@ -60,16 +63,25 @@ fn main() -> Result<()> {
let now = Instant::now();
let goo = slice_goo(&slice_config, &mesh, |layer, layers| {
let slicer = Slicer::new(slice_config.clone(), mesh);
let progress = slicer.progress();
let goo = thread::spawn(move || slicer.slice());
let mut completed = 0;
while completed < progress.total() {
completed = progress.wait();
print!(
"\rLayer: {}/{layers} ({:.1}%)",
layer + 1,
(layer as f32 + 1.0) / layers as f32 * 100.0
"\rLayer: {}/{}, {:.1}%",
completed,
progress.total(),
completed as f32 / progress.total() as f32 * 100.0
);
});
stdout().flush()?;
}
let mut serializer = DynamicSerializer::new();
goo.serialize(&mut serializer);
goo.join().unwrap().serialize(&mut serializer);
fs::write(OUTPUT_PATH, serializer.into_inner())?;
println!("\nDone. Elapsed: {:.1}s", now.elapsed().as_secs_f32());

View File

@@ -50,35 +50,33 @@ impl Mesh {
let v1 = self.transform(&self.vertices[face[1] as usize]);
let v2 = self.transform(&self.vertices[face[2] as usize]);
let mut dot0 = v0.z - height;
let mut dot1 = v1.z - height;
let mut dot2 = v2.z - height;
let a = v0.z - height;
let b = v1.z - height;
let c = v2.z - height;
if dot0 == 0.0 || dot1 == 0.0 || dot2 == 0.0 {
dot0 -= 0.0001;
dot1 -= 0.0001;
dot2 -= 0.0001;
}
let a_pos = a > 0.0;
let b_pos = b > 0.0;
let c_pos = c > 0.0;
let mut result = [Pos::zeros(); 2];
let mut index = 0;
if dot0 * dot1 < 0.0 {
let t = dot0 / (dot0 - dot1);
if a_pos ^ b_pos {
let t = a / (a - b);
let intersection = v0 + t * (v1 - v0);
result[index] = intersection;
index += 1;
}
if dot1 * dot2 < 0.0 {
let t = dot1 / (dot1 - dot2);
if b_pos ^ c_pos {
let t = b / (b - c);
let intersection = v1 + t * (v2 - v1);
result[index] = intersection;
index += 1;
}
if dot2 * dot0 < 0.0 {
let t = dot2 / (dot2 - dot0);
if c_pos ^ a_pos {
let t = c / (c - a);
let intersection = v2 + t * (v0 - v2);
result[index] = intersection;
}

View File

@@ -1,166 +1,212 @@
use nalgebra::{Vector2, Vector3};
use ordered_float::OrderedFloat;
use std::{
ops::Deref,
sync::{
atomic::{AtomicU32, Ordering},
Arc, Condvar, Mutex,
},
};
use crate::mesh::Mesh;
use ordered_float::OrderedFloat;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{config::SliceConfig, mesh::Mesh};
use goo_format::{File as GooFile, HeaderInfo, LayerContent, LayerEncoder};
#[derive(Clone, Debug)]
pub struct SliceConfig {
pub platform_resolution: Vector2<u32>,
pub platform_size: Vector3<f32>,
pub slice_height: f32,
pub exposure_config: ExposureConfig,
pub first_exposure_config: ExposureConfig,
pub first_layers: u32,
pub struct Slicer {
slice_config: SliceConfig,
model: Mesh,
progress: Progress,
}
#[derive(Clone, Debug)]
pub struct ExposureConfig {
pub exposure_time: f32,
pub lift_distance: f32,
pub lift_speed: f32,
pub retract_distance: f32,
pub retract_speed: f32,
#[derive(Clone)]
pub struct Progress {
inner: Arc<ProgressInner>,
}
pub fn slice_goo(slice_config: &SliceConfig, model: &Mesh, progress: impl Fn(u32, u32)) -> GooFile {
let (_, max) = model.minmax_point();
let max = model.transform(&max);
let layers = (max.z / slice_config.slice_height).ceil() as u32;
pub struct ProgressInner {
completed: AtomicU32,
total: u32,
let layers = (0..layers)
.inspect(|&layer| {
progress(layer, layers);
})
.map(|layer| {
let height = layer as f32 * slice_config.slice_height;
let intersections = model.intersect_plane(height);
notify: Condvar,
last_completed: Mutex<u32>,
}
let segments = intersections
.chunks(2)
.map(|x| (x[0], x[1]))
.collect::<Vec<_>>();
impl Slicer {
pub fn new(slice_config: SliceConfig, model: Mesh) -> Self {
let max = model.transform(&model.minmax_point().1);
let layers = (max.z / slice_config.slice_height).ceil() as u32;
let mut out = Vec::new();
for y in 0..slice_config.platform_resolution.y {
let mut intersections = segments
.iter()
.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
}
})
Self {
slice_config,
model,
progress: Progress {
inner: Arc::new(ProgressInner {
completed: AtomicU32::new(0),
total: layers,
notify: Condvar::new(),
last_completed: Mutex::new(0),
}),
},
}
}
pub fn progress(&self) -> Progress {
self.progress.clone()
}
pub fn slice(&self) -> GooFile {
let (slice_config, model) = (&self.slice_config, &self.model);
let layers = (0..self.progress.total)
.into_par_iter()
.inspect(|_| {
self.progress.completed.fetch_add(1, Ordering::Relaxed);
self.progress.notify.notify_all();
})
.map(|layer| {
let height = layer as f32 * slice_config.slice_height;
let intersections = model.intersect_plane(height);
let segments = intersections
.chunks(2)
.map(|x| (x[0], x[1]))
.collect::<Vec<_>>();
intersections.sort_by_key(|&x| OrderedFloat(x));
intersections.dedup();
let mut out = Vec::new();
for y in 0..slice_config.platform_resolution.y {
let mut intersections = segments
.iter()
.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<_>>();
for span in intersections.chunks_exact(2) {
let y_offset = (slice_config.platform_resolution.x * y) as u64;
out.push((y_offset + span[0] as u64, y_offset + 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);
intersections.sort_by_key(|&x| OrderedFloat(x));
for span in intersections.chunks_exact(2) {
let y_offset = (slice_config.platform_resolution.x * y) as u64;
out.push((y_offset + span[0] as u64, y_offset + span[1] as u64));
}
}
if start > end {
eprintln!("Invalid run {}-{}", start, end);
} else {
let mut encoder = LayerEncoder::new();
let mut last = 0;
for (start, end) in out {
if start > last {
encoder.add_run(start - last, 0);
}
assert!(end >= start);
encoder.add_run(end - start, 255);
last = end;
}
}
let image_size = slice_config.platform_resolution.x as u64
* slice_config.platform_resolution.y as u64;
encoder.add_run(image_size - last, 0);
let image_size = slice_config.platform_resolution.x as u64
* slice_config.platform_resolution.y as u64;
encoder.add_run(image_size - last, 0);
let (data, checksum) = encoder.finish();
let layer_exposure = if layer < slice_config.first_layers {
&slice_config.first_exposure_config
} else {
&slice_config.exposure_config
};
let (data, checksum) = encoder.finish();
let layer_exposure = if layer < slice_config.first_layers {
&slice_config.first_exposure_config
} else {
&slice_config.exposure_config
};
LayerContent {
data,
checksum,
layer_position_z: slice_config.slice_height * (layer + 1) as f32,
LayerContent {
data,
checksum,
layer_position_z: slice_config.slice_height * (layer + 1) as f32,
layer_exposure_time: layer_exposure.exposure_time,
lift_distance: layer_exposure.lift_distance,
lift_speed: layer_exposure.lift_speed,
retract_distance: layer_exposure.retract_distance,
retract_speed: layer_exposure.retract_speed,
pause_position_z: slice_config.platform_size.z,
..Default::default()
}
})
.collect::<Vec<_>>();
let layer_time = slice_config.exposure_config.exposure_time
+ slice_config.exposure_config.lift_distance / slice_config.exposure_config.lift_speed;
let bottom_layer_time = slice_config.first_exposure_config.exposure_time
+ slice_config.first_exposure_config.lift_distance
/ slice_config.first_exposure_config.lift_speed;
let total_time = (layers.len() as u32 - slice_config.first_layers) as f32 * layer_time
+ slice_config.first_layers as f32 * bottom_layer_time;
GooFile::new(
HeaderInfo {
x_resolution: slice_config.platform_resolution.x as u16,
y_resolution: slice_config.platform_resolution.y as u16,
x_size: slice_config.platform_size.x,
y_size: slice_config.platform_size.y,
layer_count: layers.len() as u32,
printing_time: total_time as u32,
layer_thickness: slice_config.slice_height,
bottom_layers: slice_config.first_layers,
transition_layers: slice_config.first_layers as u16 + 1,
exposure_time: slice_config.exposure_config.exposure_time,
lift_distance: slice_config.exposure_config.lift_distance,
lift_speed: slice_config.exposure_config.lift_speed,
retract_distance: slice_config.exposure_config.retract_distance,
retract_speed: slice_config.exposure_config.retract_speed,
bottom_exposure_time: slice_config.first_exposure_config.exposure_time,
bottom_lift_distance: slice_config.first_exposure_config.lift_distance,
bottom_lift_speed: slice_config.first_exposure_config.lift_speed,
bottom_retract_distance: slice_config.first_exposure_config.retract_distance,
bottom_retract_speed: slice_config.first_exposure_config.retract_speed,
layer_exposure_time: layer_exposure.exposure_time,
lift_distance: layer_exposure.lift_distance,
lift_speed: layer_exposure.lift_speed,
retract_distance: layer_exposure.retract_distance,
retract_speed: layer_exposure.retract_speed,
pause_position_z: slice_config.platform_size.z,
..Default::default()
}
})
.collect::<Vec<_>>();
let layer_time = slice_config.exposure_config.exposure_time
+ slice_config.exposure_config.lift_distance / slice_config.exposure_config.lift_speed;
let bottom_layer_time = slice_config.first_exposure_config.exposure_time
+ slice_config.first_exposure_config.lift_distance
/ slice_config.first_exposure_config.lift_speed;
let total_time = (layers.len() as u32 - slice_config.first_layers) as f32 * layer_time
+ slice_config.first_layers as f32 * bottom_layer_time;
GooFile::new(
HeaderInfo {
x_resolution: slice_config.platform_resolution.x as u16,
y_resolution: slice_config.platform_resolution.y as u16,
x_size: slice_config.platform_size.x,
y_size: slice_config.platform_size.y,
layer_count: layers.len() as u32,
printing_time: total_time as u32,
layer_thickness: slice_config.slice_height,
bottom_layers: slice_config.first_layers,
transition_layers: slice_config.first_layers as u16 + 1,
exposure_time: slice_config.exposure_config.exposure_time,
lift_distance: slice_config.exposure_config.lift_distance,
lift_speed: slice_config.exposure_config.lift_speed,
retract_distance: slice_config.exposure_config.retract_distance,
retract_speed: slice_config.exposure_config.retract_speed,
bottom_exposure_time: slice_config.first_exposure_config.exposure_time,
bottom_lift_distance: slice_config.first_exposure_config.lift_distance,
bottom_lift_speed: slice_config.first_exposure_config.lift_speed,
bottom_retract_distance: slice_config.first_exposure_config.retract_distance,
bottom_retract_speed: slice_config.first_exposure_config.retract_speed,
..Default::default()
},
layers,
)
}
impl Default for ExposureConfig {
fn default() -> Self {
Self {
exposure_time: 3.0,
lift_distance: 5.0,
lift_speed: 65.0,
retract_distance: 5.0,
retract_speed: 150.0,
}
},
layers,
)
}
}
impl Deref for Progress {
type Target = ProgressInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Progress {
pub fn wait(&self) -> u32 {
let mut last_completed = self
.notify
.wait(self.last_completed.lock().unwrap())
.unwrap();
let current = self.completed.load(Ordering::Relaxed);
if *last_completed < current {
*last_completed = current;
}
current
}
pub fn completed(&self) -> u32 {
self.completed.load(Ordering::Relaxed)
}
pub fn total(&self) -> u32 {
self.total
}
}

View File

@@ -18,3 +18,4 @@ wgpu = "0.19.4"
common = { path = "../common" }
goo_format = { path = "../goo_format" }
slicer = { path = "../slicer" }
clone-macro = "0.1.0"

View File

@@ -1,11 +1,15 @@
use std::{
sync::{atomic::AtomicU32, Arc, Mutex, RwLock},
sync::{Arc, Mutex, RwLock},
time::Instant,
};
use egui::{CentralPanel, Frame, Sense};
use egui_wgpu::Callback;
use nalgebra::{Vector2, Vector3};
use slicer::{
config::{ExposureConfig, SliceConfig},
slicer::Progress as SliceProgress,
};
use crate::{
render::{
@@ -15,27 +19,20 @@ use crate::{
windows::{self, Windows},
};
use goo_format::File as GooFile;
use slicer::slicer::{ExposureConfig, SliceConfig};
pub struct App {
pub camera: Camera,
pub slice_config: SliceConfig,
pub meshes: Arc<RwLock<Vec<RenderedMesh>>>,
pub slice_progress: Option<Arc<SliceProgress>>,
pub slice_progress: Option<SliceProgress>,
pub slice_result: Arc<Mutex<Option<SliceResult>>>,
pub render_style: RenderStyle,
pub fps: FpsTracker,
pub windows: Windows,
}
pub struct SliceProgress {
pub current: AtomicU32,
pub total: AtomicU32,
pub result: Mutex<Option<SliceResult>>,
}
pub struct SliceResult {
pub goo: GooFile,
@@ -120,6 +117,7 @@ impl Default for App {
},
fps: FpsTracker::new(),
slice_progress: None,
slice_result: Arc::new(Mutex::new(None)),
meshes: Arc::new(RwLock::new(Vec::new())),
windows: Windows::default(),
render_style: RenderStyle::Normals,

View File

@@ -1,6 +1,6 @@
use eframe::Frame;
use egui::{Context, DragValue, Grid, Ui, Window};
use slicer::slicer::ExposureConfig;
use slicer::config::ExposureConfig;
use crate::{
app::App,

View File

@@ -9,93 +9,86 @@ use crate::{
};
pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
if let Some(slice_progress) = &app.slice_progress {
if let Some(result) = slice_progress.result.lock().unwrap().as_mut() {
Window::new("Slice Preview")
.resizable([true, true])
.show(ctx, move |ui| {
ui.horizontal(|ui| {
ui.add(
Slider::new(
&mut result.slice_preview_layer,
1..=result.goo.layers.len(),
)
if let Some(result) = app.slice_result.lock().unwrap().as_mut() {
Window::new("Slice Preview")
.resizable([true, true])
.show(ctx, move |ui| {
ui.horizontal(|ui| {
ui.add(
Slider::new(&mut result.slice_preview_layer, 1..=result.goo.layers.len())
.vertical()
.show_value(false),
);
let new_preview = if result.last_preview_layer != result.slice_preview_layer {
result.last_preview_layer = result.slice_preview_layer;
let (width, height) = (
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
);
let new_preview = if result.last_preview_layer != result.slice_preview_layer
{
result.last_preview_layer = result.slice_preview_layer;
let (width, height) = (
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
);
let layer_data = &result.goo.layers[result.slice_preview_layer - 1].data;
let decoder = LayerDecoder::new(layer_data);
let layer_data =
&result.goo.layers[result.slice_preview_layer - 1].data;
let decoder = LayerDecoder::new(layer_data);
let mut image = vec![0; (width * height) as usize];
let mut pixel = 0;
for run in decoder {
for _ in 0..run.length {
image[pixel] = run.value;
pixel += 1;
}
let mut image = vec![0; (width * height) as usize];
let mut pixel = 0;
for run in decoder {
for _ in 0..run.length {
image[pixel] = run.value;
pixel += 1;
}
}
Some(image)
} else {
None
};
Some(image)
} else {
None
};
egui::Frame::canvas(ui.style()).show(ui, |ui| {
let available_size = ui.available_size();
let (rect, _response) = ui.allocate_exact_size(
Vec2::new(
available_size.x,
available_size.x / result.goo.header.x_resolution as f32
* result.goo.header.y_resolution as f32,
),
Sense::drag(),
);
let callback = Callback::new_paint_callback(
rect,
SlicePreviewRenderCallback {
dimensions: Vector2::new(
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
),
offset: result.preview_offset,
scale: result.preview_scale,
new_preview,
},
);
ui.painter().add(callback);
});
});
ui.horizontal(|ui| {
ui.add(
DragValue::new(&mut result.slice_preview_layer)
.clamp_range(1..=result.goo.layers.len())
.suffix(format!("/{}", result.goo.layers.len())),
egui::Frame::canvas(ui.style()).show(ui, |ui| {
let available_size = ui.available_size();
let (rect, _response) = ui.allocate_exact_size(
Vec2::new(
available_size.x,
available_size.x / result.goo.header.x_resolution as f32
* result.goo.header.y_resolution as f32,
),
Sense::drag(),
);
result.slice_preview_layer +=
ui.button(RichText::new("+").monospace()).clicked() as usize;
result.slice_preview_layer -=
ui.button(RichText::new("-").monospace()).clicked() as usize;
ui.separator();
ui.label("Offset");
vec2_dragger(ui, result.preview_offset.as_mut(), |x| x);
ui.separator();
ui.label("Scale");
ui.add(DragValue::new(&mut result.preview_scale));
let callback = Callback::new_paint_callback(
rect,
SlicePreviewRenderCallback {
dimensions: Vector2::new(
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
),
offset: result.preview_offset,
scale: result.preview_scale,
new_preview,
},
);
ui.painter().add(callback);
});
});
}
ui.horizontal(|ui| {
ui.add(
DragValue::new(&mut result.slice_preview_layer)
.clamp_range(1..=result.goo.layers.len())
.suffix(format!("/{}", result.goo.layers.len())),
);
result.slice_preview_layer +=
ui.button(RichText::new("+").monospace()).clicked() as usize;
result.slice_preview_layer -=
ui.button(RichText::new("-").monospace()).clicked() as usize;
ui.separator();
ui.label("Offset");
vec2_dragger(ui, result.preview_offset.as_mut(), |x| x);
ui.separator();
ui.label("Scale");
ui.add(DragValue::new(&mut result.preview_scale));
});
});
}
}

View File

@@ -1,4 +1,4 @@
use std::{fs::File, io::Write, sync::atomic::Ordering};
use std::{fs::File, io::Write};
use common::serde::DynamicSerializer;
use eframe::Frame;
@@ -12,8 +12,7 @@ pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
let mut save_complete = false;
if let Some(progress) = app.slice_progress.as_ref() {
let current = progress.current.load(Ordering::Relaxed) + 1;
let total = progress.total.load(Ordering::Relaxed);
let (current, total) = (progress.completed(), progress.total());
let mut window = Window::new("Slice Progress");
@@ -33,7 +32,7 @@ pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
} else {
ui.label("Slicing complete!");
if ui.button("Save").clicked() {
let result = progress.result.lock().unwrap().take().unwrap();
let result = app.slice_result.lock().unwrap().take().unwrap();
if let Some(path) = FileDialog::new().save_file() {
let mut file = File::create(path).unwrap();
let mut serializer = DynamicSerializer::new();

View File

@@ -1,19 +1,14 @@
use std::{
sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex,
},
thread,
};
use std::thread;
use clone_macro::clone;
use eframe::Frame;
use egui::{Context, TopBottomPanel, Ui};
use nalgebra::Vector2;
use rfd::FileDialog;
use slicer::{slicer::slice_goo, Pos};
use slicer::{slicer::Slicer, Pos};
use crate::{
app::{App, SliceProgress, SliceResult},
app::{App, SliceResult},
render::rendered_mesh::RenderedMesh,
};
@@ -83,26 +78,26 @@ pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
mesh.position.z - app.slice_config.slice_height,
);
let progress = Arc::new(SliceProgress {
current: AtomicU32::new(0),
total: AtomicU32::new(0),
result: Mutex::new(None),
});
app.slice_progress = Some(progress.clone());
// let progress = Arc::new(SliceProgress {
// current: AtomicU32::new(0),
// total: AtomicU32::new(0),
// result: Mutex::new(None),
// });
// app.slice_progress = Some(progress.clone());
thread::spawn(move || {
let goo = slice_goo(&slice_config, &mesh, |current, total| {
progress.current.store(current, Ordering::Relaxed);
progress.total.store(total, Ordering::Relaxed);
});
progress.result.lock().unwrap().replace(SliceResult {
let slicer = Slicer::new(slice_config, mesh);
app.slice_progress = Some(slicer.progress());
thread::spawn(clone!([{ app.slice_result } as slice_result], move || {
let goo = slicer.slice();
slice_result.lock().unwrap().replace(SliceResult {
goo,
slice_preview_layer: 0,
last_preview_layer: 0,
preview_offset: Vector2::new(0.0, 0.0),
preview_scale,
});
});
}));
}
});
});