mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-02 07:16:53 -06:00
* feat(engine): INSERT of a drawing file keeps an attribute definition's field The attribute definitions of a drawing file read by INSERT name=file lost their extension dictionary, so the ACAD_FIELD dictionary and the FIELD objects of an attribute whose value is a field (the file name in a title block) were gone from the block. adopt() now copies that object tree to the drawing under new handles, with the FIELD class and a field list if the drawing has none. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * foreign: index the kept objects once when walking and copying a dictionary tree A file can keep many objects; a linear lookup per pointer grew with their product. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: andrewbrake <6925387+andrewbrake@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
108 lines
4.7 KiB
Rust
108 lines
4.7 KiB
Rust
//! `INSERT name=file` keeps the field of an attribute definition: the definition's extension
|
|
//! dictionary and the objects in it come along under new handles, so the attributes the block's
|
|
//! references get can carry the field when the drawing is saved.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use cadcraft_engine::Session;
|
|
use cadcraft_engine::cmd::file::{IoHooks, set_io};
|
|
use cadcraft_engine::doc::foreign::{ForeignExtra, ForeignObject};
|
|
use cadcraft_engine::doc::{Attrib, Drawing, Entity, EntityKind, Handle, RawTag, Text};
|
|
use cadcraft_engine::geom::Vec3;
|
|
use serde_json::json;
|
|
|
|
#[path = "support/temp_dir.rs"]
|
|
mod temp_dir;
|
|
|
|
use temp_dir::TempDir;
|
|
|
|
fn tag(code: i32, value: &str) -> RawTag {
|
|
RawTag { code, value: value.into() }
|
|
}
|
|
|
|
fn object(handle: u64, kind: &str, tags: Vec<RawTag>) -> ForeignObject {
|
|
ForeignObject { handle: Handle(handle), kind: kind.into(), tags }
|
|
}
|
|
|
|
/// A drawing file in a test format: model space holds a definition NUMBER (handle 0x50) whose
|
|
/// extension dictionary 0x60 owns the ACAD_FIELD dictionary 0x61, which owns the field 0x62 with
|
|
/// a child field 0x63. Handles 0x61..0x63 stand for the file's own; a second, unrelated object
|
|
/// 0x70 is not part of that tree. The bytes `plain` give the same definition without any of it.
|
|
fn read(bytes: &[u8], _name: &str) -> Result<Drawing, String> {
|
|
let mut d = Drawing::new_imperial();
|
|
let text = Text {
|
|
insert: Vec3::ZERO,
|
|
align_pt: None,
|
|
height: 2.0,
|
|
value: "TEMPLATE".into(),
|
|
rotation: 0.0,
|
|
width_factor: 1.0,
|
|
oblique: 0.0,
|
|
style: "Standard".into(),
|
|
halign: Default::default(),
|
|
valign: Default::default(),
|
|
};
|
|
let def = Attrib {
|
|
tag: "NUMBER".into(),
|
|
text,
|
|
invisible: false,
|
|
constant: false,
|
|
prompt: String::new(),
|
|
props: Default::default(),
|
|
multiline: None,
|
|
modes: Default::default(),
|
|
};
|
|
d.model.push(Entity::new(Handle(0x50), EntityKind::AttDef(def)));
|
|
if bytes == b"plain" {
|
|
return Ok(d);
|
|
}
|
|
let f = Arc::make_mut(&mut d.foreign);
|
|
f.entities.insert(Handle(0x50), ForeignExtra { xdict: Some(Handle(0x60)), ..Default::default() });
|
|
f.objects.push(object(0x60, "DICTIONARY", vec![tag(5, "60"), tag(330, "50"), tag(3, "ACAD_FIELD"), tag(360, "61")]));
|
|
f.objects.push(object(0x61, "DICTIONARY", vec![tag(5, "61"), tag(330, "60"), tag(3, "TEXT"), tag(360, "62")]));
|
|
f.objects.push(object(0x62, "FIELD", vec![tag(5, "62"), tag(330, "61"), tag(1, "_text"), tag(360, "63")]));
|
|
f.objects.push(object(0x63, "FIELD", vec![tag(5, "63"), tag(330, "62"), tag(1, "AcVar")]));
|
|
f.objects.push(object(0x70, "XRECORD", vec![tag(5, "70"), tag(330, "0")]));
|
|
f.classes.push(vec![tag(1, "FIELD"), tag(2, "AcDbField")]);
|
|
Ok(d)
|
|
}
|
|
|
|
fn hooks() {
|
|
set_io(IoHooks { read, write: |_, _| Err("no writer in this test".into()), plot: None });
|
|
}
|
|
|
|
#[test]
|
|
fn the_definition_of_an_inserted_file_keeps_its_field() {
|
|
hooks();
|
|
let dir = TempDir::new("insert-file-field");
|
|
let path = dir.file("border.dxf", "");
|
|
let mut s = Session::new();
|
|
s.execute("insert", &json!({ "name": "BORDER", "file": path, "at": [0, 0], "attribs": { "NUMBER": "A-1" } })).unwrap();
|
|
let d = s.doc().unwrap();
|
|
let def = d.block("BORDER").and_then(|b| b.entities.iter().find(|e| matches!(e.kind, EntityKind::AttDef(_)))).expect("the definition");
|
|
assert_ne!(def.handle, Handle(0x50), "the definition has a handle of this drawing");
|
|
let root = d.foreign.entities.get(&def.handle).and_then(|x| x.xdict).expect("the definition has an extension dictionary");
|
|
let tree = d.foreign.tree(root);
|
|
assert_eq!(tree.len(), 4, "the dictionaries and both fields, not the unrelated object");
|
|
assert!(tree.iter().all(|h| ![0x60, 0x61, 0x62, 0x63, 0x70].contains(&h.0)), "the copies have new handles");
|
|
// The root's owner is the definition, the others point at each other.
|
|
let owner =
|
|
|h: Handle| d.foreign.objects.iter().find(|o| o.handle == h).and_then(|o| o.tags.iter().find(|t| t.code == 330)).map(|t| t.value.clone());
|
|
assert_eq!(owner(tree[0]), Some(def.handle.hex()));
|
|
assert_eq!(owner(tree[1]), Some(tree[0].hex()));
|
|
assert_eq!(owner(tree[3]), Some(tree[2].hex()));
|
|
assert!(d.foreign.classes.iter().any(|c| c.iter().any(|t| t.code == 1 && t.value == "FIELD")), "the field class comes along");
|
|
}
|
|
|
|
#[test]
|
|
fn a_definition_without_a_field_gets_none() {
|
|
hooks();
|
|
let dir = TempDir::new("insert-file-nofield");
|
|
let path = dir.file("plain.dxf", "plain");
|
|
let mut s = Session::new();
|
|
s.execute("insert", &json!({ "name": "PLAIN", "file": path, "at": [0, 0] })).unwrap();
|
|
let d = s.doc().unwrap();
|
|
assert!(d.block("PLAIN").is_some());
|
|
assert!(d.foreign.is_empty(), "nothing is kept for a drawing without fields");
|
|
}
|