Save / load app config
This commit is contained in:
49
Cargo.lock
generated
49
Cargo.lock
generated
@@ -1398,6 +1398,15 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
@@ -1408,6 +1417,18 @@ dependencies = [
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
@@ -1493,6 +1514,7 @@ dependencies = [
|
||||
"pollster",
|
||||
"raw-window-handle 0.5.2",
|
||||
"raw-window-handle 0.6.2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
@@ -2803,6 +2825,7 @@ dependencies = [
|
||||
"clone-macro",
|
||||
"common",
|
||||
"const_format",
|
||||
"dirs",
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui-modal",
|
||||
@@ -2820,8 +2843,10 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"remote_send",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slicer",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wgpu",
|
||||
@@ -3358,6 +3383,12 @@ version = "11.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "orbclient"
|
||||
version = "0.3.47"
|
||||
@@ -4204,9 +4235,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
||||
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -4630,21 +4661,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.14"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
|
||||
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.14",
|
||||
"toml_edit 0.22.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -4673,9 +4704,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.14"
|
||||
version = "0.22.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
@@ -11,7 +11,8 @@ chrono = "0.4.38"
|
||||
clone-macro = "0.1.0"
|
||||
const_format = "0.2.32"
|
||||
criterion = "0.5.1"
|
||||
eframe = { version = "0.27.2", features = ["wgpu"] }
|
||||
dirs = "5.0.1"
|
||||
eframe = { version = "0.27.2", features = ["wgpu", "serde"] }
|
||||
egui = "0.27.2"
|
||||
egui_dock = "0.12.0"
|
||||
egui_tracing = "0.2.2"
|
||||
@@ -35,6 +36,7 @@ serde_json = "1.0.120"
|
||||
serde_repr = "0.1.19"
|
||||
soon = { git = "https://github.com/connorslade/misc" }
|
||||
stl_io = "0.7.0"
|
||||
toml = "0.8.16"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
wgpu = "0.19.4"
|
||||
wgpu = "0.19.4"
|
2
TODO.md
2
TODO.md
@@ -53,7 +53,7 @@
|
||||
- [ ] Verify printer capabilities before uploading / printing
|
||||
- [ ] Move remote print module to remote send?
|
||||
- [ ] Add some optional alert sound for print completion
|
||||
- [ ] Create config struct thats saved / loaded on shutdown / startup
|
||||
- [x] Create config struct thats saved / loaded on shutdown / startup
|
||||
- [ ] Publish goo_format crate
|
||||
- [ ] Allow uploading / printing a local .goo file
|
||||
- [x] Load normals from model files
|
||||
|
@@ -9,6 +9,7 @@ bytemuck.workspace = true
|
||||
chrono.workspace = true
|
||||
clone-macro.workspace = true
|
||||
const_format.workspace = true
|
||||
dirs.workspace = true
|
||||
eframe.workspace = true
|
||||
egui_dock.workspace = true
|
||||
egui_tracing.workspace = true
|
||||
@@ -25,6 +26,8 @@ plexus.workspace = true
|
||||
rand.workspace = true
|
||||
rfd.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
toml.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing.workspace = true
|
||||
wgpu.workspace = true
|
||||
|
@@ -1,7 +1,8 @@
|
||||
use std::{sync::Arc, thread, time::Instant};
|
||||
use std::{path::PathBuf, sync::Arc, thread, time::Instant};
|
||||
|
||||
use clone_macro::clone;
|
||||
use eframe::Theme;
|
||||
use egui::Visuals;
|
||||
use egui_dock::{DockState, NodeIndex};
|
||||
use egui_modal::{DialogBuilder, Icon, Modal};
|
||||
use egui_tracing::EventCollector;
|
||||
@@ -9,12 +10,14 @@ use image::imageops::FilterType;
|
||||
use nalgebra::{Vector2, Vector3};
|
||||
use parking_lot::RwLock;
|
||||
use slicer::{slicer::Slicer, Pos};
|
||||
use tracing::info;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
remote_print::RemotePrint,
|
||||
render::{camera::Camera, pipelines::model::RenderStyle, rendered_mesh::RenderedMesh},
|
||||
render::{camera::Camera, rendered_mesh::RenderedMesh},
|
||||
slice_operation::{SliceOperation, SliceResult},
|
||||
ui_state::UiState,
|
||||
windows::{self, Tab},
|
||||
};
|
||||
use common::config::{ExposureConfig, SliceConfig};
|
||||
@@ -33,31 +36,7 @@ pub struct App {
|
||||
pub meshes: Arc<RwLock<Vec<RenderedMesh>>>,
|
||||
pub slice_operation: Option<SliceOperation>,
|
||||
pub remote_print: RemotePrint,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UiState {
|
||||
pub event_collector: EventCollector,
|
||||
pub working_address: String,
|
||||
pub send_print_completion: bool,
|
||||
pub remote_print_connecting: RemotePrintConnectStatus,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub enum RemotePrintConnectStatus {
|
||||
#[default]
|
||||
None,
|
||||
Connecting,
|
||||
Scanning,
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub render_style: RenderStyle,
|
||||
pub grid_size: f32,
|
||||
pub theme: Theme,
|
||||
pub alert_print_completion: bool,
|
||||
pub init_remote_print_at_startup: bool,
|
||||
pub network_timeout: f32,
|
||||
pub config_dir: PathBuf,
|
||||
}
|
||||
|
||||
pub struct FpsTracker {
|
||||
@@ -73,6 +52,15 @@ impl App {
|
||||
let [_old_node, new_node] = surface.split_below(new_node, 0.5, vec![Tab::SliceConfig]);
|
||||
surface.split_below(new_node, 0.5, vec![Tab::Workspace, Tab::RemotePrint]);
|
||||
|
||||
let config_dir = dirs::config_dir().unwrap().join("mslicer");
|
||||
let config = match Config::load(&config_dir) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
warn!("Failed to load config, using defaults: {}", err);
|
||||
Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
dock_state,
|
||||
modal: None,
|
||||
@@ -80,14 +68,7 @@ impl App {
|
||||
event_collector,
|
||||
..Default::default()
|
||||
},
|
||||
config: Config {
|
||||
render_style: RenderStyle::Rended,
|
||||
theme: Theme::Dark,
|
||||
grid_size: 12.16,
|
||||
alert_print_completion: false,
|
||||
init_remote_print_at_startup: false,
|
||||
network_timeout: 5.0,
|
||||
},
|
||||
config,
|
||||
camera: Camera::default(),
|
||||
slice_config: SliceConfig {
|
||||
platform_resolution: Vector2::new(11_520, 5_120),
|
||||
@@ -108,6 +89,7 @@ impl App {
|
||||
meshes: Arc::new(RwLock::new(Vec::new())),
|
||||
slice_operation: None,
|
||||
remote_print: RemotePrint::uninitialized(),
|
||||
config_dir,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,17 +187,32 @@ impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
ctx.request_repaint();
|
||||
self.fps.update();
|
||||
self.remote_print.tick(&mut self.modal, &mut self.state);
|
||||
|
||||
match self.config.theme {
|
||||
Theme::Dark => ctx.set_visuals(Visuals::dark()),
|
||||
Theme::Light => ctx.set_visuals(Visuals::light()),
|
||||
}
|
||||
|
||||
match &mut self.modal {
|
||||
Some(modal) => modal.show_dialog(),
|
||||
None => self.modal = Some(Modal::new(ctx, "modal")),
|
||||
}
|
||||
|
||||
self.remote_print.tick(&mut self.modal, &mut self.state);
|
||||
windows::ui(self, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for App {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self.config.save(&self.config_dir) {
|
||||
warn!("Failed to save config: {}", err);
|
||||
return;
|
||||
}
|
||||
info!("Successfully saved config");
|
||||
}
|
||||
}
|
||||
|
||||
impl FpsTracker {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
|
62
mslicer/src/config.rs
Normal file
62
mslicer/src/config.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use std::{
|
||||
fs,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use eframe::Theme;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
|
||||
use crate::render::pipelines::model::RenderStyle;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub render_style: RenderStyle,
|
||||
pub grid_size: f32,
|
||||
pub theme: Theme,
|
||||
pub alert_print_completion: bool,
|
||||
pub init_remote_print_at_startup: bool,
|
||||
pub network_timeout: f32,
|
||||
pub network_broadcast_address: IpAddr,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(config_dir: &Path) -> Result<Self> {
|
||||
let config_file = config_dir.join("config.toml");
|
||||
Ok(if config_file.exists() {
|
||||
let file = fs::read(&config_file)?;
|
||||
let string = String::from_utf8_lossy(&file);
|
||||
let config = toml::from_str(&string)?;
|
||||
info!("Successfully loaded config file");
|
||||
config
|
||||
} else {
|
||||
info!("No config file found, using defaults");
|
||||
Self::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save(&self, config_dir: &Path) -> Result<()> {
|
||||
fs::create_dir_all(config_dir)?;
|
||||
|
||||
let config_file = config_dir.join("config.toml");
|
||||
let string = toml::to_string(self)?;
|
||||
fs::write(config_file, string)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
render_style: RenderStyle::Rended,
|
||||
theme: Theme::Dark,
|
||||
grid_size: 12.16,
|
||||
alert_print_completion: false,
|
||||
init_remote_print_at_startup: false,
|
||||
network_timeout: 5.0,
|
||||
network_broadcast_address: IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255)),
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,9 +12,11 @@ const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm;
|
||||
|
||||
mod app;
|
||||
mod components;
|
||||
mod config;
|
||||
mod remote_print;
|
||||
mod render;
|
||||
mod slice_operation;
|
||||
mod ui_state;
|
||||
mod windows;
|
||||
use app::App;
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
io::ErrorKind,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket},
|
||||
net::{IpAddr, SocketAddr, TcpListener, UdpSocket},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
thread::{self, JoinHandle},
|
||||
@@ -23,7 +23,7 @@ use remote_send::{
|
||||
Response,
|
||||
};
|
||||
|
||||
use crate::app::{RemotePrintConnectStatus, UiState};
|
||||
use crate::ui_state::{RemotePrintConnectStatus, UiState};
|
||||
|
||||
pub struct RemotePrint {
|
||||
services: Option<Arc<Services>>,
|
||||
@@ -157,12 +157,12 @@ impl RemotePrint {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scan_for_printers(&mut self) {
|
||||
pub fn scan_for_printers(&mut self, broadcast: IpAddr) {
|
||||
self.jobs.push(AsyncJob::new(
|
||||
thread::spawn(clone!(
|
||||
[{ self.printers } as printers, { self.services } as services],
|
||||
move || {
|
||||
scan_for_printers(services.unwrap(), printers)
|
||||
scan_for_printers(services.unwrap(), printers, broadcast)
|
||||
.context("Error scanning for printers.")
|
||||
}
|
||||
)),
|
||||
@@ -254,13 +254,15 @@ fn add_printer(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scan_for_printers(services: Arc<Services>, printers: Arc<Mutex<Vec<Printer>>>) -> Result<()> {
|
||||
const BROADCAST: IpAddr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255));
|
||||
|
||||
info!("Scanning for printers on {BROADCAST}");
|
||||
fn scan_for_printers(
|
||||
services: Arc<Services>,
|
||||
printers: Arc<Mutex<Vec<Printer>>>,
|
||||
broadcast: IpAddr,
|
||||
) -> Result<()> {
|
||||
info!("Scanning for printers on {broadcast}");
|
||||
services
|
||||
.udp
|
||||
.send_to(b"M99999", SocketAddr::new(BROADCAST, 3000))?;
|
||||
.send_to(b"M99999", SocketAddr::new(broadcast, 3000))?;
|
||||
|
||||
let mut buffer = [0; 1024];
|
||||
loop {
|
||||
|
@@ -2,6 +2,7 @@ use egui::epaint::util::OrderedFloat;
|
||||
use egui_wgpu::ScreenDescriptor;
|
||||
use encase::{ShaderType, UniformBuffer};
|
||||
use nalgebra::{Matrix4, Vector3, Vector4};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wgpu::{
|
||||
util::{BufferInitDescriptor, DeviceExt},
|
||||
BindGroup, BindGroupEntry, BindGroupLayout, BlendState, BufferUsages, ColorTargetState,
|
||||
@@ -39,7 +40,7 @@ struct ModelUniforms {
|
||||
render_style: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum RenderStyle {
|
||||
Normals,
|
||||
|
17
mslicer/src/ui_state.rs
Normal file
17
mslicer/src/ui_state.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use egui_tracing::EventCollector;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UiState {
|
||||
pub event_collector: EventCollector,
|
||||
pub working_address: String,
|
||||
pub send_print_completion: bool,
|
||||
pub remote_print_connecting: RemotePrintConnectStatus,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
pub enum RemotePrintConnectStatus {
|
||||
#[default]
|
||||
None,
|
||||
Connecting,
|
||||
Scanning,
|
||||
}
|
@@ -8,7 +8,7 @@ use egui::{
|
||||
use notify_rust::Notification;
|
||||
use remote_send::status::{FileTransferStatus, PrintInfoStatus};
|
||||
|
||||
use crate::app::{App, RemotePrintConnectStatus};
|
||||
use crate::{app::App, ui_state::RemotePrintConnectStatus};
|
||||
|
||||
enum Action {
|
||||
None,
|
||||
@@ -212,7 +212,8 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
||||
let height = scan.rect.height();
|
||||
if scan.clicked() {
|
||||
app.state.remote_print_connecting = RemotePrintConnectStatus::Scanning;
|
||||
app.remote_print.scan_for_printers();
|
||||
app.remote_print
|
||||
.scan_for_printers(app.config.network_broadcast_address);
|
||||
}
|
||||
|
||||
ui.add_sized(vec2(2.0, height), Separator::default());
|
||||
|
@@ -1,5 +1,5 @@
|
||||
use eframe::Theme;
|
||||
use egui::{ComboBox, Context, Ui, Visuals};
|
||||
use egui::{ComboBox, Context, Ui};
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
render::pipelines::model::RenderStyle,
|
||||
};
|
||||
|
||||
pub fn ui(app: &mut App, ui: &mut Ui, ctx: &Context) {
|
||||
pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
||||
ComboBox::new("render_style", "Render Style")
|
||||
.selected_text(app.config.render_style.name())
|
||||
.show_ui(ui, |ui| {
|
||||
@@ -19,7 +19,6 @@ pub fn ui(app: &mut App, ui: &mut Ui, ctx: &Context) {
|
||||
ui.selectable_value(&mut app.config.render_style, RenderStyle::Rended, "Rended");
|
||||
});
|
||||
|
||||
let last_theme = app.config.theme;
|
||||
ComboBox::new("theme", "Theme")
|
||||
.selected_text(match app.config.theme {
|
||||
Theme::Dark => "Dark",
|
||||
@@ -30,13 +29,6 @@ pub fn ui(app: &mut App, ui: &mut Ui, ctx: &Context) {
|
||||
ui.selectable_value(&mut app.config.theme, Theme::Light, "Light");
|
||||
});
|
||||
|
||||
if last_theme != app.config.theme {
|
||||
match app.config.theme {
|
||||
Theme::Dark => ctx.set_visuals(Visuals::dark()),
|
||||
Theme::Light => ctx.set_visuals(Visuals::light()),
|
||||
}
|
||||
}
|
||||
|
||||
dragger(ui, "Grid Size", &mut app.config.grid_size, |x| x.speed(0.1));
|
||||
|
||||
ui.collapsing("Camera", |ui| {
|
||||
|
Reference in New Issue
Block a user