mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 03:08:11 +08:00
Plotter
This commit is contained in:
@@ -183,6 +183,23 @@ pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
|
||||
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
|
||||
|
||||
// PLOTTER
|
||||
/// Address of the pen plotter print server, hard-coded for the conference booth LAN where it won't change.
|
||||
pub const PLOTTER_SERVER_ADDRESS: &str = "http://192.168.77.10:4747";
|
||||
/// Drawable area of letter paper in the plotter, in inches (portrait). The artwork's bounding box (the artboard is
|
||||
/// dropped) is scaled to fit, rotated to landscape when wider than tall.
|
||||
pub const PLOTTER_PAPER_SIZE_INCHES: (f64, f64) = (7.5, 10.);
|
||||
/// Fixed per-job overhead in seconds before the pen starts moving. Derived directly from timed plots: two versions of
|
||||
/// the same artwork plotted separately (3:24 + 12:17) minus both overlaid in a single job (15:23) leaves the setup
|
||||
/// time counted one extra time, giving 18 seconds.
|
||||
pub const PLOTTER_SETUP_SECONDS: f64 = 18.;
|
||||
/// Pen-down drawing speed in inches per second. Fitted (with the pen lift time below) to timed plots of the same
|
||||
/// artwork with solid strokes (349 in, 20 lifts, 3:24) and dash-baked strokes (176 in, 818 lifts, 12:17).
|
||||
pub const PLOTTER_PEN_SPEED_INCHES_PER_SECOND: f64 = 2.047;
|
||||
/// Seconds per pen lift/reposition/lower cycle, once per subpath. This also covers pen-up travel, which is not
|
||||
/// modeled by distance because the print server reorders paths to minimize it. Fitted alongside the pen speed above.
|
||||
pub const PLOTTER_PEN_LIFT_SECONDS: f64 = 0.774;
|
||||
|
||||
// INPUT
|
||||
pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ pub enum DialogMessage {
|
||||
NewDocumentDialog(NewDocumentDialogMessage),
|
||||
#[child]
|
||||
PreferencesDialog(PreferencesDialogMessage),
|
||||
#[child]
|
||||
SendToPlotterDialog(SendToPlotterDialogMessage),
|
||||
|
||||
// Messages
|
||||
Dismiss,
|
||||
@@ -22,6 +24,9 @@ pub enum DialogMessage {
|
||||
title: String,
|
||||
description: String,
|
||||
},
|
||||
DisplaySendToPlotterSuccess {
|
||||
job_name: String,
|
||||
},
|
||||
RequestAboutGraphiteDialog,
|
||||
RequestAboutGraphiteDialogWithLocalizedCommitDate {
|
||||
localized_commit_date: String,
|
||||
@@ -37,6 +42,7 @@ pub enum DialogMessage {
|
||||
},
|
||||
RequestNewDocumentDialog,
|
||||
RequestPreferencesDialog,
|
||||
RequestSendToPlotterDialog,
|
||||
RequestConfirmRestartDialog {
|
||||
preferences_requiring_restart: Vec<String>,
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::messages::dialog::simple_dialogs::{ConfirmRestartDialog, LicensesThir
|
||||
use crate::messages::frontend::utility_types::ExportBounds;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::vector::style::RenderMode;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DialogMessageContext<'a> {
|
||||
@@ -18,6 +19,7 @@ pub struct DialogMessageHandler {
|
||||
export_dialog: ExportDialogMessageHandler,
|
||||
new_document_dialog: NewDocumentDialogMessageHandler,
|
||||
preferences_dialog: PreferencesDialogMessageHandler,
|
||||
send_to_plotter_dialog: SendToPlotterDialogMessageHandler,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -29,6 +31,7 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
||||
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }),
|
||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
|
||||
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }),
|
||||
DialogMessage::SendToPlotterDialog(message) => self.send_to_plotter_dialog.process_message(message, responses, SendToPlotterDialogMessageContext { portfolio }),
|
||||
|
||||
DialogMessage::Dismiss => {
|
||||
if let Some(message) = self.on_dismiss.take() {
|
||||
@@ -60,6 +63,11 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
||||
let dialog = simple_dialogs::ErrorDialog { title, description };
|
||||
dialog.send_dialog_to_frontend(responses);
|
||||
}
|
||||
DialogMessage::DisplaySendToPlotterSuccess { job_name } => {
|
||||
self.on_dismiss = None;
|
||||
let dialog = simple_dialogs::SendToPlotterSuccessDialog { job_name };
|
||||
dialog.send_dialog_to_frontend(responses);
|
||||
}
|
||||
DialogMessage::RequestAboutGraphiteDialog => {
|
||||
self.on_dismiss = Some(DialogMessage::Close.into());
|
||||
responses.add(FrontendMessage::TriggerAboutGraphiteLocalizedCommitDate {
|
||||
@@ -136,6 +144,23 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
||||
self.on_dismiss = Some(PreferencesDialogMessage::Confirm.into());
|
||||
self.preferences_dialog.send_dialog_to_frontend(responses, preferences);
|
||||
}
|
||||
DialogMessage::RequestSendToPlotterDialog => {
|
||||
self.on_dismiss = Some(DialogMessage::Close.into());
|
||||
if let Some(document) = portfolio.active_document() {
|
||||
// Switch the viewport to outline mode so the artwork previews as the pen plotter will draw it
|
||||
responses.add(DocumentMessage::SetRenderMode { render_mode: RenderMode::Outline });
|
||||
|
||||
// Kick off a render of the plotter SVG purely to measure it for the time estimate shown in the dialog
|
||||
responses.add(PortfolioMessage::SubmitPlotterExport {
|
||||
job_name: document.name.clone(),
|
||||
estimate_only: true,
|
||||
});
|
||||
|
||||
self.send_to_plotter_dialog.job_name = document.name.clone();
|
||||
self.send_to_plotter_dialog.estimate = crate::messages::dialog::send_to_plotter_dialog::PlotTimeEstimate::Pending;
|
||||
self.send_to_plotter_dialog.send_dialog_to_frontend(responses);
|
||||
}
|
||||
}
|
||||
DialogMessage::RequestConfirmRestartDialog { preferences_requiring_restart } => {
|
||||
self.on_dismiss = Some(DialogMessage::Close.into());
|
||||
let dialog = ConfirmRestartDialog { preferences_requiring_restart };
|
||||
@@ -149,5 +174,6 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
||||
RequestExportDialog,
|
||||
RequestNewDocumentDialog,
|
||||
RequestPreferencesDialog,
|
||||
RequestSendToPlotterDialog,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ mod dialog_message_handler;
|
||||
pub mod export_dialog;
|
||||
pub mod new_document_dialog;
|
||||
pub mod preferences_dialog;
|
||||
pub mod send_to_plotter_dialog;
|
||||
pub mod simple_dialogs;
|
||||
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
mod send_to_plotter_dialog_message;
|
||||
mod send_to_plotter_dialog_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use send_to_plotter_dialog_message::{SendToPlotterDialogMessage, SendToPlotterDialogMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use send_to_plotter_dialog_message_handler::{PlotTimeEstimate, SendToPlotterDialogMessageContext, SendToPlotterDialogMessageHandler, estimated_plot_seconds};
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[impl_message(Message, DialogMessage, SendToPlotterDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum SendToPlotterDialogMessage {
|
||||
JobName { name: String },
|
||||
UpdateTimeEstimate { seconds: Option<f64> },
|
||||
|
||||
Submit,
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
use crate::consts::{PLOTTER_PAPER_SIZE_INCHES, PLOTTER_PEN_LIFT_SECONDS, PLOTTER_PEN_SPEED_INCHES_PER_SECOND, PLOTTER_SETUP_SECONDS};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::renderer::plot_statistics::PlotStatistics;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct SendToPlotterDialogMessageContext<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
}
|
||||
|
||||
/// How long the plotter is expected to take on the current document, shown in the dialog.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
pub enum PlotTimeEstimate {
|
||||
/// The estimate render is still in flight.
|
||||
#[default]
|
||||
Pending,
|
||||
/// The estimate could not be computed.
|
||||
Unavailable,
|
||||
/// Estimated plot duration in seconds.
|
||||
Seconds(f64),
|
||||
}
|
||||
|
||||
/// A dialog to send the current document to the pen plotter print server as an SVG.
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct SendToPlotterDialogMessageHandler {
|
||||
pub job_name: String,
|
||||
pub estimate: PlotTimeEstimate,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<SendToPlotterDialogMessage, SendToPlotterDialogMessageContext<'_>> for SendToPlotterDialogMessageHandler {
|
||||
fn process_message(&mut self, message: SendToPlotterDialogMessage, responses: &mut VecDeque<Message>, context: SendToPlotterDialogMessageContext) {
|
||||
let SendToPlotterDialogMessageContext { portfolio } = context;
|
||||
|
||||
match message {
|
||||
SendToPlotterDialogMessage::JobName { name } => self.job_name = name,
|
||||
|
||||
SendToPlotterDialogMessage::UpdateTimeEstimate { seconds } => {
|
||||
self.estimate = match seconds {
|
||||
Some(seconds) => PlotTimeEstimate::Seconds(seconds),
|
||||
None => PlotTimeEstimate::Unavailable,
|
||||
};
|
||||
|
||||
// Refresh only the dialog content, since re-displaying the whole dialog would reopen it if the user has already closed it
|
||||
self.send_layout(responses, LayoutTarget::DialogColumn1);
|
||||
return;
|
||||
}
|
||||
|
||||
SendToPlotterDialogMessage::Submit => {
|
||||
// Fall back to the document name so the attendant can still tell jobs apart if the field was cleared
|
||||
let job_name = if self.job_name.trim().is_empty() {
|
||||
portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default()
|
||||
} else {
|
||||
self.job_name.clone()
|
||||
};
|
||||
|
||||
responses.add_front(PortfolioMessage::SubmitPlotterExport { job_name, estimate_only: false });
|
||||
}
|
||||
}
|
||||
|
||||
self.send_dialog_to_frontend(responses);
|
||||
}
|
||||
|
||||
advertise_actions!(SendToPlotterDialogUpdate;
|
||||
);
|
||||
}
|
||||
|
||||
impl DialogLayoutHolder for SendToPlotterDialogMessageHandler {
|
||||
const ICON: &'static str = "File";
|
||||
const TITLE: &'static str = "Send to Plotter";
|
||||
|
||||
fn layout_buttons(&self) -> Layout {
|
||||
let widgets = vec![
|
||||
TextButton::new("Send")
|
||||
.emphasized(true)
|
||||
.on_update(|_| {
|
||||
DialogMessage::CloseAndThen {
|
||||
followups: vec![SendToPlotterDialogMessage::Submit.into()],
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_instance(),
|
||||
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
||||
];
|
||||
|
||||
Layout(vec![LayoutGroup::row(widgets)])
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutHolder for SendToPlotterDialogMessageHandler {
|
||||
fn layout(&self) -> Layout {
|
||||
let job_name = vec![
|
||||
TextLabel::new("Job Name").table_align(true).min_width(100).widget_instance(),
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
TextInput::new(&self.job_name)
|
||||
.on_update(|text_input: &TextInput| SendToPlotterDialogMessage::JobName { name: text_input.value.clone() }.into())
|
||||
.min_width(200)
|
||||
.widget_instance(),
|
||||
];
|
||||
|
||||
let estimate_text = match self.estimate {
|
||||
PlotTimeEstimate::Pending => "Estimating…".to_string(),
|
||||
PlotTimeEstimate::Unavailable => "Unknown".to_string(),
|
||||
PlotTimeEstimate::Seconds(seconds) => format_plot_duration(seconds),
|
||||
};
|
||||
let estimated_time = vec![
|
||||
TextLabel::new("Estimated Time").table_align(true).min_width(100).widget_instance(),
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
TextLabel::new(estimate_text).widget_instance(),
|
||||
];
|
||||
|
||||
Layout(vec![LayoutGroup::row(job_name), LayoutGroup::row(estimated_time)])
|
||||
}
|
||||
}
|
||||
|
||||
/// Estimates how long the plotter will take to draw the given SVG, in seconds.
|
||||
///
|
||||
/// The artwork's bounding box is scaled to fit the paper (rotated to landscape when wider than tall), then timed as a
|
||||
/// fixed setup cost, pen-down drawing at a constant speed, and a fixed cost per pen lift (which also covers pen-up
|
||||
/// travel, since the print server reorders paths to minimize it). The pen draws every path as its outline, so total
|
||||
/// path length approximates the pen-down distance regardless of fills. Intricate fine detail like text tends to run
|
||||
/// over this estimate because the machine cannot reach full speed on tiny curves.
|
||||
pub fn estimated_plot_seconds(statistics: &PlotStatistics) -> f64 {
|
||||
let (paper_width, paper_height) = if statistics.width > statistics.height {
|
||||
(PLOTTER_PAPER_SIZE_INCHES.1, PLOTTER_PAPER_SIZE_INCHES.0)
|
||||
} else {
|
||||
PLOTTER_PAPER_SIZE_INCHES
|
||||
};
|
||||
let scale = if statistics.width > 0. && statistics.height > 0. {
|
||||
(paper_width / statistics.width).min(paper_height / statistics.height)
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
||||
let pen_down_inches = statistics.pen_down_distance * scale;
|
||||
|
||||
PLOTTER_SETUP_SECONDS + pen_down_inches / PLOTTER_PEN_SPEED_INCHES_PER_SECOND + statistics.pen_lift_count as f64 * PLOTTER_PEN_LIFT_SECONDS
|
||||
}
|
||||
|
||||
/// Formats an estimated duration like "About 3 min 20 sec", rounded to the nearest 5 seconds.
|
||||
fn format_plot_duration(seconds: f64) -> String {
|
||||
let total = (((seconds / 5.).round() * 5.).max(5.)) as u64;
|
||||
let minutes = total / 60;
|
||||
let seconds = total % 60;
|
||||
|
||||
if minutes == 0 {
|
||||
format!("About {seconds} sec")
|
||||
} else if seconds == 0 {
|
||||
format!("About {minutes} min")
|
||||
} else {
|
||||
format!("About {minutes} min {seconds} sec")
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ mod failed_to_load_documents_dialog;
|
||||
mod failed_to_open_document_dialog;
|
||||
mod licenses_dialog;
|
||||
mod licenses_third_party_dialog;
|
||||
mod send_to_plotter_success_dialog;
|
||||
|
||||
pub use about_graphite_dialog::AboutGraphiteDialog;
|
||||
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
||||
@@ -20,3 +21,4 @@ pub use failed_to_load_documents_dialog::FailedToLoadDocumentsDialog;
|
||||
pub use failed_to_open_document_dialog::FailedToOpenDocumentDialog;
|
||||
pub use licenses_dialog::LicensesDialog;
|
||||
pub use licenses_third_party_dialog::LicensesThirdPartyDialog;
|
||||
pub use send_to_plotter_success_dialog::SendToPlotterSuccessDialog;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
/// A dialog to confirm that a job was successfully queued on the pen plotter print server.
|
||||
pub struct SendToPlotterSuccessDialog {
|
||||
pub job_name: String,
|
||||
}
|
||||
|
||||
impl DialogLayoutHolder for SendToPlotterSuccessDialog {
|
||||
const ICON: &'static str = "CheckboxChecked";
|
||||
const TITLE: &'static str = "Send to Plotter";
|
||||
|
||||
fn layout_buttons(&self) -> Layout {
|
||||
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
||||
|
||||
Layout(vec![LayoutGroup::row(widgets)])
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutHolder for SendToPlotterSuccessDialog {
|
||||
fn layout(&self) -> Layout {
|
||||
Layout(vec![
|
||||
LayoutGroup::row(vec![TextLabel::new("Sent to the plotter queue").bold(true).widget_instance()]),
|
||||
LayoutGroup::row(vec![
|
||||
TextLabel::new(format!(
|
||||
"The job \"{}\" was added to the queue.\nA booth attendant will start the plot from the dashboard.",
|
||||
self.job_name
|
||||
))
|
||||
.multiline(true)
|
||||
.widget_instance(),
|
||||
]),
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,11 @@ pub enum FrontendMessage {
|
||||
mime: String,
|
||||
size: (f64, f64),
|
||||
},
|
||||
TriggerSendToPlotter {
|
||||
name: String,
|
||||
svg: String,
|
||||
address: String,
|
||||
},
|
||||
TriggerFetchAndOpenDocument {
|
||||
name: String,
|
||||
filename: String,
|
||||
|
||||
@@ -453,6 +453,7 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
|
||||
// DialogMessage
|
||||
entry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog),
|
||||
entry!(KeyDown(KeyN); modifiers=[Accel], action_dispatch=DialogMessage::RequestNewDocumentDialog),
|
||||
entry!(KeyDown(KeyP); modifiers=[Accel], action_dispatch=DialogMessage::RequestSendToPlotterDialog),
|
||||
entry!(KeyDown(Comma); modifiers=[Accel], action_dispatch=DialogMessage::RequestPreferencesDialog),
|
||||
//
|
||||
// DebugMessage
|
||||
|
||||
@@ -172,6 +172,12 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
.tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestExportDialog))
|
||||
.on_commit(|_| DialogMessage::RequestExportDialog.into())
|
||||
.disabled(no_active_document),
|
||||
MenuListEntry::new("Send to Plotter…")
|
||||
.label("Send to Plotter…")
|
||||
.icon("FileExport")
|
||||
.tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestSendToPlotterDialog))
|
||||
.on_commit(|_| DialogMessage::RequestSendToPlotterDialog.into())
|
||||
.disabled(no_active_document),
|
||||
],
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
vec![preferences],
|
||||
|
||||
@@ -1323,6 +1323,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
DocumentMessage::SetRenderMode { render_mode } => {
|
||||
self.render_mode = render_mode;
|
||||
responses.add_front(NodeGraphMessage::RunDocumentGraph);
|
||||
// Keep the document bar's render mode radio buttons in sync when the mode is set programmatically
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
DocumentMessage::AddTransaction => {
|
||||
// Reverse order since they are added to the front
|
||||
|
||||
@@ -197,6 +197,10 @@ pub enum PortfolioMessage {
|
||||
artboard_name: Option<String>,
|
||||
artboard_count: usize,
|
||||
},
|
||||
SubmitPlotterExport {
|
||||
job_name: String,
|
||||
estimate_only: bool,
|
||||
},
|
||||
SubmitActiveGraphRender,
|
||||
SubmitGraphRender {
|
||||
document_id: DocumentId,
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::application::{Editor, generate_uuid};
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, GDD_FILE_EXTENSION};
|
||||
use crate::messages::animation::TimingInformation;
|
||||
use crate::messages::dialog::simple_dialogs;
|
||||
use crate::messages::frontend::utility_types::{DocumentInfo, PersistedState};
|
||||
use crate::messages::frontend::utility_types::{DocumentInfo, ExportBounds, FileType, PersistedState};
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
@@ -1461,6 +1461,32 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
});
|
||||
}
|
||||
}
|
||||
PortfolioMessage::SubmitPlotterExport { job_name, estimate_only } => {
|
||||
let document_id = self.active_document_id.expect("Tried to render non-existent document");
|
||||
let document = self.documents.get_mut(&document_id).expect("Tried to render non-existent document");
|
||||
let export_config = ExportConfig {
|
||||
name: job_name,
|
||||
file_type: FileType::Svg,
|
||||
scale_factor: 1.,
|
||||
bounds: ExportBounds::AllArtwork,
|
||||
for_plotter: true,
|
||||
plotter_estimate_only: estimate_only,
|
||||
..Default::default()
|
||||
};
|
||||
let result = self.executor.submit_document_export(document, document_id, export_config);
|
||||
|
||||
if let Err(description) = result {
|
||||
// A failed estimate render (e.g. an empty document) just marks the estimate as unavailable
|
||||
if estimate_only {
|
||||
responses.add(DialogMessage::SendToPlotterDialog(SendToPlotterDialogMessage::UpdateTimeEstimate { seconds: None }));
|
||||
} else {
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
title: "Unable to send to the plotter".to_string(),
|
||||
description,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
PortfolioMessage::SubmitActiveGraphRender => {
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
|
||||
|
||||
@@ -14,6 +14,7 @@ pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMe
|
||||
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
||||
pub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler};
|
||||
pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageContext, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};
|
||||
pub use crate::messages::dialog::send_to_plotter_dialog::{SendToPlotterDialogMessage, SendToPlotterDialogMessageContext, SendToPlotterDialogMessageDiscriminant, SendToPlotterDialogMessageHandler};
|
||||
pub use crate::messages::dialog::{DialogMessage, DialogMessageContext, DialogMessageDiscriminant, DialogMessageHandler};
|
||||
pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
pub use crate::messages::future::{FutureMessage, FutureMessageContext, FutureMessageDiscriminant, FutureMessageHandler, MessageFuture, MessageSpawner, Wake};
|
||||
|
||||
@@ -196,6 +196,8 @@ impl NodeGraphExecutor {
|
||||
render_mode: document.render_mode,
|
||||
for_export: false,
|
||||
for_eyedropper: false,
|
||||
hide_artboard_background: false,
|
||||
bake_stroke_dashes: false,
|
||||
};
|
||||
|
||||
// Execute the node graph
|
||||
@@ -267,6 +269,8 @@ impl NodeGraphExecutor {
|
||||
render_mode,
|
||||
for_export: false,
|
||||
for_eyedropper: true,
|
||||
hide_artboard_background: false,
|
||||
bake_stroke_dashes: false,
|
||||
};
|
||||
|
||||
// Execute the node graph
|
||||
@@ -315,15 +319,25 @@ impl NodeGraphExecutor {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// The plotter flow switches the viewport to Outline mode as a preview, so pin its export to the normal render
|
||||
// mode for a deterministic serialization regardless of what the viewport is showing
|
||||
let render_mode = if export_config.for_plotter {
|
||||
graphene_std::vector::style::RenderMode::Normal
|
||||
} else {
|
||||
document.render_mode
|
||||
};
|
||||
|
||||
let render_config = RenderConfig {
|
||||
viewport,
|
||||
scale: export_config.scale_factor,
|
||||
time: Default::default(),
|
||||
pointer: DVec2::ZERO,
|
||||
export_format,
|
||||
render_mode: document.render_mode,
|
||||
render_mode,
|
||||
for_export: true,
|
||||
for_eyedropper: false,
|
||||
hide_artboard_background: export_config.for_plotter,
|
||||
bake_stroke_dashes: export_config.for_plotter,
|
||||
};
|
||||
export_config.size = resolution;
|
||||
|
||||
@@ -568,6 +582,8 @@ impl NodeGraphExecutor {
|
||||
render_mode: document.render_mode,
|
||||
for_export: false,
|
||||
for_eyedropper: false,
|
||||
hide_artboard_background: false,
|
||||
bake_stroke_dashes: false,
|
||||
};
|
||||
let execution_id = self.queue_execution(render_config);
|
||||
self.futures.push_back((
|
||||
@@ -733,6 +749,8 @@ impl NodeGraphExecutor {
|
||||
size,
|
||||
artboard_name,
|
||||
artboard_count,
|
||||
for_plotter,
|
||||
plotter_estimate_only,
|
||||
..
|
||||
} = export_config;
|
||||
|
||||
@@ -753,7 +771,18 @@ impl NodeGraphExecutor {
|
||||
data: RenderOutputType::Svg { svg, .. },
|
||||
..
|
||||
}) => {
|
||||
if file_type == FileType::Svg {
|
||||
if plotter_estimate_only {
|
||||
// Measure the SVG the plotter would receive and report the time estimate back to the dialog
|
||||
let seconds =
|
||||
graphene_std::renderer::plot_statistics::svg_plot_statistics(&svg).map(|statistics| crate::messages::dialog::send_to_plotter_dialog::estimated_plot_seconds(&statistics));
|
||||
responses.add(DialogMessage::SendToPlotterDialog(SendToPlotterDialogMessage::UpdateTimeEstimate { seconds }));
|
||||
} else if for_plotter {
|
||||
responses.add(FrontendMessage::TriggerSendToPlotter {
|
||||
name: base_name,
|
||||
svg,
|
||||
address: crate::consts::PLOTTER_SERVER_ADDRESS.to_string(),
|
||||
});
|
||||
} else if file_type == FileType::Svg {
|
||||
responses.add(FrontendMessage::TriggerSaveFile {
|
||||
name,
|
||||
folder,
|
||||
|
||||
@@ -91,6 +91,10 @@ pub struct ExportConfig {
|
||||
pub size: UVec2,
|
||||
pub artboard_name: Option<String>,
|
||||
pub artboard_count: usize,
|
||||
/// Send the exported SVG to the pen plotter print server instead of saving it as a file.
|
||||
pub for_plotter: bool,
|
||||
/// Only measure the plotter SVG for a time estimate instead of sending it to the print server.
|
||||
pub plotter_estimate_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
Reference in New Issue
Block a user