Start on remote print UI
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2730,7 +2730,9 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"plexus",
|
"plexus",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"remote_send",
|
||||||
"rfd",
|
"rfd",
|
||||||
|
"serde_json",
|
||||||
"slicer",
|
"slicer",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -4054,9 +4056,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.117"
|
version = "1.0.120"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
1
TODO.md
1
TODO.md
@@ -47,3 +47,4 @@
|
|||||||
- [x] Disable slice button while slicing
|
- [x] Disable slice button while slicing
|
||||||
- [x] Use egui_dock to get a more clean look
|
- [x] Use egui_dock to get a more clean look
|
||||||
- [x] Align to bed button
|
- [x] Align to bed button
|
||||||
|
- [ ] Define all crate versions in workspace toml
|
||||||
|
@@ -21,10 +21,12 @@ parking_lot = "0.12.3"
|
|||||||
plexus = "0.0.11"
|
plexus = "0.0.11"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rfd = "0.14.1"
|
rfd = "0.14.1"
|
||||||
|
serde_json = "1.0.120"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
wgpu = "0.19.4"
|
wgpu = "0.19.4"
|
||||||
|
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
goo_format = { path = "../goo_format" }
|
goo_format = { path = "../goo_format" }
|
||||||
|
remote_send = { path = "../remote_send" }
|
||||||
slicer = { path = "../slicer" }
|
slicer = { path = "../slicer" }
|
||||||
|
@@ -11,6 +11,7 @@ use slicer::{slicer::Slicer, Pos};
|
|||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
remote_print::RemotePrint,
|
||||||
render::{camera::Camera, pipelines::model::RenderStyle, rendered_mesh::RenderedMesh},
|
render::{camera::Camera, pipelines::model::RenderStyle, rendered_mesh::RenderedMesh},
|
||||||
slice_operation::{SliceOperation, SliceResult},
|
slice_operation::{SliceOperation, SliceResult},
|
||||||
windows::{self, Tab},
|
windows::{self, Tab},
|
||||||
@@ -20,12 +21,14 @@ use goo_format::{File as GooFile, LayerEncoder, PreviewImage};
|
|||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub dock_state: DockState<Tab>,
|
pub dock_state: DockState<Tab>,
|
||||||
|
pub state: UiState,
|
||||||
modal: Option<Modal>,
|
modal: Option<Modal>,
|
||||||
|
|
||||||
pub camera: Camera,
|
pub camera: Camera,
|
||||||
pub slice_config: SliceConfig,
|
pub slice_config: SliceConfig,
|
||||||
pub meshes: Arc<RwLock<Vec<RenderedMesh>>>,
|
pub meshes: Arc<RwLock<Vec<RenderedMesh>>>,
|
||||||
pub slice_operation: Option<SliceOperation>,
|
pub slice_operation: Option<SliceOperation>,
|
||||||
|
pub remote_print: RemotePrint,
|
||||||
|
|
||||||
pub render_style: RenderStyle,
|
pub render_style: RenderStyle,
|
||||||
pub grid_size: f32,
|
pub grid_size: f32,
|
||||||
@@ -33,6 +36,11 @@ pub struct App {
|
|||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct UiState {
|
||||||
|
pub working_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FpsTracker {
|
pub struct FpsTracker {
|
||||||
last_frame: Instant,
|
last_frame: Instant,
|
||||||
last_frame_time: f32,
|
last_frame_time: f32,
|
||||||
@@ -166,6 +174,7 @@ impl Default for App {
|
|||||||
Self {
|
Self {
|
||||||
dock_state,
|
dock_state,
|
||||||
modal: None,
|
modal: None,
|
||||||
|
state: UiState::default(),
|
||||||
|
|
||||||
camera: Camera::default(),
|
camera: Camera::default(),
|
||||||
slice_config: SliceConfig {
|
slice_config: SliceConfig {
|
||||||
@@ -190,6 +199,7 @@ impl Default for App {
|
|||||||
theme: Theme::Dark,
|
theme: Theme::Dark,
|
||||||
grid_size: 12.16,
|
grid_size: 12.16,
|
||||||
slice_operation: None,
|
slice_operation: None,
|
||||||
|
remote_print: RemotePrint::uninitialized(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm;
|
|||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod components;
|
mod components;
|
||||||
|
mod remote_print;
|
||||||
mod render;
|
mod render;
|
||||||
mod slice_operation;
|
mod slice_operation;
|
||||||
mod windows;
|
mod windows;
|
||||||
|
112
mslicer/src/remote_print.rs
Normal file
112
mslicer/src/remote_print.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
use std::{
|
||||||
|
net::{IpAddr, SocketAddr, TcpListener, UdpSocket},
|
||||||
|
str::FromStr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use remote_send::{
|
||||||
|
http_server::HttpServer, mqtt::MqttServer, mqtt_server::Mqtt, status::FullStatusData, Response,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct RemotePrint {
|
||||||
|
services: Option<Services>,
|
||||||
|
printers: Arc<Mutex<Vec<Printer>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Services {
|
||||||
|
mqtt: Mqtt,
|
||||||
|
http: HttpServer,
|
||||||
|
udp: UdpSocket,
|
||||||
|
|
||||||
|
mqtt_port: u16,
|
||||||
|
http_port: u16,
|
||||||
|
udp_port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Printer {
|
||||||
|
pub data: FullStatusData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemotePrint {
|
||||||
|
pub fn uninitialized() -> Self {
|
||||||
|
Self {
|
||||||
|
services: None,
|
||||||
|
printers: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_initialized(&self) -> bool {
|
||||||
|
self.services.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printers(&self) -> MutexGuard<Vec<Printer>> {
|
||||||
|
self.printers.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self) -> Result<()> {
|
||||||
|
info!("Starting remote print services");
|
||||||
|
|
||||||
|
let mqtt_listener = TcpListener::bind("0.0.0.0:0")?;
|
||||||
|
let mqtt_port = mqtt_listener.local_addr()?.port();
|
||||||
|
let mqtt = Mqtt::new();
|
||||||
|
MqttServer::new(mqtt.clone()).start_async(mqtt_listener)?;
|
||||||
|
|
||||||
|
let http_listener = TcpListener::bind("0.0.0.0:0")?;
|
||||||
|
let http_port = http_listener.local_addr()?.port();
|
||||||
|
let http = HttpServer::new(http_listener);
|
||||||
|
http.start_async();
|
||||||
|
|
||||||
|
let udp = UdpSocket::bind("0.0.0.0:0")?;
|
||||||
|
let udp_port = udp.local_addr()?.port();
|
||||||
|
|
||||||
|
info!("Binds: {{ UDP: {udp_port}, MQTT: {mqtt_port}, HTTP: {http_port} }}");
|
||||||
|
|
||||||
|
self.services = Some(Services {
|
||||||
|
mqtt,
|
||||||
|
http,
|
||||||
|
udp,
|
||||||
|
|
||||||
|
mqtt_port,
|
||||||
|
http_port,
|
||||||
|
udp_port,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: async
|
||||||
|
pub fn add_printer(&mut self, address: &str) -> Result<()> {
|
||||||
|
let services = self.services.as_ref().unwrap();
|
||||||
|
|
||||||
|
let address = IpAddr::from_str(address)?;
|
||||||
|
let address = SocketAddr::new(address, 3000);
|
||||||
|
|
||||||
|
services.udp.send_to(b"M99999", address)?;
|
||||||
|
|
||||||
|
let mut buffer = [0; 1024];
|
||||||
|
let (len, _addr) = services.udp.recv_from(&mut buffer)?;
|
||||||
|
|
||||||
|
let received = String::from_utf8_lossy(&buffer[..len]);
|
||||||
|
let response = serde_json::from_str::<Response<FullStatusData>>(&received)?;
|
||||||
|
info!(
|
||||||
|
"Got status from `{}`",
|
||||||
|
response.data.attributes.machine_name
|
||||||
|
);
|
||||||
|
|
||||||
|
self.printers.lock().push(Printer {
|
||||||
|
data: response.data.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
services.mqtt.add_future_client(response);
|
||||||
|
|
||||||
|
services
|
||||||
|
.udp
|
||||||
|
.send_to(format!("M66666 {}", services.mqtt_port).as_bytes(), address)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@ use crate::{app::App, render::workspace::WorkspaceRenderCallback};
|
|||||||
|
|
||||||
mod about;
|
mod about;
|
||||||
mod models;
|
mod models;
|
||||||
|
mod remote_print;
|
||||||
mod slice_config;
|
mod slice_config;
|
||||||
mod slice_operation;
|
mod slice_operation;
|
||||||
mod stats;
|
mod stats;
|
||||||
@@ -22,10 +23,11 @@ struct Tabs<'a> {
|
|||||||
pub enum Tab {
|
pub enum Tab {
|
||||||
About,
|
About,
|
||||||
Models,
|
Models,
|
||||||
|
RemotePrint,
|
||||||
SliceConfig,
|
SliceConfig,
|
||||||
Stats,
|
Stats,
|
||||||
Workspace,
|
|
||||||
Viewport,
|
Viewport,
|
||||||
|
Workspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
@@ -33,10 +35,11 @@ impl Tab {
|
|||||||
match self {
|
match self {
|
||||||
Tab::About => "About",
|
Tab::About => "About",
|
||||||
Tab::Models => "Models",
|
Tab::Models => "Models",
|
||||||
|
Tab::RemotePrint => "Remote Print",
|
||||||
Tab::SliceConfig => "Slice Config",
|
Tab::SliceConfig => "Slice Config",
|
||||||
Tab::Stats => "Stats",
|
Tab::Stats => "Stats",
|
||||||
Tab::Workspace => "Workspace",
|
|
||||||
Tab::Viewport => "Viewport",
|
Tab::Viewport => "Viewport",
|
||||||
|
Tab::Workspace => "Workspace",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +55,7 @@ impl<'a> TabViewer for Tabs<'a> {
|
|||||||
match tab {
|
match tab {
|
||||||
Tab::About => about::ui(self.app, ui, self.ctx),
|
Tab::About => about::ui(self.app, ui, self.ctx),
|
||||||
Tab::Models => models::ui(self.app, ui, self.ctx),
|
Tab::Models => models::ui(self.app, ui, self.ctx),
|
||||||
|
Tab::RemotePrint => remote_print::ui(self.app, ui, self.ctx),
|
||||||
Tab::SliceConfig => slice_config::ui(self.app, ui, self.ctx),
|
Tab::SliceConfig => slice_config::ui(self.app, ui, self.ctx),
|
||||||
Tab::Stats => stats::ui(self.app, ui, self.ctx),
|
Tab::Stats => stats::ui(self.app, ui, self.ctx),
|
||||||
Tab::Viewport => viewport(self.app, ui, self.ctx),
|
Tab::Viewport => viewport(self.app, ui, self.ctx),
|
||||||
@@ -66,6 +70,7 @@ impl<'a> TabViewer for Tabs<'a> {
|
|||||||
for tab in [
|
for tab in [
|
||||||
Tab::About,
|
Tab::About,
|
||||||
Tab::Models,
|
Tab::Models,
|
||||||
|
Tab::RemotePrint,
|
||||||
Tab::SliceConfig,
|
Tab::SliceConfig,
|
||||||
Tab::Stats,
|
Tab::Stats,
|
||||||
Tab::Workspace,
|
Tab::Workspace,
|
||||||
|
64
mslicer/src/windows/remote_print.rs
Normal file
64
mslicer/src/windows/remote_print.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
use egui::{vec2, Align, Context, Layout, Separator, TextEdit, Ui};
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
|
||||||
|
pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
||||||
|
if !app.remote_print.is_initialized() {
|
||||||
|
ui.label("Remote print services have not been initialized.");
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
if ui.button("Initialize").clicked() {
|
||||||
|
app.remote_print.init().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.heading("Printers");
|
||||||
|
let printers = app.remote_print.printers();
|
||||||
|
if printers.is_empty() {
|
||||||
|
ui.label("No printers have been added yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, printer) in printers.iter().enumerate() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.strong(&printer.data.attributes.name);
|
||||||
|
ui.monospace(&printer.data.attributes.mainboard_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
if i + 1 != printers.len() {
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(printers);
|
||||||
|
|
||||||
|
ui.add_space(16.0);
|
||||||
|
ui.heading("Add Printer");
|
||||||
|
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||||
|
let scan = ui.button("Scan");
|
||||||
|
let height = scan.rect.height();
|
||||||
|
if scan.clicked() {
|
||||||
|
app.dialog_builder()
|
||||||
|
.with_title("Unimplemented")
|
||||||
|
.with_body("Printer scanning is not implemented yet.")
|
||||||
|
.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_sized(vec2(2.0, height), Separator::default());
|
||||||
|
if ui.button("Connect").clicked() {
|
||||||
|
app.remote_print
|
||||||
|
.add_printer(&app.state.working_address)
|
||||||
|
.unwrap();
|
||||||
|
app.state.working_address.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_sized(
|
||||||
|
vec2(ui.available_width(), height),
|
||||||
|
TextEdit::singleline(&mut app.state.working_address)
|
||||||
|
.hint_text("192.168.1.233")
|
||||||
|
.desired_width(ui.available_width()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@@ -14,7 +14,7 @@ pub struct Response<Data> {
|
|||||||
pub data: Data,
|
pub data: Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Resolution {
|
pub struct Resolution {
|
||||||
pub x: u16,
|
pub x: u16,
|
||||||
pub y: u16,
|
pub y: u16,
|
||||||
|
@@ -2,14 +2,14 @@ use serde::Deserialize;
|
|||||||
|
|
||||||
use crate::{parse_resolution, Resolution};
|
use crate::{parse_resolution, Resolution};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct FullStatusData {
|
pub struct FullStatusData {
|
||||||
pub attributes: Attributes,
|
pub attributes: Attributes,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct StatusData {
|
pub struct StatusData {
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
@@ -18,7 +18,7 @@ pub struct StatusData {
|
|||||||
pub time_stamp: u64,
|
pub time_stamp: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -40,14 +40,14 @@ pub struct Attributes {
|
|||||||
pub capabilities: Vec<Capability>,
|
pub capabilities: Vec<Capability>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum Capability {
|
pub enum Capability {
|
||||||
FileTransfer,
|
FileTransfer,
|
||||||
PrintControl,
|
PrintControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
pub current_status: u8,
|
pub current_status: u8,
|
||||||
@@ -56,7 +56,7 @@ pub struct Status {
|
|||||||
pub file_transfer_info: FileTransferInfo,
|
pub file_transfer_info: FileTransferInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct PrintInfo {
|
pub struct PrintInfo {
|
||||||
pub status: u8,
|
pub status: u8,
|
||||||
@@ -68,7 +68,7 @@ pub struct PrintInfo {
|
|||||||
pub filename: String,
|
pub filename: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct FileTransferInfo {
|
pub struct FileTransferInfo {
|
||||||
pub status: u8,
|
pub status: u8,
|
||||||
|
Reference in New Issue
Block a user