fix(io): materialize missing BLOCK/ENDBLK sentinels on load

The DXF reader keeps marker identities on the block record but never materializes the entities, so block_entity_handle/block_end_handle dangle after every DXF load (reopened baked *D dimension blocks hit this every time). Re-create missing sentinels at load finalize, reusing the file handle when free and minting otherwise. Idempotent and DWG-safe: loads whose markers already resolve are untouched. Pinned by dimlinear_survives_save_reload_with_baked_geometry.
This commit is contained in:
Karim Jerbi committed 2026-10-05 00:07:21 +01:00
1 parent a0d7447e2c
commit 7d1fca000b
2 files changed
+152

No files matched your search

+73
View File
@@ -1970,4 +1970,77 @@ mod tests {
assert!((line.start.x - 2.0).abs() < 1e-12);
assert!((line.end.x - 2.0).abs() < 1e-12);
}
// RED (TDD): a real DIMLINEAR must survive save -> reload with its baked
// *D geometry intact and a clean handle graph (no duplicates, no dangling
// owners). Headless codec round-trip, no GUI needed.
#[test]
fn dimlinear_survives_save_reload_with_baked_geometry() {
let mut failures = Vec::new();
for ext in ["dxf", "dwg"] {
let mut doc = CadDocument::new();
let mut d =
DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0));
d.definition_point = Vector3::new(0.0, 5.0, 0.0);
d.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
doc.add_entity(EntityType::Dimension(Dimension::Linear(d)))
.unwrap();
let path = std::env::temp_dir().join(format!(
"ocs_dim_rt_{}.{ext}",
std::process::id()
));
crate::io::save_as_version(&doc, &path, codec::DxfVersion::AC1032)
.expect("save");
let loaded = crate::io::load_file(&path).expect("load");
let _ = std::fs::remove_file(&path);
let mut seen = std::collections::HashSet::new();
for e in loaded.entities() {
if !seen.insert(e.common().handle) {
failures.push(format!(
"{ext}: duplicate handle {:?}",
e.common().handle
));
}
}
let dims: Vec<_> = loaded
.entities()
.filter_map(|e| match e {
EntityType::Dimension(d) => Some(d.clone()),
_ => None,
})
.collect();
if dims.len() != 1 {
failures.push(format!("{ext}: dimension entity lost"));
continue;
}
let block_name = dims[0].base().block_name.clone();
if block_name.trim().is_empty() {
failures.push(format!("{ext}: dimension came back blockless"));
continue;
}
let Some(rec) = loaded.block_records.get(&block_name) else {
failures.push(format!("{ext}: *D block {block_name} missing after reload"));
continue;
};
let owned: Vec<Handle> = {
let mut h = rec.entity_handles.clone();
h.push(rec.block_entity_handle);
h.push(rec.block_end_handle);
h
};
if rec.entity_handles.is_empty() {
failures.push(format!("{ext}: baked block has no sub-entities"));
}
for h in &owned {
if h.is_null() || loaded.get_entity(*h).is_none() {
failures.push(format!(
"{ext}: dangling handle {h:?} in baked block {block_name}"
));
}
}
}
assert!(failures.is_empty(), "round-trip failures:\n{}", failures.join("\n"));
}
}