fix(web): preserve DWG render state

Carry rendering fields omitted by document serialization and normalize block origins before building web caches.
This commit is contained in:
Hakan Seven 2026-08-11 17:16:46 +03:00
commit 6cafea92bc
6 changed files with 104 additions and 6 deletions

1
Cargo.lock generated
View file

@ -3769,6 +3769,7 @@ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"getrandom 0.3.4", "getrandom 0.3.4",
"js-sys", "js-sys",
"serde",
"sha2 0.10.9", "sha2 0.10.9",
"wasm-bindgen", "wasm-bindgen",
] ]

View file

@ -10,6 +10,7 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7e2fa8c", features = ["serde"] } acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7e2fa8c", features = ["serde"] }
bincode = "1.3" bincode = "1.3"
serde = { version = "1", features = ["derive"] }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
getrandom = { version = "0.3", features = ["wasm_js"] } getrandom = { version = "0.3", features = ["wasm_js"] }
js-sys = "0.3" js-sys = "0.3"

View file

@ -8,7 +8,23 @@ use sha2::{Digest, Sha256};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
const HASH_MARKER: &str = "\nreport-source-sha256:"; const HASH_MARKER: &str = "\nreport-source-sha256:";
const PROTOCOL_VERSION: u16 = 3; const PROTOCOL_VERSION: u16 = 4;
#[derive(serde::Serialize)]
struct EntityRuntimeFields {
handle: u64,
linetype_handle: Option<u64>,
color_book_handle: Option<u64>,
face_visual_style_handle: Option<u64>,
edge_visual_style_handle: Option<u64>,
material_flags: u8,
material_handle: Option<u64>,
shadow_flags: u8,
plotstyle_flags: u8,
plotstyle_handle: Option<u64>,
entity_mode: Option<u8>,
has_ds_data: bool,
}
/// Parse DWG/DXF on a dedicated browser worker and return a compact serialized /// Parse DWG/DXF on a dedicated browser worker and return a compact serialized
/// document. The main wasm instance only deserializes and installs it, so the /// document. The main wasm instance only deserializes and installs it, so the
@ -159,11 +175,43 @@ fn encode_result(
recoverable_parse_error: bool, recoverable_parse_error: bool,
bytes: &[u8], bytes: &[u8],
) -> Result<Uint8Array, JsValue> { ) -> Result<Uint8Array, JsValue> {
let runtime_fields = result
.as_ref()
.ok()
.map(|outcome| {
outcome
.document
.entities()
.map(|entity| {
let common = entity.common();
EntityRuntimeFields {
handle: common.handle.value(),
linetype_handle: common.linetype_handle.map(|handle| handle.value()),
color_book_handle: common.color_book_handle.map(|handle| handle.value()),
face_visual_style_handle: common
.face_visual_style_handle
.map(|handle| handle.value()),
edge_visual_style_handle: common
.edge_visual_style_handle
.map(|handle| handle.value()),
material_flags: common.material_flags,
material_handle: common.material_handle.map(|handle| handle.value()),
shadow_flags: common.shadow_flags,
plotstyle_flags: common.plotstyle_flags,
plotstyle_handle: common.plotstyle_handle.map(|handle| handle.value()),
entity_mode: common.entity_mode,
has_ds_data: common.has_ds_data,
}
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let encoded = bincode::serialize(&( let encoded = bincode::serialize(&(
PROTOCOL_VERSION, PROTOCOL_VERSION,
result, result,
source_sha256, source_sha256,
recoverable_parse_error, recoverable_parse_error,
runtime_fields,
)) ))
.map_err(|error| worker_error(error.to_string(), bytes, true))?; .map_err(|error| worker_error(error.to_string(), bytes, true))?;
Ok(Uint8Array::from(encoded.as_slice())) Ok(Uint8Array::from(encoded.as_slice()))

View file

@ -663,6 +663,7 @@ async fn load_web_bytes(
merge_read_diagnostics(&mut outcome.stats, initial_stats); merge_read_diagnostics(&mut outcome.stats, initial_stats);
} }
let mut doc = outcome.document; let mut doc = outcome.document;
normalize_block_origins(&mut doc);
if name.to_ascii_lowercase().ends_with(".dxf") { if name.to_ascii_lowercase().ends_with(".dxf") {
fix_dxf_dimension_rotations(&mut doc); fix_dxf_dimension_rotations(&mut doc);
fix_dxf_layout_plot_settings(&mut doc); fix_dxf_layout_plot_settings(&mut doc);

View file

@ -7,8 +7,24 @@ use wasm_bindgen::{JsCast, JsValue};
use web_sys::{ErrorEvent, MessageEvent, Worker, WorkerOptions, WorkerType}; use web_sys::{ErrorEvent, MessageEvent, Worker, WorkerOptions, WorkerType};
const HASH_MARKER: &str = "\nreport-source-sha256:"; const HASH_MARKER: &str = "\nreport-source-sha256:";
const PROTOCOL_VERSION: u16 = 3; const PROTOCOL_VERSION: u16 = 4;
const WORKER_URL: &str = "ocs-parse-worker.js?v=3"; const WORKER_URL: &str = "ocs-parse-worker.js?v=4";
#[derive(serde::Deserialize)]
struct EntityRuntimeFields {
handle: u64,
linetype_handle: Option<u64>,
color_book_handle: Option<u64>,
face_visual_style_handle: Option<u64>,
edge_visual_style_handle: Option<u64>,
material_flags: u8,
material_handle: Option<u64>,
shadow_flags: u8,
plotstyle_flags: u8,
plotstyle_handle: Option<u64>,
entity_mode: Option<u8>,
has_ds_data: bool,
}
pub(super) async fn parse_document( pub(super) async fn parse_document(
name: &str, name: &str,
@ -45,6 +61,7 @@ pub(super) async fn parse_document(
>, >,
Option<String>, Option<String>,
bool, bool,
Vec<EntityRuntimeFields>,
) = bincode::deserialize(&bytes) ) = bincode::deserialize(&bytes)
.map_err(|error| super::OpenLoadError::from(error.to_string()))?; .map_err(|error| super::OpenLoadError::from(error.to_string()))?;
if payload.0 != PROTOCOL_VERSION { if payload.0 != PROTOCOL_VERSION {
@ -54,7 +71,13 @@ pub(super) async fn parse_document(
))); )));
} }
match payload.1 { match payload.1 {
Ok(outcome) => Ok((outcome, payload.2)), Ok(mut outcome) => {
restore_entity_runtime_fields(
&mut outcome.document,
payload.4,
);
Ok((outcome, payload.2))
}
Err((message, read_stats)) => Err(super::OpenLoadError { Err((message, read_stats)) => Err(super::OpenLoadError {
message, message,
source_sha256: payload.2, source_sha256: payload.2,
@ -119,6 +142,30 @@ pub(super) async fn parse_document(
result result
} }
fn restore_entity_runtime_fields(
document: &mut acadrust::CadDocument,
fields: Vec<EntityRuntimeFields>,
) {
let handle = |value: Option<u64>| value.map(acadrust::Handle::new);
for fields in fields {
let Some(entity) = document.get_entity_mut(acadrust::Handle::new(fields.handle)) else {
continue;
};
let common = entity.common_mut();
common.linetype_handle = handle(fields.linetype_handle);
common.color_book_handle = handle(fields.color_book_handle);
common.face_visual_style_handle = handle(fields.face_visual_style_handle);
common.edge_visual_style_handle = handle(fields.edge_visual_style_handle);
common.material_flags = fields.material_flags;
common.material_handle = handle(fields.material_handle);
common.shadow_flags = fields.shadow_flags;
common.plotstyle_flags = fields.plotstyle_flags;
common.plotstyle_handle = handle(fields.plotstyle_handle);
common.entity_mode = fields.entity_mode;
common.has_ds_data = fields.has_ds_data;
}
}
pub(super) async fn sha256_document(bytes: &[u8]) -> Result<String, super::OpenLoadError> { pub(super) async fn sha256_document(bytes: &[u8]) -> Result<String, super::OpenLoadError> {
let options = WorkerOptions::new(); let options = WorkerOptions::new();
options.set_type(WorkerType::Module); options.set_type(WorkerType::Module);

View file

@ -1,7 +1,7 @@
import init, { parse_document, sha256_document } from "./worker_pkg/ocs_web_worker.js?v=3"; import init, { parse_document, sha256_document } from "./worker_pkg/ocs_web_worker.js?v=4";
const ready = init( const ready = init(
new URL("./worker_pkg/ocs_web_worker_bg.wasm?v=3", import.meta.url), new URL("./worker_pkg/ocs_web_worker_bg.wasm?v=4", import.meta.url),
); );
self.onmessage = async ({ data }) => { self.onmessage = async ({ data }) => {