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));
|
||||
}
|
||||
|
||||
// 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" | "EXPORTSTEP" | "STPOUT" => {
|
||||
return Some(Task::done(Message::StepExport));
|
||||
|
|
|
|||
|
|
@ -1012,6 +1012,10 @@ pub(super) struct OpenCADStudio {
|
|||
save_dialog_filename: String,
|
||||
/// True when triggered from the unsaved-changes flow.
|
||||
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.
|
||||
default_save_format: String,
|
||||
/// Persisted interface language selection.
|
||||
|
|
@ -1562,6 +1566,9 @@ pub enum ModalKind {
|
|||
DimStyle,
|
||||
Unsaved,
|
||||
SaveDialog,
|
||||
/// CMMS embedding (not upstream): filename popup for "Export to CAM"
|
||||
/// (Save-As's dialog, minus the format picker - always STL).
|
||||
CamExportDialog,
|
||||
Recovery,
|
||||
RecoveryPrompt,
|
||||
Options,
|
||||
|
|
@ -1809,6 +1816,11 @@ pub enum Message {
|
|||
SaveDialogFilenameChanged(String),
|
||||
SaveDialogConfirm,
|
||||
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).
|
||||
SaveDialogPathPicked(Option<std::path::PathBuf>),
|
||||
/// Open the application-wide Options dialog.
|
||||
|
|
@ -3264,6 +3276,7 @@ impl OpenCADStudio {
|
|||
save_dialog_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
||||
save_dialog_filename: "drawing.dwg".to_string(),
|
||||
save_dialog_for_unsaved: false,
|
||||
cam_export_filename: "drawing.stl".to_string(),
|
||||
default_save_format: crate::io::DEFAULT_SAVE_FORMAT.to_string(),
|
||||
language: crate::i18n::Language::default(),
|
||||
// Plot style
|
||||
|
|
|
|||
|
|
@ -1173,6 +1173,66 @@ impl OpenCADStudio {
|
|||
|
||||
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"))]
|
||||
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::AttributeEditor) => crate::tr!("modal", "attribute-editor"),
|
||||
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::RecoveryPrompt) => crate::tr!("modal", "recovery-prompt"),
|
||||
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 => {
|
||||
let report = self.recovery_report.as_ref()?;
|
||||
sized_flow(ex, 680, 460, |flow| {
|
||||
|
|
@ -1522,6 +1531,61 @@ fn save_as_dialog_window<'a>(
|
|||
.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(
|
||||
name: &str,
|
||||
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::SAVE, "Save", "SAVE").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(),
|
||||
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(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue