web: XREFs, data links & extraction, ARCHIVE transmittals, worker-side saves

The last four big web-vs-desktop gaps.

XREFs: XATTACH reads the picked drawing's bytes and registers them
name-keyed (the image/PDF-underlay pattern), pre-parses on the web
worker, and the commit merge produces the same xref block flow as
native. Opened drawings resolve xrefs from the registry (trailing-name
match) before cache build; unresolved references produce ONE warning
listing the names and the fix — drop the referenced files on the page
(drops and picker opens now register their bytes) and reopen or
XRELOAD. XRELOAD works off the registry on unsaved drawings too.

Data Link & Extraction: spreadsheet links read picked xlsx/csv bytes
(calamine from slices / CSV from string) through a session registry,
so preview, refresh, and table-insert work on web; extraction takes
external drawings via a multi-file picker parsed on the worker, and
outputs CSV/TXT/.dxex via download_bytes. The disabled 'Add folder'
button explains why.

ARCHIVE/ETRANSMIT: assembles the drawing plus registered images, PDF
underlays, and session plot tables into a deterministic ZIP (minimal
writer over flate2 — no new dependency) delivered as
{stem}_transmit.zip; skipped dependencies are reported by name.

Save/autosave off the UI thread: the parse worker gains a serialize
action (protocol v5) — the document is prepared once on the main
thread (clone, styles, dimension bake), then the DWG/DXF writer pass,
the dominant cost, runs on the worker; any worker failure falls back
to inline serialization with the identical shared implementation.
The save path also clears the dirty flag only when the tab didn't
change mid-write, and the runtime-fields sidecar now carries
graphic_data, fixing a pre-existing loss of raw DWG round-trip bytes
on web-opened documents.
This commit is contained in:
Afonsop committed 2026-09-17 23:23:01 +01:00
1 parent c643a10ed9
commit 2e1006348b
44 files changed
+2564 -330

No files matched your search

+35 -4
View File
@@ -571,13 +571,29 @@ impl OpenCADStudio {
}
"XRELOAD" => {
// Reload all xrefs for the current drawing.
if let Some(path) = &self.tabs[i].current_path.clone() {
if let Some(base_dir) = path.parent() {
// Reload all xrefs for the current drawing. Web: xrefs
// re-read from the in-session bytes registry, so an unsaved
// drawing reloads too; native needs the drawing's own folder
// for relative xref paths.
#[cfg(target_arch = "wasm32")]
let base_dir: Option<std::path::PathBuf> = Some(std::path::PathBuf::new());
#[cfg(not(target_arch = "wasm32"))]
let base_dir: Option<std::path::PathBuf> = self
.tabs[i]
.current_path
.clone()
.and_then(|path| path.parent().map(std::path::Path::to_path_buf));
if let Some(base_dir) = base_dir {
{
let (infos, _dropped) = crate::io::xref::resolve_xrefs(
&mut self.tabs[i].scene.document,
base_dir,
&base_dir,
);
// Web: unresolved references surface once, with the
// drop hint, instead of a filesystem path no browser
// can open.
#[cfg(target_arch = "wasm32")]
let mut unresolved: Vec<String> = Vec::new();
for info in &infos {
match info.status {
crate::io::xref::XrefStatus::Loaded => {
@@ -590,12 +606,17 @@ impl OpenCADStudio {
info.name
).as_ref());
}
#[cfg(not(target_arch = "wasm32"))]
crate::io::xref::XrefStatus::NotFound => {
self.command_line.push_error(crate::tf!(
"XREF Not found: \"{}\" ({})",
info.name, info.path
).as_ref());
}
#[cfg(target_arch = "wasm32")]
crate::io::xref::XrefStatus::NotFound => {
unresolved.push(info.name.clone());
}
crate::io::xref::XrefStatus::Failed => {
self.command_line.push_error(crate::tf!(
"XREF Reload failed: \"{}\" ({})",
@@ -610,6 +631,16 @@ impl OpenCADStudio {
}
}
}
#[cfg(target_arch = "wasm32")]
if !unresolved.is_empty() {
let names = unresolved.join(", ");
self.command_line.push_warning(
crate::tf!(
"XREF Unresolved external references: {names} — drop the referenced DWG/DXF files on the page to register them, then reopen this drawing or run XRELOAD."
)
.as_ref(),
);
}
self.tabs[i].scene.populate_hatches_from_document();
self.tabs[i].scene.populate_images_from_document();
self.tabs[i].scene.populate_meshes_from_document();