Cleanup slice operations window
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::config::SliceConfig;
|
||||
|
||||
pub struct SliceResult<'a, Layer> {
|
||||
@@ -17,3 +19,16 @@ pub trait EncodableLayer {
|
||||
fn add_run(&mut self, length: u64, value: u8);
|
||||
fn finish(self, layer: usize, config: &SliceConfig) -> Self::Output;
|
||||
}
|
||||
|
||||
pub fn human_duration(duration: Duration) -> String {
|
||||
let ms = duration.as_millis() as f32;
|
||||
if ms < 1000.0 {
|
||||
format!("{:}ms", ms)
|
||||
} else if ms < 60_000.0 {
|
||||
format!("{:.2}s", ms / 1000.0)
|
||||
} else {
|
||||
let minutes = ms / 60_000.0;
|
||||
let seconds = (minutes - minutes.floor()) * 60.0;
|
||||
format!("{:.0}m {:.2}s", minutes.floor(), seconds)
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ use std::f32::consts::PI;
|
||||
use egui_wgpu::ScreenDescriptor;
|
||||
use image::{Rgba, RgbaImage};
|
||||
use nalgebra::{Vector2, Vector3};
|
||||
use tracing::{debug, info};
|
||||
use tracing::info;
|
||||
use wgpu::{
|
||||
BufferAddress, BufferDescriptor, BufferUsages, Color, CommandEncoder, CommandEncoderDescriptor,
|
||||
Device, Extent3d, ImageCopyBuffer, ImageCopyTexture, ImageDataLayout, LoadOp, Maintain,
|
||||
@@ -62,10 +62,8 @@ pub fn render_preview_image(
|
||||
.view_projection_matrix(size.0 as f32 / size.1 as f32),
|
||||
..old_workspace
|
||||
};
|
||||
debug!("Preparing");
|
||||
model_pipeline.prepare(device, queue, screen_descriptor, encoder, &workspace);
|
||||
|
||||
debug!("Rendering");
|
||||
render_preview(
|
||||
device,
|
||||
queue,
|
||||
@@ -76,7 +74,6 @@ pub fn render_preview_image(
|
||||
&depth_texture_view,
|
||||
);
|
||||
|
||||
debug!("Downloading");
|
||||
download_preview(device, queue, &resolved_texture)
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,12 @@
|
||||
use std::{
|
||||
sync::Arc,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use common::misc::human_duration;
|
||||
use goo_format::File as GooFile;
|
||||
use image::RgbaImage;
|
||||
use nalgebra::Vector2;
|
||||
@@ -13,6 +17,7 @@ use tracing::info;
|
||||
#[derive(Clone)]
|
||||
pub struct SliceOperation {
|
||||
start_time: Instant,
|
||||
completion: Arc<AtomicU32>,
|
||||
|
||||
pub progress: SliceProgress,
|
||||
pub result: Arc<Mutex<Option<SliceResult>>>,
|
||||
@@ -34,6 +39,7 @@ impl SliceOperation {
|
||||
pub fn new(progress: SliceProgress) -> Self {
|
||||
Self {
|
||||
start_time: Instant::now(),
|
||||
completion: Arc::new(AtomicU32::new(0)),
|
||||
progress,
|
||||
result: Arc::new(Mutex::new(None)),
|
||||
preview_image: Arc::new(Mutex::new(None)),
|
||||
@@ -41,8 +47,9 @@ impl SliceOperation {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
self.start_time.elapsed()
|
||||
pub fn completion(&self) -> Option<String> {
|
||||
let time = self.completion.load(Ordering::Relaxed);
|
||||
(time != 0).then(|| human_duration(Duration::from_millis(time as u64)))
|
||||
}
|
||||
|
||||
pub fn needs_preview_image(&self) -> bool {
|
||||
@@ -64,7 +71,11 @@ impl SliceOperation {
|
||||
}
|
||||
|
||||
pub fn add_result(&self, result: SliceResult) {
|
||||
info!("Slice operation completed in {:?}", self.elapsed());
|
||||
let elapsed = self.start_time.elapsed();
|
||||
self.completion
|
||||
.store(elapsed.as_millis() as u32, Ordering::Relaxed);
|
||||
|
||||
info!("Slice operation completed in {:?}", elapsed);
|
||||
self.result.lock().replace(result);
|
||||
}
|
||||
|
||||
|
@@ -6,8 +6,7 @@ use crate::app::App;
|
||||
mod about;
|
||||
mod models;
|
||||
mod slice_config;
|
||||
mod slice_preview;
|
||||
mod slice_progress;
|
||||
mod slice_operation;
|
||||
mod stats;
|
||||
mod top_bar;
|
||||
mod workspace;
|
||||
@@ -27,8 +26,7 @@ pub fn ui(app: &mut App, ctx: &Context, frame: &mut Frame) {
|
||||
stats::ui(app, ctx, frame);
|
||||
models::ui(app, ctx, frame);
|
||||
about::ui(app, ctx, frame);
|
||||
slice_progress::ui(app, ctx, frame);
|
||||
slice_preview::ui(app, ctx, frame);
|
||||
slice_operation::ui(app, ctx, frame);
|
||||
}
|
||||
|
||||
impl Default for Windows {
|
||||
|
164
mslicer/src/windows/slice_operation.rs
Normal file
164
mslicer/src/windows/slice_operation.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use eframe::Frame;
|
||||
use egui::{
|
||||
style::HandleShape, Align, Context, DragValue, Layout, ProgressBar, RichText, Sense, Slider,
|
||||
Vec2, Window,
|
||||
};
|
||||
use egui_wgpu::Callback;
|
||||
use goo_format::LayerDecoder;
|
||||
use nalgebra::Vector2;
|
||||
use rfd::FileDialog;
|
||||
|
||||
use crate::{
|
||||
app::App, components::vec2_dragger, render::slice_preview::SlicePreviewRenderCallback,
|
||||
slice_operation::SliceResult,
|
||||
};
|
||||
use common::serde::DynamicSerializer;
|
||||
|
||||
pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
|
||||
let mut window_open = true;
|
||||
let mut save_complete = false;
|
||||
|
||||
if let Some(slice_operation) = &app.slice_operation {
|
||||
let progress = &slice_operation.progress;
|
||||
|
||||
let (current, total) = (progress.completed(), progress.total());
|
||||
|
||||
let mut window = Window::new("Slice Operation");
|
||||
|
||||
if current >= total {
|
||||
window = window.open(&mut window_open);
|
||||
}
|
||||
|
||||
window.show(ctx, |ui| {
|
||||
let completion = slice_operation.completion();
|
||||
|
||||
if completion.is_none() {
|
||||
ui.add(
|
||||
ProgressBar::new(current as f32 / total as f32)
|
||||
.text(format!("{:.2}%", current as f32 / total as f32 * 100.0)),
|
||||
);
|
||||
|
||||
ui.label(format!("Slicing... {}/{}", current, total));
|
||||
ctx.request_repaint();
|
||||
} else {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("Slicing completed in {}!", completion.unwrap()));
|
||||
|
||||
ui.with_layout(Layout::default().with_cross_align(Align::Max), |ui| {
|
||||
if ui.button("Save").clicked() {
|
||||
let result = app.slice_operation.as_ref().unwrap().result();
|
||||
let result = result.as_ref().unwrap();
|
||||
|
||||
if let Some(path) = FileDialog::new().save_file() {
|
||||
let mut file = File::create(path).unwrap();
|
||||
let mut serializer = DynamicSerializer::new();
|
||||
result.goo.serialize(&mut serializer);
|
||||
file.write_all(&serializer.into_inner()).unwrap();
|
||||
save_complete = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let mut result = slice_operation.result();
|
||||
let Some(result) = result.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
slice_preview(ui, result);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(
|
||||
DragValue::new(&mut result.slice_preview_layer)
|
||||
.clamp_range(1..=result.goo.layers.len())
|
||||
.custom_formatter(|n, _| format!("{}/{}", n, 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));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if !window_open || save_complete {
|
||||
app.slice_operation = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn slice_preview(ui: &mut egui::Ui, result: &mut SliceResult) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().slider_width = ui.available_size().x
|
||||
/ result.goo.header.x_resolution as f32
|
||||
* result.goo.header.y_resolution as f32
|
||||
- 10.0;
|
||||
ui.add(
|
||||
Slider::new(&mut result.slice_preview_layer, 1..=result.goo.layers.len())
|
||||
.vertical()
|
||||
.handle_shape(HandleShape::Rect { aspect_ratio: 1.0 })
|
||||
.show_value(false),
|
||||
);
|
||||
|
||||
result.slice_preview_layer = result.slice_preview_layer.clamp(1, result.goo.layers.len());
|
||||
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 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
|
||||
};
|
||||
|
||||
result.preview_scale = result.preview_scale.max(0.1);
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
@@ -1,105 +0,0 @@
|
||||
use eframe::Frame;
|
||||
use egui::{style::HandleShape, Context, DragValue, RichText, Sense, Slider, Vec2, Window};
|
||||
use egui_wgpu::Callback;
|
||||
use goo_format::LayerDecoder;
|
||||
use nalgebra::Vector2;
|
||||
|
||||
use crate::{
|
||||
app::App, components::vec2_dragger, render::slice_preview::SlicePreviewRenderCallback,
|
||||
};
|
||||
|
||||
pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
|
||||
if let Some(slice_operation) = &app.slice_operation {
|
||||
let mut result = slice_operation.result();
|
||||
let Some(result) = result.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
Window::new("Slice Preview").show(ctx, move |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().slider_width = ui.available_size().x
|
||||
/ result.goo.header.x_resolution as f32
|
||||
* result.goo.header.y_resolution as f32
|
||||
- 10.0;
|
||||
ui.add(
|
||||
Slider::new(&mut result.slice_preview_layer, 1..=result.goo.layers.len())
|
||||
.vertical()
|
||||
.handle_shape(HandleShape::Rect { aspect_ratio: 1.0 })
|
||||
.show_value(false),
|
||||
);
|
||||
|
||||
result.slice_preview_layer =
|
||||
result.slice_preview_layer.clamp(1, result.goo.layers.len());
|
||||
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 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
|
||||
};
|
||||
|
||||
result.preview_scale = result.preview_scale.max(0.1);
|
||||
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())
|
||||
.custom_formatter(|n, _| format!("{}/{}", n, 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));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,55 +0,0 @@
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use common::serde::DynamicSerializer;
|
||||
use eframe::Frame;
|
||||
use egui::{Context, ProgressBar, Window};
|
||||
use rfd::FileDialog;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
|
||||
let mut window_open = true;
|
||||
let mut save_complete = false;
|
||||
|
||||
if let Some(slice_operation) = &app.slice_operation {
|
||||
let progress = &slice_operation.progress;
|
||||
|
||||
let (current, total) = (progress.completed(), progress.total());
|
||||
|
||||
let mut window = Window::new("Slice Progress");
|
||||
|
||||
if current >= total {
|
||||
window = window.open(&mut window_open);
|
||||
}
|
||||
|
||||
window.show(ctx, |ui| {
|
||||
ui.add(
|
||||
ProgressBar::new(current as f32 / total as f32)
|
||||
.text(format!("{:.2}%", current as f32 / total as f32 * 100.0)),
|
||||
);
|
||||
|
||||
if current < total {
|
||||
ui.label(format!("Slicing... {}/{}", current, total));
|
||||
ctx.request_repaint();
|
||||
} else {
|
||||
ui.label("Slicing complete!");
|
||||
if ui.button("Save").clicked() {
|
||||
let result = app.slice_operation.as_ref().unwrap().result();
|
||||
let result = result.as_ref().unwrap();
|
||||
|
||||
if let Some(path) = FileDialog::new().save_file() {
|
||||
let mut file = File::create(path).unwrap();
|
||||
let mut serializer = DynamicSerializer::new();
|
||||
result.goo.serialize(&mut serializer);
|
||||
file.write_all(&serializer.into_inner()).unwrap();
|
||||
save_complete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if !window_open || save_complete {
|
||||
app.slice_operation = None;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user