feat(plot): add Print All workflow
This commit is contained in:
parent
734afd5611
commit
f6b9d6514e
11 changed files with 655 additions and 41 deletions
|
|
@ -2408,6 +2408,192 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn on_print_all_open(&mut self) -> Task<Message> {
|
||||
self.print_all_layouts = self.tabs[self.active_tab]
|
||||
.scene
|
||||
.layout_names()
|
||||
.into_iter()
|
||||
.filter(|name| name != "Model")
|
||||
.map(|name| (name, true))
|
||||
.collect();
|
||||
self.active_modal = Some(crate::app::ModalKind::PrintAll);
|
||||
self.reset_modal_geometry();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(super) fn on_print_all_options(&mut self) -> Task<Message> {
|
||||
let previous = self.plot_dialog.clone();
|
||||
let previous_style = self.active_plot_style.clone();
|
||||
let previous_window = self.plot_window;
|
||||
let task = self.on_plot_dialog_open();
|
||||
self.print_all_options_prev = Some(previous);
|
||||
self.print_all_plot_style_prev = Some(previous_style);
|
||||
self.print_all_plot_window_prev = Some(previous_window);
|
||||
self.print_all_options = true;
|
||||
self.plot_dialog.paper_space = true;
|
||||
self.plot_dialog.area = "Layout".into();
|
||||
task
|
||||
}
|
||||
|
||||
fn print_all_pages(&mut self) -> Result<Vec<crate::io::pdf_export::PdfPageInput>, String> {
|
||||
let available = self.tabs[self.active_tab].scene.layout_names();
|
||||
let selected: Vec<String> = self
|
||||
.print_all_layouts
|
||||
.iter()
|
||||
.filter(|(name, checked)| *checked && available.contains(name))
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
if selected.is_empty() {
|
||||
return Err(crate::t!("Select at least one layout.").into_owned());
|
||||
}
|
||||
|
||||
let i = self.active_tab;
|
||||
let original_layout = self.tabs[i].scene.current_layout.clone();
|
||||
let original_viewport = self.tabs[i].scene.active_viewport;
|
||||
let dialog = self.plot_dialog.clone();
|
||||
let mut pages = Vec::with_capacity(selected.len());
|
||||
for name in selected {
|
||||
// Plot helpers read the active layout. Swap only this transient
|
||||
// selector so the drawing's saved active-space metadata is not
|
||||
// touched while the owned page snapshot is collected.
|
||||
{
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
scene.current_layout = name;
|
||||
scene.active_viewport = None;
|
||||
}
|
||||
let (
|
||||
wires,
|
||||
hatches,
|
||||
wipeouts,
|
||||
group_splits,
|
||||
paper_w,
|
||||
paper_h,
|
||||
offset_x,
|
||||
offset_y,
|
||||
rotation_deg,
|
||||
scale,
|
||||
clip,
|
||||
) = self.layout_plot_params_for("Layout");
|
||||
pages.push(crate::io::pdf_export::PdfPageInput {
|
||||
wires,
|
||||
hatches,
|
||||
wipeouts,
|
||||
paper_w,
|
||||
paper_h,
|
||||
offset_x,
|
||||
offset_y,
|
||||
rotation_deg,
|
||||
scale,
|
||||
clip,
|
||||
options: Self::pdf_plot_options(&dialog, group_splits),
|
||||
});
|
||||
}
|
||||
self.tabs[i].scene.current_layout = original_layout;
|
||||
self.tabs[i].scene.active_viewport = original_viewport;
|
||||
Ok(pages)
|
||||
}
|
||||
|
||||
pub(super) fn on_print_all_pdf_path_some(
|
||||
&mut self,
|
||||
path: std::path::PathBuf,
|
||||
) -> Task<Message> {
|
||||
let dialog = self.plot_dialog.clone();
|
||||
if dialog.style_missing {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"Plot style table '{}' is not loaded.",
|
||||
dialog.style_name
|
||||
).as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
let pages = match self.print_all_pages() {
|
||||
Ok(pages) => pages,
|
||||
Err(error) => {
|
||||
self.command_line.push_error(&error);
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
let plot_style = self.dialog_plot_style(&dialog);
|
||||
let worker_path = path.clone();
|
||||
self.save_config();
|
||||
self.close_active_modal();
|
||||
let work = move || {
|
||||
crate::io::pdf_export::export_pdf_pages(
|
||||
&pages,
|
||||
&worker_path,
|
||||
plot_style.as_ref(),
|
||||
)
|
||||
.map(|_| format!("Exported {} layouts to {}", pages.len(), worker_path.display()))
|
||||
.map_err(|error| format!("Export failed: {error}"))
|
||||
};
|
||||
self.run_print_all_work(dialog.background, work)
|
||||
}
|
||||
|
||||
pub(super) fn on_print_all_print(&mut self) -> Task<Message> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.command_line.push_error(
|
||||
crate::t!("Printing is not available in the web version.").as_ref(),
|
||||
);
|
||||
Task::none()
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let dialog = self.plot_dialog.clone();
|
||||
if dialog.style_missing {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"Plot style table '{}' is not loaded.",
|
||||
dialog.style_name
|
||||
).as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
let pages = match self.print_all_pages() {
|
||||
Ok(pages) => pages,
|
||||
Err(error) => {
|
||||
self.command_line.push_error(&error);
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
let plot_style = self.dialog_plot_style(&dialog);
|
||||
let options = self.plot_print_options(&dialog, Default::default());
|
||||
let stamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|duration| duration.as_nanos())
|
||||
.unwrap_or(0);
|
||||
let temp_path = std::env::temp_dir().join(format!(
|
||||
"open_cad_studio_print_all_{stamp}.pdf"
|
||||
));
|
||||
self.save_config();
|
||||
self.close_active_modal();
|
||||
self.command_line.push_info(
|
||||
crate::t!("Sending selected layouts to the system printer…").as_ref(),
|
||||
);
|
||||
let work = move || {
|
||||
crate::io::pdf_export::export_pdf_pages(
|
||||
&pages,
|
||||
&temp_path,
|
||||
plot_style.as_ref(),
|
||||
)
|
||||
.and_then(|_| {
|
||||
crate::io::print_to_printer::print_existing_pdf(&temp_path, &options)
|
||||
})
|
||||
.map(|printer| format!("Sent {} layouts to printer: {printer}", pages.len()))
|
||||
.map_err(|error| format!("Print failed: {error}"))
|
||||
};
|
||||
self.run_print_all_work(dialog.background, work)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_print_all_work<F>(&mut self, background: bool, work: F) -> Task<Message>
|
||||
where
|
||||
F: FnOnce() -> Result<String, String> + Send + 'static,
|
||||
{
|
||||
if background {
|
||||
background_task(work, Message::PrintAllFinished)
|
||||
} else {
|
||||
Task::done(Message::PrintAllFinished(work()))
|
||||
}
|
||||
}
|
||||
|
||||
fn run_plot_work<F>(
|
||||
&mut self,
|
||||
background: bool,
|
||||
|
|
@ -2940,6 +3126,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
Task::none()
|
||||
}
|
||||
M::Area(s) => {
|
||||
if self.print_all_options && s != "Layout" {
|
||||
return Task::none();
|
||||
}
|
||||
// A paper Layout page setup commonly carries physical-sheet
|
||||
// origin and 1:1 scale. Those values are correct only for
|
||||
// Layout; carrying them into Window/Extents/Display moves a
|
||||
|
|
@ -3040,6 +3229,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
Task::none()
|
||||
}
|
||||
M::PickWindow => {
|
||||
if self.print_all_options {
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].scene.current_layout != "Model"
|
||||
&& self.tabs[i].scene.active_viewport.is_some()
|
||||
|
|
@ -3060,14 +3252,24 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
}
|
||||
M::SelectSetup(name) => {
|
||||
self.select_page_setup(&name);
|
||||
if self.print_all_options {
|
||||
self.plot_dialog.paper_space = true;
|
||||
self.plot_dialog.area = "Layout".into();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
M::SetCurrent => {
|
||||
if self.print_all_options {
|
||||
return Task::none();
|
||||
}
|
||||
self.apply_dialog_to_layout();
|
||||
self.command_line.push_info(crate::t!("Page setup applied to the layout.").as_ref());
|
||||
Task::none()
|
||||
}
|
||||
M::NewSetup => {
|
||||
if self.print_all_options {
|
||||
return Task::none();
|
||||
}
|
||||
// Create a setup from the current editor values, then start an
|
||||
// inline rename so the user can name it.
|
||||
let name = self.next_page_setup_name("Setup");
|
||||
|
|
@ -3081,6 +3283,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
Task::none()
|
||||
}
|
||||
M::CopySetup => {
|
||||
if self.print_all_options {
|
||||
return Task::none();
|
||||
}
|
||||
// Duplicate the selected entry — a layout OR a named setup —
|
||||
// into a new standalone named page setup.
|
||||
let sel = self.plot_dialog.selected_setup.clone();
|
||||
|
|
@ -3103,6 +3308,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
Task::none()
|
||||
}
|
||||
M::RenameStart(name) => {
|
||||
if self.print_all_options {
|
||||
return Task::none();
|
||||
}
|
||||
// Only standalone named setups can be renamed.
|
||||
if is_layout_entry(&name) || is_special_entry(&name) {
|
||||
return Task::none();
|
||||
|
|
@ -3116,6 +3324,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
Task::none()
|
||||
}
|
||||
M::DeleteSetup => {
|
||||
if self.print_all_options {
|
||||
return Task::none();
|
||||
}
|
||||
let sel = self.plot_dialog.selected_setup.clone();
|
||||
if !sel.is_empty() && !is_layout_entry(&sel) {
|
||||
self.tabs[self.active_tab].scene.page_setup_delete(&sel);
|
||||
|
|
@ -3153,7 +3364,30 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
self.plot_dialog.name_rename = false;
|
||||
Task::none()
|
||||
}
|
||||
M::Preview if self.print_all_options => Task::none(),
|
||||
M::Preview => self.on_plot_dlg_commit(true),
|
||||
M::Commit if self.print_all_options => {
|
||||
if self.plot_dialog.style_missing {
|
||||
self.command_line.push_error(crate::tf!(
|
||||
"Plot style table '{}' is not loaded.",
|
||||
self.plot_dialog.style_name
|
||||
).as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
self.plot_dialog.paper_space = true;
|
||||
self.plot_dialog.area = "Layout".into();
|
||||
self.sync_dialog_plot_runtime();
|
||||
self.save_config();
|
||||
self.print_all_options = false;
|
||||
self.print_all_options_prev = None;
|
||||
self.print_all_plot_style_prev = None;
|
||||
if let Some(previous) = self.print_all_plot_window_prev.take() {
|
||||
self.plot_window = previous;
|
||||
}
|
||||
self.active_modal = Some(crate::app::ModalKind::PrintAll);
|
||||
self.reset_modal_geometry();
|
||||
Task::none()
|
||||
}
|
||||
M::Commit => self.on_plot_dlg_commit(false),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,21 @@ impl OpenCADStudio {
|
|||
/// changes, and the ribbon tool that launched the dialog is de-highlighted.
|
||||
fn close_active_modal(&mut self) {
|
||||
use super::ModalKind::*;
|
||||
if self.active_modal == Some(Plot) && self.print_all_options {
|
||||
if let Some(previous) = self.print_all_options_prev.take() {
|
||||
self.plot_dialog = previous;
|
||||
}
|
||||
if let Some(previous) = self.print_all_plot_style_prev.take() {
|
||||
self.active_plot_style = previous;
|
||||
}
|
||||
if let Some(previous) = self.print_all_plot_window_prev.take() {
|
||||
self.plot_window = previous;
|
||||
}
|
||||
self.print_all_options = false;
|
||||
self.active_modal = Some(PrintAll);
|
||||
self.reset_modal_geometry();
|
||||
return;
|
||||
}
|
||||
if matches!(
|
||||
self.active_modal,
|
||||
Some(TextStyle | DimStyle | TableStyle | MLeaderStyle | MlStyle)
|
||||
|
|
@ -5461,6 +5476,73 @@ impl OpenCADStudio {
|
|||
}
|
||||
Message::PlotDialogOpen => self.on_plot_dialog_open(),
|
||||
Message::PlotDlg(m) => self.on_plot_dlg(m),
|
||||
Message::PrintAllOpen => self.on_print_all_open(),
|
||||
Message::PrintAllToggle(name) => {
|
||||
if let Some((_, selected)) = self
|
||||
.print_all_layouts
|
||||
.iter_mut()
|
||||
.find(|(layout, _)| layout == &name)
|
||||
{
|
||||
*selected = !*selected;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::PrintAllSelectAll => {
|
||||
for (_, selected) in &mut self.print_all_layouts {
|
||||
*selected = true;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::PrintAllSelectNone => {
|
||||
for (_, selected) in &mut self.print_all_layouts {
|
||||
*selected = false;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::PrintAllOptions => self.on_print_all_options(),
|
||||
Message::PrintAllPdf => {
|
||||
let i = self.active_tab;
|
||||
let stem = self.tabs[i]
|
||||
.current_path
|
||||
.as_deref()
|
||||
.and_then(|path| path.file_stem())
|
||||
.map(|name| format!("{}_layouts", name.to_string_lossy()))
|
||||
.unwrap_or_else(|| "drawing_layouts".into());
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let Some(window_id) = self.main_window else {
|
||||
return Task::done(Message::PrintAllPdfPath(None));
|
||||
};
|
||||
iced::window::run(window_id, move |parent| {
|
||||
crate::io::pdf_export::pick_pdf_path_owned(stem, parent)
|
||||
})
|
||||
.map(Message::PrintAllPdfPath)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = stem;
|
||||
self.command_line.push_error(
|
||||
crate::t!("PDF export is not available in the web version.").as_ref(),
|
||||
);
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::PrintAllPdfPath(None) => Task::none(),
|
||||
Message::PrintAllPdfPath(Some(path)) => self.on_print_all_pdf_path_some(path),
|
||||
Message::PrintAllPrint => self.on_print_all_print(),
|
||||
Message::PrintAllFinished(result) => {
|
||||
match result {
|
||||
Ok(message) => self.command_line.push_info(&message),
|
||||
Err(error) => {
|
||||
self.command_line.push_error(&error);
|
||||
if self.active_modal.is_none() {
|
||||
self.active_modal = Some(super::ModalKind::PrintAll);
|
||||
self.reset_modal_geometry();
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Plot / Export ─────────────────────────────────────────────
|
||||
Message::PlotExport => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue