Add a real Export to CAM ribbon button + filename dialog
Previously the CMMS's STL-export-and-navigate flow was only reachable by typing STLOUT/EXPORTSTL into the command line, with no visible button and no way to choose the filename. Adds a proper UI entry point: - Ribbon: a new quick-access button next to Save As (same style/position, GEAR icon - no dedicated CAM/CNC icon asset exists), command EXPORTCAM. - A filename dialog (ModalKind::CamExportDialog, cam_export_filename field) mirrors the existing Save-As dialog's shape minus the format picker (always STL) - defaults to "<drawing name>.stl", editable before confirming. - STLOUT/EXPORTSTL commands are unchanged (still instant-export using the drawing's own name, no popup) - this is a second, separate entry point, not a replacement.
This commit is contained in:
parent
3f8b69c71b
commit
56d1c0370e
5 changed files with 152 additions and 0 deletions
|
|
@ -528,6 +528,13 @@ impl OpenCADStudio {
|
||||||
return Some(Task::done(Message::StlExport));
|
return Some(Task::done(Message::StlExport));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CMMS embedding (not upstream): "Export to CAM" ribbon button -
|
||||||
|
// opens a filename dialog first, unlike STLOUT/EXPORTSTL above
|
||||||
|
// which export immediately using the drawing's own name.
|
||||||
|
"EXPORTCAM" => {
|
||||||
|
return Some(Task::done(Message::CamExportDialogOpen));
|
||||||
|
}
|
||||||
|
|
||||||
// STEPOUT — export 3D meshes to STEP AP203 format
|
// STEPOUT — export 3D meshes to STEP AP203 format
|
||||||
"STEPOUT" | "EXPORTSTEP" | "STPOUT" => {
|
"STEPOUT" | "EXPORTSTEP" | "STPOUT" => {
|
||||||
return Some(Task::done(Message::StepExport));
|
return Some(Task::done(Message::StepExport));
|
||||||
|
|
|
||||||
|
|
@ -1012,6 +1012,10 @@ pub(super) struct OpenCADStudio {
|
||||||
save_dialog_filename: String,
|
save_dialog_filename: String,
|
||||||
/// True when triggered from the unsaved-changes flow.
|
/// True when triggered from the unsaved-changes flow.
|
||||||
save_dialog_for_unsaved: bool,
|
save_dialog_for_unsaved: bool,
|
||||||
|
/// CMMS embedding (not upstream): editable filename for the "Export to
|
||||||
|
/// CAM" dialog, e.g. "Bracket.stl" - separate from save_dialog_filename
|
||||||
|
/// so the two dialogs never clobber each other's typed text.
|
||||||
|
cam_export_filename: String,
|
||||||
/// User preference for the first save of a new/unsaved drawing.
|
/// User preference for the first save of a new/unsaved drawing.
|
||||||
default_save_format: String,
|
default_save_format: String,
|
||||||
/// Persisted interface language selection.
|
/// Persisted interface language selection.
|
||||||
|
|
@ -1562,6 +1566,9 @@ pub enum ModalKind {
|
||||||
DimStyle,
|
DimStyle,
|
||||||
Unsaved,
|
Unsaved,
|
||||||
SaveDialog,
|
SaveDialog,
|
||||||
|
/// CMMS embedding (not upstream): filename popup for "Export to CAM"
|
||||||
|
/// (Save-As's dialog, minus the format picker - always STL).
|
||||||
|
CamExportDialog,
|
||||||
Recovery,
|
Recovery,
|
||||||
RecoveryPrompt,
|
RecoveryPrompt,
|
||||||
Options,
|
Options,
|
||||||
|
|
@ -1809,6 +1816,11 @@ pub enum Message {
|
||||||
SaveDialogFilenameChanged(String),
|
SaveDialogFilenameChanged(String),
|
||||||
SaveDialogConfirm,
|
SaveDialogConfirm,
|
||||||
SaveDialogCancel,
|
SaveDialogCancel,
|
||||||
|
// ── CMMS embedding (not upstream): "Export to CAM" filename dialog ─────
|
||||||
|
CamExportDialogOpen,
|
||||||
|
CamExportFilenameChanged(String),
|
||||||
|
CamExportConfirm,
|
||||||
|
CamExportCancel,
|
||||||
/// Destination picked in the native OS save dialog (`None` = cancelled).
|
/// Destination picked in the native OS save dialog (`None` = cancelled).
|
||||||
SaveDialogPathPicked(Option<std::path::PathBuf>),
|
SaveDialogPathPicked(Option<std::path::PathBuf>),
|
||||||
/// Open the application-wide Options dialog.
|
/// Open the application-wide Options dialog.
|
||||||
|
|
@ -3264,6 +3276,7 @@ impl OpenCADStudio {
|
||||||
save_dialog_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
save_dialog_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
||||||
save_dialog_filename: "drawing.dwg".to_string(),
|
save_dialog_filename: "drawing.dwg".to_string(),
|
||||||
save_dialog_for_unsaved: false,
|
save_dialog_for_unsaved: false,
|
||||||
|
cam_export_filename: "drawing.stl".to_string(),
|
||||||
default_save_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
default_save_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
||||||
language: crate::i18n::Language::default(),
|
language: crate::i18n::Language::default(),
|
||||||
// Plot style
|
// Plot style
|
||||||
|
|
|
||||||
|
|
@ -1173,6 +1173,66 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
Message::SaveDialogCancel => self.close_save_dialog_window(),
|
Message::SaveDialogCancel => self.close_save_dialog_window(),
|
||||||
|
|
||||||
|
// CMMS embedding (not upstream): "Export to CAM" filename dialog.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
Message::CamExportDialogOpen => {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if self.tabs[i].scene.meshes.is_empty() {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("STLOUT: no 3D mesh data in this drawing.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
self.cam_export_filename = format!("{}.stl", self.tabs[i].tab_display_name());
|
||||||
|
self.active_modal = Some(crate::app::ModalKind::CamExportDialog);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
Message::CamExportDialogOpen => Task::none(),
|
||||||
|
|
||||||
|
Message::CamExportFilenameChanged(name) => {
|
||||||
|
self.cam_export_filename = name;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::CamExportCancel => {
|
||||||
|
if self.active_modal == Some(crate::app::ModalKind::CamExportDialog) {
|
||||||
|
self.active_modal = None;
|
||||||
|
self.reset_modal_geometry();
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
Message::CamExportConfirm => {
|
||||||
|
self.active_modal = None;
|
||||||
|
self.reset_modal_geometry();
|
||||||
|
let i = self.active_tab;
|
||||||
|
let meshes: Vec<crate::scene::model::mesh_model::MeshModel> = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.meshes
|
||||||
|
.values()
|
||||||
|
.filter_map(|s| s.lods.first().cloned())
|
||||||
|
.collect();
|
||||||
|
let mut filename = self.cam_export_filename.trim().to_string();
|
||||||
|
if filename.is_empty() {
|
||||||
|
filename = format!("{}.stl", self.tabs[i].tab_display_name());
|
||||||
|
}
|
||||||
|
if std::path::Path::new(&filename).extension().is_none() {
|
||||||
|
filename = format!("{filename}.stl");
|
||||||
|
}
|
||||||
|
Task::perform(
|
||||||
|
async move {
|
||||||
|
let mesh_refs: Vec<_> = meshes.iter().collect();
|
||||||
|
let bytes = crate::io::stl::build_stl(&mesh_refs)
|
||||||
|
.ok_or_else(|| "no mesh data to export".to_string())?;
|
||||||
|
crate::sys::post_stl_export_to_host(&bytes, &filename).await
|
||||||
|
},
|
||||||
|
Message::CmmsStlExportResult,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
Message::CamExportConfirm => Task::none(),
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
Message::SaveDialogPathPicked(picked) => self.on_save_dialog_path_picked(picked),
|
Message::SaveDialogPathPicked(picked) => self.on_save_dialog_path_picked(picked),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ impl OpenCADStudio {
|
||||||
Some(K::PointStyle) => crate::tr!("modal", "point-style"),
|
Some(K::PointStyle) => crate::tr!("modal", "point-style"),
|
||||||
Some(K::AttributeEditor) => crate::tr!("modal", "attribute-editor"),
|
Some(K::AttributeEditor) => crate::tr!("modal", "attribute-editor"),
|
||||||
Some(K::SaveDialog) => crate::tr!("modal", "save-drawing-as"),
|
Some(K::SaveDialog) => crate::tr!("modal", "save-drawing-as"),
|
||||||
|
// CMMS embedding (not upstream): plain literal, not crate::tr!()
|
||||||
|
// - not worth wiring a new key into every locale catalog file
|
||||||
|
// for one CMMS-only dialog.
|
||||||
|
Some(K::CamExportDialog) => "Export to CAM".to_string(),
|
||||||
Some(K::Recovery) => crate::tr!("modal", "recovery-report"),
|
Some(K::Recovery) => crate::tr!("modal", "recovery-report"),
|
||||||
Some(K::RecoveryPrompt) => crate::tr!("modal", "recovery-prompt"),
|
Some(K::RecoveryPrompt) => crate::tr!("modal", "recovery-prompt"),
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
|
|
@ -1358,6 +1362,11 @@ impl OpenCADStudio {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
super::super::ModalKind::CamExportDialog => {
|
||||||
|
automatic_flow(ex, |flow| {
|
||||||
|
cam_export_dialog_window(&self.cam_export_filename, flow)
|
||||||
|
})
|
||||||
|
}
|
||||||
super::super::ModalKind::Recovery => {
|
super::super::ModalKind::Recovery => {
|
||||||
let report = self.recovery_report.as_ref()?;
|
let report = self.recovery_report.as_ref()?;
|
||||||
sized_flow(ex, 680, 460, |flow| {
|
sized_flow(ex, 680, 460, |flow| {
|
||||||
|
|
@ -1522,6 +1531,61 @@ fn save_as_dialog_window<'a>(
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CMMS embedding (not upstream): filename popup for "Export to CAM" -
|
||||||
|
/// same visual shape as save_as_dialog_window, minus the format picker
|
||||||
|
/// (always STL, there's nothing to choose).
|
||||||
|
fn cam_export_dialog_window<'a>(
|
||||||
|
filename: &'a str,
|
||||||
|
sizing: crate::ui::modal::ModalSizing,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
let label = |s: Cow<'static, str>| text(s).size(11).style(dialog_muted_text_style);
|
||||||
|
let field_width = if matches!(sizing.width, iced::Length::Fill) {
|
||||||
|
Fill
|
||||||
|
} else {
|
||||||
|
iced::Length::Shrink
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut items: Vec<Element<'a, Message>> = Vec::new();
|
||||||
|
items.push(text("Export to CAM").size(14).into());
|
||||||
|
items.push(Space::new().height(12).into());
|
||||||
|
items.push(
|
||||||
|
row![
|
||||||
|
label(Cow::Borrowed("File name:")).width(70),
|
||||||
|
iced::widget::text_input("drawing.stl", filename)
|
||||||
|
.on_input(Message::CamExportFilenameChanged)
|
||||||
|
.size(13)
|
||||||
|
.padding([5, 8])
|
||||||
|
.width(field_width),
|
||||||
|
]
|
||||||
|
.align_y(iced::Alignment::Center)
|
||||||
|
.spacing(6)
|
||||||
|
.width(sizing.width)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
items.push(Space::new().height(16).into());
|
||||||
|
items.push(
|
||||||
|
row![
|
||||||
|
Space::new().width(Fit),
|
||||||
|
dialog_button(t!("Export"), Message::CamExportConfirm, button::primary),
|
||||||
|
Space::new().width(8),
|
||||||
|
dialog_button(t!("Cancel"), Message::CamExportCancel, button::secondary),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let body = column(items)
|
||||||
|
.spacing(0)
|
||||||
|
.width(sizing.width)
|
||||||
|
.height(sizing.height);
|
||||||
|
|
||||||
|
container(body)
|
||||||
|
.style(dialog_body_style)
|
||||||
|
.padding([14, 16])
|
||||||
|
.width(sizing.width)
|
||||||
|
.height(sizing.height)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn unsaved_changes_dialog_window(
|
fn unsaved_changes_dialog_window(
|
||||||
name: &str,
|
name: &str,
|
||||||
sizing: crate::ui::modal::ModalSizing,
|
sizing: crate::ui::modal::ModalSizing,
|
||||||
|
|
|
||||||
|
|
@ -410,6 +410,14 @@ impl Ribbon {
|
||||||
quick_access_btn(crate::ui::icons::FOLDER_OPEN, "Open", "OPEN").into(),
|
quick_access_btn(crate::ui::icons::FOLDER_OPEN, "Open", "OPEN").into(),
|
||||||
quick_access_btn(crate::ui::icons::SAVE, "Save", "SAVE").into(),
|
quick_access_btn(crate::ui::icons::SAVE, "Save", "SAVE").into(),
|
||||||
quick_access_btn(crate::ui::icons::FILE_EXPORT, "Save As", "SAVEAS").into(),
|
quick_access_btn(crate::ui::icons::FILE_EXPORT, "Save As", "SAVEAS").into(),
|
||||||
|
// CMMS embedding (not upstream): "Export to CAM" - reuses the
|
||||||
|
// GEAR icon (no dedicated CAM/CNC icon asset exists) since it's
|
||||||
|
// the closest thematic fit already bundled. No-ops gracefully
|
||||||
|
// outside the CMMS (native build: dialog doesn't open; standalone
|
||||||
|
// web: opens, but the export POST fails with a clear "not
|
||||||
|
// running inside the CMMS" message) rather than being hidden -
|
||||||
|
// same always-visible treatment as New/Open/Print here.
|
||||||
|
quick_access_btn(crate::ui::icons::GEAR, "Export to CAM", "EXPORTCAM").into(),
|
||||||
quick_access_btn(crate::ui::icons::PRINT, "Print", "PRINT").into(),
|
quick_access_btn(crate::ui::icons::PRINT, "Print", "PRINT").into(),
|
||||||
render_history_control("Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown).into(),
|
render_history_control("Undo", UNDO_HISTORY_ID, undo_count, &self.open_dropdown).into(),
|
||||||
render_history_control("Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown).into(),
|
render_history_control("Redo", REDO_HISTORY_ID, redo_count, &self.open_dropdown).into(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue