CMMS embedding: Export to CAM - build STL, save, navigate to Kiri:Moto
Wires Message::StlExport's existing STLOUT command to the CMMS when embedded, instead of the native save-dialog flow (which was already non-functional on the web build regardless - handle_path() has no real filesystem to write to there, a pre-existing upstream gap, not something this touches). - Cargo.toml: add the Location web-sys feature (needed for window.location().set_href()). - src/sys.rs: post_stl_export_to_host() - POSTs STL bytes as a new attachment (cad_export_stl action, not cad_save/cad_save_new - STL isn't in CAD_EDITABLE_EXT, it needs its own allowlist), then navigates the browser straight to the CMMS's Kiri:Moto CAM page for the attachment it just created. Splits cfg.job_id's "type:id" owner token apart to build cam-edit.php's entity_type=/entity_id= params. - src/app/mod.rs + update/mod.rs: CmmsStlExportResult message/handler - only ever surfaces a failure (a success already navigated away). - src/app/update/mod.rs: Message::StlExport gains a wasm32+embedded branch that skips the file-picker entirely and calls the above directly.
This commit is contained in:
parent
64d0230952
commit
3f8b69c71b
4 changed files with 120 additions and 0 deletions
|
|
@ -82,6 +82,7 @@ js-sys = "0.3"
|
|||
bincode = "1.3"
|
||||
web-sys = { version = "=0.3.85", features = [
|
||||
"Window",
|
||||
"Location",
|
||||
"Storage",
|
||||
"StorageManager",
|
||||
"Navigator",
|
||||
|
|
|
|||
|
|
@ -1796,6 +1796,12 @@ pub enum Message {
|
|||
/// to the host page instead of triggering a browser download.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
CmmsSaveResult(Result<(), String>),
|
||||
/// CMMS embedding (not upstream): result of "Export to CAM" - POSTing a
|
||||
/// freshly-built STL as a new attachment and navigating to Kiri:Moto.
|
||||
/// A successful result already navigated away, so this only ever
|
||||
/// surfaces a failure to the command line.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
CmmsStlExportResult(Result<(), String>),
|
||||
SaveFile,
|
||||
SaveAs,
|
||||
// ── Custom Save-As dialog ─────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -839,6 +839,20 @@ impl OpenCADStudio {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
// A success already navigated the browser to the CAM page
|
||||
// (post_stl_export_to_host does this itself once the save
|
||||
// response comes back) - only a failure is ever actually seen
|
||||
// here, the whole point of surfacing it in the command line
|
||||
// rather than just logging to the console.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Message::CmmsStlExportResult(result) => {
|
||||
if let Err(error) = result {
|
||||
self.command_line
|
||||
.push_error(crate::tf!("Export to CAM failed: {error}").as_ref());
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ImagePick => {
|
||||
Task::perform(crate::io::pick_image_file(), Message::ImagePickResult)
|
||||
}
|
||||
|
|
@ -985,6 +999,31 @@ impl OpenCADStudio {
|
|||
.push_error(crate::t!("STLOUT: no 3D mesh data in this drawing.").as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
// CMMS embedding (not upstream), "Export to CAM": when
|
||||
// launched from the CMMS, skip the (broken on web anyway -
|
||||
// handle_path() has no real filesystem to write to there)
|
||||
// native save-dialog flow entirely and instead build STL
|
||||
// bytes, POST them as a new attachment, and navigate
|
||||
// straight to the Kiri:Moto CAM page for it.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if crate::sys::cmms_config().is_some() {
|
||||
let meshes: Vec<crate::scene::model::mesh_model::MeshModel> = self.tabs[i]
|
||||
.scene
|
||||
.meshes
|
||||
.values()
|
||||
.filter_map(|s| s.lods.first().cloned())
|
||||
.collect();
|
||||
let filename = format!("{}.stl", self.tabs[i].tab_display_name());
|
||||
return 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,
|
||||
);
|
||||
}
|
||||
Task::perform(
|
||||
async {
|
||||
crate::sys::file_dialog()
|
||||
|
|
|
|||
74
src/sys.rs
74
src/sys.rs
|
|
@ -442,6 +442,80 @@ pub async fn post_bytes_to_host(bytes: &[u8], filename: &str) -> Result<(), Stri
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// CMMS embedding (not upstream): "Export to CAM" - POSTs STL bytes as a
|
||||
/// brand-new attachment (mirrors post_bytes_to_host's create path but
|
||||
/// against the cad_export_stl action, not cad_save/cad_save_new - STL
|
||||
/// isn't in CAD_EDITABLE_EXT, it needs its own allowlist), then navigates
|
||||
/// the browser straight to the Kiri:Moto CAM page for the file it just
|
||||
/// created. `cfg.job_id` already carries the "type:id" owner token (see
|
||||
/// public/vendor/cad-editor/edit.php's $ownerToken) - split it apart here
|
||||
/// to build cam-edit.php's separate entity_type=/entity_id= params.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn post_stl_export_to_host(bytes: &[u8], filename: &str) -> Result<(), String> {
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
let cfg = cmms_config().ok_or("not running inside the CMMS")?;
|
||||
|
||||
let form = web_sys::FormData::new().map_err(|_| "could not build form data")?;
|
||||
form.append_with_str("action", "cad_export_stl")
|
||||
.map_err(|_| "form append failed")?;
|
||||
form.append_with_str("job_id", &cfg.job_id)
|
||||
.map_err(|_| "form append failed")?;
|
||||
form.append_with_str("original_name", filename)
|
||||
.map_err(|_| "form append failed")?;
|
||||
form.append_with_str("csrf_token", &cfg.csrf_token)
|
||||
.map_err(|_| "form append failed")?;
|
||||
|
||||
let array = js_sys::Uint8Array::from(bytes);
|
||||
let parts = js_sys::Array::new();
|
||||
parts.push(&array.buffer());
|
||||
let blob = web_sys::Blob::new_with_u8_array_sequence(&parts)
|
||||
.map_err(|_| "could not build blob")?;
|
||||
form.append_with_blob("file", &blob)
|
||||
.map_err(|_| "form append failed")?;
|
||||
|
||||
let window = web_sys::window().ok_or("no window")?;
|
||||
let opts = web_sys::RequestInit::new();
|
||||
opts.set_method("POST");
|
||||
opts.set_body(&form);
|
||||
let request = web_sys::Request::new_with_str_and_init(&cfg.save_url, &opts)
|
||||
.map_err(|_| "could not build request")?;
|
||||
let response: web_sys::Response = JsFuture::from(window.fetch_with_request(&request))
|
||||
.await
|
||||
.map_err(|_| "export request failed")?
|
||||
.dyn_into()
|
||||
.map_err(|_| "export response is invalid")?;
|
||||
if !response.ok() {
|
||||
return Err(format!("export failed: HTTP {}", response.status()));
|
||||
}
|
||||
let text = JsFuture::from(response.text().map_err(|_| "text() unavailable")?)
|
||||
.await
|
||||
.map_err(|_| "export response body read failed")?;
|
||||
let body = text.as_string().ok_or("export response body is not a string")?;
|
||||
let parsed: serde_json::Value =
|
||||
serde_json::from_str(&body).map_err(|_| "export response is not valid JSON")?;
|
||||
let new_id = parsed
|
||||
.get("id")
|
||||
.and_then(|v| v.as_i64())
|
||||
.ok_or("export response missing id")?;
|
||||
|
||||
let (entity_type, entity_id) = cfg
|
||||
.job_id
|
||||
.split_once(':')
|
||||
.ok_or("malformed owner token")?;
|
||||
let url = format!(
|
||||
"/cam-edit.php?entity_type={}&entity_id={}&attachment_id={new_id}",
|
||||
percent_encode(entity_type),
|
||||
percent_encode(entity_id)
|
||||
);
|
||||
window
|
||||
.location()
|
||||
.set_href(&url)
|
||||
.map_err(|_| "navigation to the CAM page failed")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Short platform string for bug reports: OS + architecture on the desktop,
|
||||
/// the browser user-agent on the web.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
|
|||
Loading…
Reference in a new issue