Implement load and saving projects
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -699,6 +699,15 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -1157,6 +1166,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"nalgebra 0.32.6",
|
"nalgebra 0.32.6",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2864,6 +2874,7 @@ name = "mslicer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clone-macro",
|
"clone-macro",
|
||||||
|
@@ -5,6 +5,7 @@ members = ["common", "goo_format", "mslicer", "remote_send", "slicer"]
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
afire = "3.0.0-alpha.3"
|
afire = "3.0.0-alpha.3"
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
|
bincode = "1.3.3"
|
||||||
bitflags = "2.5.0"
|
bitflags = "2.5.0"
|
||||||
bytemuck = "1.16.1"
|
bytemuck = "1.16.1"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
|
5
TODO.md
5
TODO.md
@@ -73,3 +73,8 @@
|
|||||||
- [x] Allow dragging slice operation preview
|
- [x] Allow dragging slice operation preview
|
||||||
- [x] Fix rotation on Z axis
|
- [x] Fix rotation on Z axis
|
||||||
- [x] Fix Z translation being doubled
|
- [x] Fix Z translation being doubled
|
||||||
|
- [ ] Merge goo_format changes into goo crate
|
||||||
|
- [ ] Implement .ctb format (see <https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc>)
|
||||||
|
- [ ] Undo / Redo
|
||||||
|
- [x] Close file menu if button clicked
|
||||||
|
- [ ] Allow dragging in project to load them?
|
||||||
|
@@ -6,3 +6,4 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra.workspace = true
|
nalgebra.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
use nalgebra::{Vector2, Vector3};
|
use nalgebra::{Vector2, Vector3};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
use crate::serde_impls::vector3f;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct SliceConfig {
|
pub struct SliceConfig {
|
||||||
|
#[serde(skip)]
|
||||||
pub platform_resolution: Vector2<u32>,
|
pub platform_resolution: Vector2<u32>,
|
||||||
|
#[serde(with = "vector3f")]
|
||||||
pub platform_size: Vector3<f32>,
|
pub platform_size: Vector3<f32>,
|
||||||
pub slice_height: f32,
|
pub slice_height: f32,
|
||||||
|
|
||||||
@@ -12,7 +17,7 @@ pub struct SliceConfig {
|
|||||||
pub transition_layers: u32,
|
pub transition_layers: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct ExposureConfig {
|
pub struct ExposureConfig {
|
||||||
pub exposure_time: f32,
|
pub exposure_time: f32,
|
||||||
pub lift_distance: f32,
|
pub lift_distance: f32,
|
||||||
|
@@ -3,3 +3,4 @@ pub mod image;
|
|||||||
pub mod misc;
|
pub mod misc;
|
||||||
pub mod oklab;
|
pub mod oklab;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
pub mod serde_impls;
|
||||||
|
88
common/src/serde_impls.rs
Normal file
88
common/src/serde_impls.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use nalgebra::Vector3;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod vector3f {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vector3<f32>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let [x, y, z] = <[f32; 3]>::deserialize(deserializer)?;
|
||||||
|
Ok(Vector3::new(x, y, z))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(data: &Vector3<f32>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
data.as_slice().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vector2u {
|
||||||
|
use nalgebra::Vector2;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vector2<u32>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let [x, y] = <[u32; 2]>::deserialize(deserializer)?;
|
||||||
|
Ok(Vector2::new(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(data: &Vector2<u32>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
data.as_slice().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vector3_list {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vector3<f32>>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let data = Vec::<f32>::deserialize(deserializer)?;
|
||||||
|
Ok(data
|
||||||
|
.chunks(3)
|
||||||
|
.map(|chunk| Vector3::new(chunk[0], chunk[1], chunk[2]))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(data: &[Vector3<f32>], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let out = data.iter().flat_map(|v| v.iter()).collect::<Vec<_>>();
|
||||||
|
out.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod index_list {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<[u32; 3]>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let data = Vec::<u32>::deserialize(deserializer)?;
|
||||||
|
Ok(data
|
||||||
|
.chunks(3)
|
||||||
|
.map(|chunk| [chunk[0], chunk[1], chunk[2]])
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(data: &[[u32; 3]], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let out = data.iter().flat_map(|v| v.iter()).collect::<Vec<_>>();
|
||||||
|
out.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
bincode.workspace = true
|
||||||
bytemuck.workspace = true
|
bytemuck.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
clone-macro.workspace = true
|
clone-macro.workspace = true
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
fs::File,
|
||||||
io::{BufRead, Seek},
|
io::{BufRead, Seek},
|
||||||
path::PathBuf,
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
thread,
|
thread,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use clone_macro::clone;
|
use clone_macro::clone;
|
||||||
use const_format::concatcp;
|
use const_format::concatcp;
|
||||||
use eframe::Theme;
|
use eframe::Theme;
|
||||||
@@ -25,6 +27,7 @@ use crate::{
|
|||||||
elephant_foot_fixer::{self},
|
elephant_foot_fixer::{self},
|
||||||
PluginManager,
|
PluginManager,
|
||||||
},
|
},
|
||||||
|
project::{BorrowedProject, OwnedProject},
|
||||||
remote_print::RemotePrint,
|
remote_print::RemotePrint,
|
||||||
render::{camera::Camera, rendered_mesh::RenderedMesh},
|
render::{camera::Camera, rendered_mesh::RenderedMesh},
|
||||||
slice_operation::{SliceOperation, SliceResult},
|
slice_operation::{SliceOperation, SliceResult},
|
||||||
@@ -225,6 +228,23 @@ impl App {
|
|||||||
.with_random_color(),
|
.with_random_color(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_project(&self, path: &Path) -> Result<()> {
|
||||||
|
let meshes = self.meshes.read();
|
||||||
|
let project = BorrowedProject::new(&meshes, &self.slice_config);
|
||||||
|
|
||||||
|
let mut file = File::create(path)?;
|
||||||
|
project.serialize(&mut file)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_project(&mut self, path: &Path) -> Result<()> {
|
||||||
|
let mut file = File::open(path)?;
|
||||||
|
let project = OwnedProject::deserialize(&mut file)?;
|
||||||
|
|
||||||
|
project.apply(self);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
|
@@ -13,6 +13,7 @@ const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm;
|
|||||||
mod app;
|
mod app;
|
||||||
mod config;
|
mod config;
|
||||||
mod plugins;
|
mod plugins;
|
||||||
|
mod project;
|
||||||
mod remote_print;
|
mod remote_print;
|
||||||
mod render;
|
mod render;
|
||||||
mod slice_operation;
|
mod slice_operation;
|
||||||
|
108
mslicer/src/project/mesh.rs
Normal file
108
mslicer/src/project/mesh.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use egui::Color32;
|
||||||
|
use nalgebra::Vector3;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::render::rendered_mesh::RenderedMesh;
|
||||||
|
use common::serde_impls::{index_list, vector3_list, vector3f};
|
||||||
|
use slicer::mesh::Mesh;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct OwnedProjectMesh {
|
||||||
|
info: ProjectMeshInfo,
|
||||||
|
|
||||||
|
#[serde(with = "vector3_list")]
|
||||||
|
vertices: Vec<Vector3<f32>>,
|
||||||
|
#[serde(with = "index_list")]
|
||||||
|
faces: Vec<[u32; 3]>,
|
||||||
|
#[serde(with = "vector3_list")]
|
||||||
|
normals: Vec<Vector3<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct BorrowedProjectMesh<'a> {
|
||||||
|
info: ProjectMeshInfo,
|
||||||
|
|
||||||
|
#[serde(with = "vector3_list")]
|
||||||
|
vertices: &'a [Vector3<f32>],
|
||||||
|
#[serde(with = "index_list")]
|
||||||
|
faces: &'a [[u32; 3]],
|
||||||
|
#[serde(with = "vector3_list")]
|
||||||
|
normals: &'a [Vector3<f32>],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ProjectMeshInfo {
|
||||||
|
name: String,
|
||||||
|
#[serde(with = "color32")]
|
||||||
|
color: Color32,
|
||||||
|
hidden: bool,
|
||||||
|
|
||||||
|
#[serde(with = "vector3f")]
|
||||||
|
position: Vector3<f32>,
|
||||||
|
#[serde(with = "vector3f")]
|
||||||
|
scale: Vector3<f32>,
|
||||||
|
#[serde(with = "vector3f")]
|
||||||
|
rotation: Vector3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedProjectMesh {
|
||||||
|
pub fn into_rendered_mesh(self) -> RenderedMesh {
|
||||||
|
let mut mesh = Mesh::new_uncentred(self.vertices, self.faces, self.normals);
|
||||||
|
mesh.set_position_unchecked(self.info.position);
|
||||||
|
mesh.set_scale_unchecked(self.info.scale);
|
||||||
|
mesh.set_rotation_unchecked(self.info.rotation);
|
||||||
|
mesh.update_transformation_matrix();
|
||||||
|
|
||||||
|
RenderedMesh::from_mesh(mesh)
|
||||||
|
.with_name(self.info.name)
|
||||||
|
.with_color(self.info.color)
|
||||||
|
.with_hidden(self.info.hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BorrowedProjectMesh<'a> {
|
||||||
|
pub fn from_rendered_mesh(rendered_mesh: &'a RenderedMesh) -> Self {
|
||||||
|
Self {
|
||||||
|
info: ProjectMeshInfo::from_rendered_mesh(rendered_mesh),
|
||||||
|
|
||||||
|
vertices: rendered_mesh.mesh.vertices(),
|
||||||
|
faces: rendered_mesh.mesh.faces(),
|
||||||
|
normals: rendered_mesh.mesh.normals(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectMeshInfo {
|
||||||
|
pub fn from_rendered_mesh(rendered_mesh: &RenderedMesh) -> Self {
|
||||||
|
Self {
|
||||||
|
name: rendered_mesh.name.clone(),
|
||||||
|
color: rendered_mesh.color,
|
||||||
|
hidden: rendered_mesh.hidden,
|
||||||
|
|
||||||
|
position: rendered_mesh.mesh.position(),
|
||||||
|
scale: rendered_mesh.mesh.scale(),
|
||||||
|
rotation: rendered_mesh.mesh.rotation(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod color32 {
|
||||||
|
use egui::Color32;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Color32, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let [r, g, b, a]: [u8; 4] = <[u8; 4]>::deserialize(deserializer)?;
|
||||||
|
Ok(Color32::from_rgba_premultiplied(r, g, b, a))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(data: &Color32, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
data.to_array().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
69
mslicer/src/project/mod.rs
Normal file
69
mslicer/src/project/mod.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{app::App, render::rendered_mesh::RenderedMesh};
|
||||||
|
use common::config::SliceConfig;
|
||||||
|
|
||||||
|
use mesh::{BorrowedProjectMesh, OwnedProjectMesh};
|
||||||
|
mod mesh;
|
||||||
|
|
||||||
|
const VERSION: u32 = 0;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct BorrowedProject<'a> {
|
||||||
|
meshes: Vec<BorrowedProjectMesh<'a>>,
|
||||||
|
slice_config: &'a SliceConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct OwnedProject {
|
||||||
|
meshes: Vec<OwnedProjectMesh>,
|
||||||
|
slice_config: SliceConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BorrowedProject<'a> {
|
||||||
|
pub fn new(meshes: &'a [RenderedMesh], slice_config: &'a SliceConfig) -> Self {
|
||||||
|
let meshes = meshes
|
||||||
|
.iter()
|
||||||
|
.map(BorrowedProjectMesh::from_rendered_mesh)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
meshes,
|
||||||
|
slice_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<Writer: Write>(&self, writer: &mut Writer) -> Result<()> {
|
||||||
|
writer.write_all(&VERSION.to_le_bytes())?;
|
||||||
|
bincode::serialize_into(writer, self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedProject {
|
||||||
|
pub fn deserialize<Reader: Read>(reader: &mut Reader) -> Result<Self> {
|
||||||
|
let mut version_bytes = [0; 4];
|
||||||
|
reader.read_exact(&mut version_bytes)?;
|
||||||
|
let version = u32::from_le_bytes(version_bytes);
|
||||||
|
|
||||||
|
if version != VERSION {
|
||||||
|
anyhow::bail!("Invalid version: Expected {VERSION} found {version}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(bincode::deserialize_from(reader).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(self, app: &mut App) {
|
||||||
|
let mut meshes = app.meshes.write();
|
||||||
|
*meshes = self
|
||||||
|
.meshes
|
||||||
|
.into_iter()
|
||||||
|
.map(|mesh| mesh.into_rendered_mesh())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
app.slice_config = self.slice_config;
|
||||||
|
}
|
||||||
|
}
|
@@ -75,6 +75,16 @@ impl RenderedMesh {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_color(mut self, color: Color32) -> Self {
|
||||||
|
self.color = color;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_hidden(mut self, hidden: bool) -> Self {
|
||||||
|
self.hidden = hidden;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_random_color(mut self) -> Self {
|
pub fn with_random_color(mut self) -> Self {
|
||||||
self.randomize_color();
|
self.randomize_color();
|
||||||
self
|
self
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use const_format::concatcp;
|
use const_format::concatcp;
|
||||||
use egui::{Context, Grid, Id, Ui};
|
use egui::{Context, Grid, Id, Ui};
|
||||||
use egui_phosphor::regular::{DICE_THREE, EYE, EYE_SLASH};
|
use egui_phosphor::regular::{ARROW_LINE_DOWN, COPY, DICE_THREE, EYE, EYE_SLASH, TRASH};
|
||||||
use slicer::Pos;
|
use slicer::Pos;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -54,18 +54,22 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
|||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.label("Actions");
|
ui.label("Actions");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.button("🗑 Delete")
|
ui.button(concatcp!(TRASH, " Delete"))
|
||||||
.clicked()
|
.clicked()
|
||||||
.then(|| action = Action::Remove(i));
|
.then(|| action = Action::Remove(i));
|
||||||
ui.button("🗋 Duplicate")
|
ui.button(concatcp!(COPY, " Duplicate"))
|
||||||
.clicked()
|
.clicked()
|
||||||
.then(|| action = Action::Duplicate(i));
|
.then(|| action = Action::Duplicate(i));
|
||||||
ui.button("⬇ Align to Bed")
|
ui.button(concatcp!(ARROW_LINE_DOWN, " Align to Bed"))
|
||||||
.clicked()
|
.clicked()
|
||||||
.then(|| mesh.align_to_bed());
|
.then(|| mesh.align_to_bed());
|
||||||
});
|
});
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Name");
|
||||||
|
ui.text_edit_singleline(&mut mesh.name);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Position");
|
ui.label("Position");
|
||||||
ui.add_space(20.0);
|
ui.add_space(20.0);
|
||||||
@@ -103,7 +107,7 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
|||||||
ui.label("Rotation");
|
ui.label("Rotation");
|
||||||
let mut rotation = rad_to_deg(mesh.mesh.rotation());
|
let mut rotation = rad_to_deg(mesh.mesh.rotation());
|
||||||
let original_rotation = rotation;
|
let original_rotation = rotation;
|
||||||
vec3_dragger(ui, rotation.as_mut(), |x| x);
|
vec3_dragger(ui, rotation.as_mut(), |x| x.suffix("°"));
|
||||||
(original_rotation != rotation)
|
(original_rotation != rotation)
|
||||||
.then(|| mesh.mesh.set_rotation(deg_to_rad(rotation)));
|
.then(|| mesh.mesh.set_rotation(deg_to_rad(rotation)));
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
use std::{fs::File, io::Write, mem, sync::Arc};
|
use std::{fs::File, io::Write, mem, sync::Arc};
|
||||||
|
|
||||||
|
use const_format::concatcp;
|
||||||
use egui::{
|
use egui::{
|
||||||
style::HandleShape, text::LayoutJob, Align, Button, Context, DragValue, FontSelection, Grid,
|
style::HandleShape, text::LayoutJob, Align, Button, Context, DragValue, FontSelection, Grid,
|
||||||
Id, Layout, ProgressBar, RichText, Sense, Slider, Style, Vec2, Window,
|
Id, Layout, ProgressBar, RichText, Sense, Slider, Style, Vec2, Window,
|
||||||
};
|
};
|
||||||
|
use egui_phosphor::regular::{FLOPPY_DISK_BACK, PAPER_PLANE_TILT};
|
||||||
use egui_wgpu::Callback;
|
use egui_wgpu::Callback;
|
||||||
use goo_format::LayerDecoder;
|
use goo_format::LayerDecoder;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
@@ -52,7 +54,9 @@ pub fn ui(app: &mut App, ctx: &Context) {
|
|||||||
ui.with_layout(Layout::default().with_cross_align(Align::Max), |ui| {
|
ui.with_layout(Layout::default().with_cross_align(Align::Max), |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_enabled_ui(app.remote_print.is_initialized(), |ui| {
|
ui.add_enabled_ui(app.remote_print.is_initialized(), |ui| {
|
||||||
ui.menu_button("Send to Printer", |ui| {
|
ui.menu_button(
|
||||||
|
concatcp!(PAPER_PLANE_TILT, " Send to Printer"),
|
||||||
|
|ui| {
|
||||||
let mqtt = app.remote_print.mqtt();
|
let mqtt = app.remote_print.mqtt();
|
||||||
for printer in app.remote_print.printers().iter() {
|
for printer in app.remote_print.printers().iter() {
|
||||||
let client = mqtt.get_client(&printer.mainboard_id);
|
let client = mqtt.get_client(&printer.mainboard_id);
|
||||||
@@ -74,7 +78,8 @@ pub fn ui(app: &mut App, ctx: &Context) {
|
|||||||
Align::LEFT,
|
Align::LEFT,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = app.slice_operation.as_ref().unwrap().result();
|
let result =
|
||||||
|
app.slice_operation.as_ref().unwrap().result();
|
||||||
let result = result.as_ref().unwrap();
|
let result = result.as_ref().unwrap();
|
||||||
|
|
||||||
let mut serializer = DynamicSerializer::new();
|
let mut serializer = DynamicSerializer::new();
|
||||||
@@ -86,10 +91,11 @@ pub fn ui(app: &mut App, ctx: &Context) {
|
|||||||
app.popup.open(name_popup(mainboard_id, data));
|
app.popup.open(name_popup(mainboard_id, data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if ui.button("Save").clicked() {
|
if ui.button(concatcp!(FLOPPY_DISK_BACK, " Save")).clicked() {
|
||||||
let result = app.slice_operation.as_ref().unwrap().result();
|
let result = app.slice_operation.as_ref().unwrap().result();
|
||||||
let result = result.as_ref().unwrap();
|
let result = result.as_ref().unwrap();
|
||||||
|
|
||||||
|
@@ -8,11 +8,17 @@ use const_format::concatcp;
|
|||||||
use egui::{Button, Context, Key, KeyboardShortcut, Modifiers, TopBottomPanel};
|
use egui::{Button, Context, Key, KeyboardShortcut, Modifiers, TopBottomPanel};
|
||||||
use egui_phosphor::regular::STACK;
|
use egui_phosphor::regular::STACK;
|
||||||
use rfd::FileDialog;
|
use rfd::FileDialog;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
ui::popup::{Popup, PopupIcon},
|
||||||
|
};
|
||||||
|
|
||||||
const IMPORT_MODEL_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::I);
|
const IMPORT_MODEL_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::I);
|
||||||
const LOAD_TEAPOT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::T);
|
const LOAD_TEAPOT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::T);
|
||||||
|
const SAVE_PROJECT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::S);
|
||||||
|
const LOAD_PROJECT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::O);
|
||||||
const QUIT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::Q);
|
const QUIT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::Q);
|
||||||
const SLICE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::R);
|
const SLICE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::R);
|
||||||
|
|
||||||
@@ -21,6 +27,10 @@ pub fn ui(app: &mut App, ctx: &Context) {
|
|||||||
.then(|| import_model(app));
|
.then(|| import_model(app));
|
||||||
ctx.input_mut(|x| x.consume_shortcut(&LOAD_TEAPOT_SHORTCUT))
|
ctx.input_mut(|x| x.consume_shortcut(&LOAD_TEAPOT_SHORTCUT))
|
||||||
.then(|| import_teapot(app));
|
.then(|| import_teapot(app));
|
||||||
|
ctx.input_mut(|x| x.consume_shortcut(&SAVE_PROJECT_SHORTCUT))
|
||||||
|
.then(|| save(app));
|
||||||
|
ctx.input_mut(|x| x.consume_shortcut(&LOAD_PROJECT_SHORTCUT))
|
||||||
|
.then(|| load(app));
|
||||||
ctx.input_mut(|x| x.consume_shortcut(&QUIT_SHORTCUT))
|
ctx.input_mut(|x| x.consume_shortcut(&QUIT_SHORTCUT))
|
||||||
.then(quit);
|
.then(quit);
|
||||||
ctx.input_mut(|x| x.consume_shortcut(&SLICE_SHORTCUT))
|
ctx.input_mut(|x| x.consume_shortcut(&SLICE_SHORTCUT))
|
||||||
@@ -49,14 +59,34 @@ pub fn ui(app: &mut App, ctx: &Context) {
|
|||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
let _ = ui.button("Save Project");
|
let save_project_button = ui.add(
|
||||||
let _ = ui.button("Load Project");
|
Button::new("Save Project")
|
||||||
|
.shortcut_text(ctx.format_shortcut(&SAVE_PROJECT_SHORTCUT)),
|
||||||
|
);
|
||||||
|
save_project_button.clicked().then(|| save(app));
|
||||||
|
|
||||||
|
let load_project_button = ui.add(
|
||||||
|
Button::new("Load Project")
|
||||||
|
.shortcut_text(ctx.format_shortcut(&LOAD_PROJECT_SHORTCUT)),
|
||||||
|
);
|
||||||
|
load_project_button.clicked().then(|| load(app));
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
let quit_button =
|
let quit_button =
|
||||||
ui.add(Button::new("Quit").shortcut_text(ctx.format_shortcut(&QUIT_SHORTCUT)));
|
ui.add(Button::new("Quit").shortcut_text(ctx.format_shortcut(&QUIT_SHORTCUT)));
|
||||||
quit_button.clicked().then(quit);
|
quit_button.clicked().then(quit);
|
||||||
|
|
||||||
|
// Close the menu if a button is clicked
|
||||||
|
for button in [
|
||||||
|
import_model_button,
|
||||||
|
import_teapot_button,
|
||||||
|
save_project_button,
|
||||||
|
load_project_button,
|
||||||
|
quit_button,
|
||||||
|
] {
|
||||||
|
button.clicked().then(|| ui.close_menu());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let slicing = match &app.slice_operation {
|
let slicing = match &app.slice_operation {
|
||||||
@@ -95,6 +125,38 @@ fn import_teapot(app: &mut App) {
|
|||||||
app.load_mesh(&mut buf, "stl", "Utah Teapot".into());
|
app.load_mesh(&mut buf, "stl", "Utah Teapot".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save(app: &mut App) {
|
||||||
|
if let Some(path) = FileDialog::new()
|
||||||
|
.add_filter("mslicer project", &["mslicer"])
|
||||||
|
.save_file()
|
||||||
|
{
|
||||||
|
if let Err(error) = app.save_project(&path) {
|
||||||
|
error!("Error saving project: {:?}", error);
|
||||||
|
app.popup.open(Popup::simple(
|
||||||
|
"Error Saving Project",
|
||||||
|
PopupIcon::Error,
|
||||||
|
error.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(app: &mut App) {
|
||||||
|
if let Some(path) = FileDialog::new()
|
||||||
|
.add_filter("mslicer project", &["mslicer"])
|
||||||
|
.pick_file()
|
||||||
|
{
|
||||||
|
if let Err(error) = app.load_project(&path) {
|
||||||
|
error!("Error loading project: {:?}", error);
|
||||||
|
app.popup.open(Popup::simple(
|
||||||
|
"Error Loading Project",
|
||||||
|
PopupIcon::Error,
|
||||||
|
error.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn quit() {
|
fn quit() {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user