From a0f5ee53038d3d341bcbee00c1a2b98eccad6ab9 Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Sat, 2 Nov 2024 13:52:05 -0400 Subject: [PATCH] Abstraction over format --- Cargo.lock | 2 + TODO.md | 4 +- common/src/config.rs | 6 +++ common/src/format.rs | 6 +++ common/src/lib.rs | 1 + mslicer/src/app/mod.rs | 19 ++----- mslicer/src/app/slice_operation.rs | 7 ++- mslicer/src/plugins/mod.rs | 6 ++- mslicer/src/windows/slice_operation.rs | 44 +++++---------- slicer/Cargo.toml | 2 + slicer/src/format.rs | 74 ++++++++++++++++++++++++++ slicer/src/lib.rs | 1 + slicer/src/main.rs | 3 ++ slicer/src/slicer.rs | 9 +++- 14 files changed, 132 insertions(+), 52 deletions(-) create mode 100644 common/src/format.rs create mode 100644 slicer/src/format.rs diff --git a/Cargo.lock b/Cargo.lock index 788ec28..ea286af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4418,9 +4418,11 @@ dependencies = [ "common", "criterion", "goo_format", + "image 0.25.1", "nalgebra 0.32.6", "obj-rs", "ordered-float", + "parking_lot", "rayon", "stl_io", "tracing", diff --git a/TODO.md b/TODO.md index d765dfd..d3464b6 100644 --- a/TODO.md +++ b/TODO.md @@ -75,6 +75,8 @@ - [x] Fix Z translation being doubled - [ ] Merge goo_format changes into goo crate - [ ] Implement .ctb format (see ) + - [x] Generic format system + - [ ] Fix plugins / post processes - [ ] Undo / Redo - [x] Close file menu if button clicked - [x] Allow dragging in project to load them @@ -82,5 +84,5 @@ - [ ] Save on remote thread - [ ] Printer profiles - [x] Save config on quit -- [ ] Refactor plugins to post processors +- [x] Refactor plugins to post processors - [ ] Update readme diff --git a/common/src/config.rs b/common/src/config.rs index e5dd3d7..e759be0 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -1,8 +1,12 @@ use nalgebra::{Vector2, Vector3}; use serde::{Deserialize, Serialize}; +use crate::format::Format; + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct SliceConfig { + pub format: Format, + pub platform_resolution: Vector2, pub platform_size: Vector3, pub slice_height: f32, @@ -25,6 +29,8 @@ pub struct ExposureConfig { impl Default for SliceConfig { fn default() -> Self { Self { + 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, diff --git a/common/src/format.rs b/common/src/format.rs new file mode 100644 index 0000000..8d7affb --- /dev/null +++ b/common/src/format.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum Format { + Goo, +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 39f7f51..a4e3f3e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,5 @@ pub mod config; +pub mod format; pub mod image; pub mod misc; pub mod oklab; diff --git a/mslicer/src/app/mod.rs b/mslicer/src/app/mod.rs index 04ddd30..b67fa72 100644 --- a/mslicer/src/app/mod.rs +++ b/mslicer/src/app/mod.rs @@ -13,7 +13,6 @@ use egui::Visuals; use egui_dock::{DockState, NodeIndex}; use egui_phosphor::regular::CARET_RIGHT; use egui_tracing::EventCollector; -use image::imageops::FilterType; use nalgebra::Vector2; use parking_lot::RwLock; use tracing::{info, warn}; @@ -33,8 +32,7 @@ use crate::{ windows::{self, Tab}, }; use common::config::SliceConfig; -use goo_format::{File as GooFile, LayerEncoder, PreviewImage}; -use slicer::{slicer::Slicer, Pos}; +use slicer::{format::FormatSliceFile, slicer::Slicer, Pos}; pub mod config; pub mod project; @@ -174,19 +172,12 @@ impl App { [{ self.slice_operation } as slice_operation], move || { let slice_operation = slice_operation.as_ref().unwrap(); - let mut goo = GooFile::from_slice_result(slicer.slice::()); + let preview_image = slice_operation.preview_image(); + let file = FormatSliceFile::from_slice_result(preview_image, slicer.slice_format()); - { - let preview_image = slice_operation.preview_image(); - goo.header.big_preview = - PreviewImage::from_image_scaled(&preview_image, FilterType::Nearest); - goo.header.small_preview = - PreviewImage::from_image_scaled(&preview_image, FilterType::Nearest); - } - - let layers = goo.layers.len(); + let layers = file.info().layers as usize; slice_operation.add_result(SliceResult { - goo, + file, slice_preview_layer: 0, last_preview_layer: 0, preview_offset: Vector2::new(0.0, 0.0), diff --git a/mslicer/src/app/slice_operation.rs b/mslicer/src/app/slice_operation.rs index 974b93b..b152fe2 100644 --- a/mslicer/src/app/slice_operation.rs +++ b/mslicer/src/app/slice_operation.rs @@ -7,11 +7,10 @@ use std::{ }; use common::misc::human_duration; -use goo_format::File as GooFile; use image::RgbaImage; use nalgebra::Vector2; use parking_lot::{Condvar, MappedMutexGuard, Mutex, MutexGuard}; -use slicer::slicer::Progress as SliceProgress; +use slicer::{format::FormatSliceFile, slicer::Progress as SliceProgress}; use tracing::info; use crate::app::App; @@ -30,7 +29,7 @@ pub struct SliceOperation { } pub struct SliceResult { - pub goo: GooFile, + pub file: FormatSliceFile, pub slice_preview_layer: usize, pub last_preview_layer: usize, @@ -95,7 +94,7 @@ impl SliceOperation { return; } - let goo = &mut result.as_mut().unwrap().goo; + let goo = &mut result.as_mut().unwrap().file; app.plugin_manager.post_slice(app, goo); self.has_post_processed = true; } diff --git a/mslicer/src/plugins/mod.rs b/mslicer/src/plugins/mod.rs index 15bf405..8e1b308 100644 --- a/mslicer/src/plugins/mod.rs +++ b/mslicer/src/plugins/mod.rs @@ -1,4 +1,5 @@ use egui::{Context, Ui}; +use slicer::format::FormatSliceFile; use crate::app::App; use goo_format::File as GooFile; @@ -18,9 +19,10 @@ pub struct PluginManager { } impl PluginManager { - pub fn post_slice(&self, app: &App, goo: &mut GooFile) { + pub fn post_slice(&self, app: &App, goo: &mut FormatSliceFile) { for plugin in &self.plugins { - plugin.post_slice(app, goo); + // TODO: FIX PLUGINS + // plugin.post_slice(app, goo); } } } diff --git a/mslicer/src/windows/slice_operation.rs b/mslicer/src/windows/slice_operation.rs index aa7de4d..7d8b8a0 100644 --- a/mslicer/src/windows/slice_operation.rs +++ b/mslicer/src/windows/slice_operation.rs @@ -7,7 +7,6 @@ use egui::{ }; use egui_phosphor::regular::{FLOPPY_DISK_BACK, PAPER_PLANE_TILT}; use egui_wgpu::Callback; -use goo_format::LayerDecoder; use nalgebra::Vector2; use rfd::FileDialog; @@ -82,7 +81,7 @@ pub fn ui(app: &mut App, ctx: &Context) { let result = result.as_ref().unwrap(); let mut serializer = DynamicSerializer::new(); - result.goo.serialize(&mut serializer); + result.file.serialize(&mut serializer); let data = Arc::new(serializer.into_inner()); let mainboard_id = printer.mainboard_id.clone(); @@ -101,7 +100,7 @@ pub fn ui(app: &mut App, ctx: &Context) { 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); + result.file.serialize(&mut serializer); file.write_all(&serializer.into_inner()).unwrap(); } } @@ -120,7 +119,7 @@ pub fn ui(app: &mut App, ctx: &Context) { let layer_digits = result.layer_count.1 as usize; ui.add( DragValue::new(&mut result.slice_preview_layer) - .clamp_range(1..=result.goo.layers.len()) + .clamp_range(1..=result.file.info().layers) .custom_formatter(|n, _| { format!("{:0>layer_digits$}/{}", n, result.layer_count.0) }), @@ -152,38 +151,27 @@ pub fn ui(app: &mut App, ctx: &Context) { } fn slice_preview(ui: &mut egui::Ui, result: &mut SliceResult) { + let info = result.file.info(); + 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.spacing_mut().slider_width = + ui.available_size().x / info.resolution.x as f32 * info.resolution.y as f32 - 10.0; ui.add( - Slider::new(&mut result.slice_preview_layer, 1..=result.goo.layers.len()) + Slider::new(&mut result.slice_preview_layer, 1..=info.layers as usize) .vertical() .handle_shape(HandleShape::Rect { aspect_ratio: 1.0 }) .show_value(false), ); - let (width, height) = ( - result.goo.header.x_resolution as u32, - result.goo.header.y_resolution as u32, - ); + let (width, height) = (info.resolution.x as u32, info.resolution.y as u32); - result.slice_preview_layer = result.slice_preview_layer.clamp(1, result.goo.layers.len()); + result.slice_preview_layer = result.slice_preview_layer.clamp(1, info.layers as usize); let new_preview = if result.last_preview_layer != result.slice_preview_layer { result.last_preview_layer = result.slice_preview_layer; - 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 layer = result.slice_preview_layer - 1; + result.file.decode_layer(layer, &mut image); Some(image) } else { @@ -195,8 +183,7 @@ fn slice_preview(ui: &mut egui::Ui, result: &mut SliceResult) { 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, + available_size.x / info.resolution.x as f32 * info.resolution.y as f32, ), Sense::drag(), ); @@ -215,10 +202,7 @@ fn slice_preview(ui: &mut egui::Ui, result: &mut SliceResult) { 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, - ), + dimensions: Vector2::new(info.resolution.x as u32, info.resolution.y as u32), offset: result.preview_offset, scale: preview_scale, new_preview, diff --git a/slicer/Cargo.toml b/slicer/Cargo.toml index 11ffb1e..a90315e 100644 --- a/slicer/Cargo.toml +++ b/slicer/Cargo.toml @@ -5,9 +5,11 @@ edition = "2021" [dependencies] anyhow.workspace = true +image.workspace = true nalgebra.workspace = true obj-rs.workspace = true ordered-float.workspace = true +parking_lot.workspace = true rayon.workspace = true stl_io.workspace = true tracing.workspace = true diff --git a/slicer/src/format.rs b/slicer/src/format.rs new file mode 100644 index 0000000..9f94a24 --- /dev/null +++ b/slicer/src/format.rs @@ -0,0 +1,74 @@ +use goo_format::PreviewImage; +use image::{imageops::FilterType, RgbaImage}; +use nalgebra::Vector2; +use parking_lot::MappedMutexGuard; + +use common::{misc::SliceResult, serde::Serializer}; + +pub enum FormatSliceResult<'a> { + Goo(SliceResult<'a, goo_format::LayerContent>), +} + +pub enum FormatSliceFile { + Goo(goo_format::File), +} + +pub struct SliceInfo { + pub layers: u32, + pub resolution: Vector2, +} + +// TODO: convert to dynamic dispatch + +impl FormatSliceFile { + pub fn from_slice_result( + preview_image: MappedMutexGuard<'_, RgbaImage>, + slice_result: FormatSliceResult, + ) -> Self { + match slice_result { + FormatSliceResult::Goo(result) => { + let mut file = goo_format::File::from_slice_result(result); + file.header.big_preview = + PreviewImage::from_image_scaled(&preview_image, FilterType::Nearest); + file.header.small_preview = + PreviewImage::from_image_scaled(&preview_image, FilterType::Nearest); + Self::Goo(file) + } + } + } + + pub fn serialize(&self, ser: &mut T) { + match self { + FormatSliceFile::Goo(file) => file.serialize(ser), + } + } + + pub fn info(&self) -> SliceInfo { + match self { + FormatSliceFile::Goo(file) => SliceInfo { + layers: file.header.layer_count, + resolution: Vector2::new( + file.header.x_resolution as u32, + file.header.y_resolution as u32, + ), + }, + } + } + + pub fn decode_layer(&self, layer: usize, image: &mut [u8]) { + match self { + FormatSliceFile::Goo(file) => { + let layer_data = &file.layers[layer].data; + let decoder = goo_format::LayerDecoder::new(layer_data); + + let mut pixel = 0; + for run in decoder { + for _ in 0..run.length { + image[pixel] = run.value; + pixel += 1; + } + } + } + } + } +} diff --git a/slicer/src/lib.rs b/slicer/src/lib.rs index 6a40341..be9c3ea 100644 --- a/slicer/src/lib.rs +++ b/slicer/src/lib.rs @@ -3,6 +3,7 @@ use nalgebra::Vector3; pub mod builder; +pub mod format; pub mod half_edge; pub mod mesh; pub mod segments; diff --git a/slicer/src/main.rs b/slicer/src/main.rs index f31dd2c..3d48a2d 100644 --- a/slicer/src/main.rs +++ b/slicer/src/main.rs @@ -10,6 +10,7 @@ use nalgebra::{Vector2, Vector3}; use common::{ config::{ExposureConfig, SliceConfig}, + format::Format, serde::DynamicSerializer, }; use goo_format::{File as GooFile, LayerEncoder}; @@ -20,6 +21,8 @@ fn main() -> Result<()> { 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, diff --git a/slicer/src/slicer.rs b/slicer/src/slicer.rs index 9b45083..46c68e0 100644 --- a/slicer/src/slicer.rs +++ b/slicer/src/slicer.rs @@ -8,12 +8,13 @@ use std::{ use common::{ config::SliceConfig, + format::Format, misc::{EncodableLayer, SliceResult}, }; use ordered_float::OrderedFloat; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use crate::{mesh::Mesh, segments::Segments1D, Pos}; +use crate::{format::FormatSliceResult, mesh::Mesh, segments::Segments1D, Pos}; /// Used to slice a mesh. pub struct Slicer { @@ -68,6 +69,12 @@ impl Slicer { self.progress.clone() } + pub fn slice_format(&self) -> FormatSliceResult { + match self.slice_config.format { + Format::Goo => FormatSliceResult::Goo(self.slice::()), + } + } + /// Actually runs the slicing operation, it is multithreaded. pub fn slice(&self) -> SliceResult { let pixels = (self.slice_config.platform_resolution.x