mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-03 08:52:05 -06:00
* Dynamic blocks: index kept objects once per listing and copy each value record once Listing blocks rebuilt a handle index of every kept foreign object for each block with an extension dictionary, multiplying the block count by the object count. The listing now builds one ObjectIndex and asks is_dynamic_block_in per block; dynamic_block answers None before indexing when the block has no extension dictionary. templates() copied a value record's tags once per dictionary key, so a record listed under many keys was copied many times, and it looked up each dictionary entry by scanning every object. It now resolves entries through its handle index and copies each record at most once (CWE-400, CWE-407). * Dynamic blocks: list a block as dynamic from its graph object without walking the graph's members
145 lines
7.0 KiB
Rust
145 lines
7.0 KiB
Rust
//! `blocks.dynamic` and `blocks.list` report a dynamic block's parameters and actions as read
|
|
//! from another program's file (kept foreign data, built by hand here).
|
|
|
|
use std::sync::Arc;
|
|
|
|
use cadcraft_engine::Session;
|
|
use cadcraft_engine::doc::foreign::{Foreign, ForeignExtra, ForeignObject};
|
|
use cadcraft_engine::doc::{Handle, RawTag};
|
|
use serde_json::json;
|
|
|
|
fn obj(handle: u64, kind: &str, tags: &[(i32, &str)]) -> ForeignObject {
|
|
ForeignObject { handle: Handle(handle), kind: kind.into(), tags: tags.iter().map(|(c, v)| RawTag { code: *c, value: (*v).into() }).collect() }
|
|
}
|
|
|
|
/// A session whose block "Door" has a visibility parameter with two states.
|
|
fn session() -> Session {
|
|
let mut s = Session::new();
|
|
let l = s.execute("line", &json!({ "points": [[0, 0], [1, 0]] })).unwrap()["handles"][0].clone();
|
|
s.execute("block", &json!({ "name": "Door", "base": [0, 0], "handles": [l] })).unwrap();
|
|
let line = s.doc().unwrap().block("Door").unwrap().entities.handles()[0].hex();
|
|
let mut f = Foreign::default();
|
|
f.records.insert(("BLOCK_RECORD".into(), "DOOR".into()), ForeignExtra { xdict: Some(Handle(0xF00)), ..Default::default() });
|
|
f.objects.push(obj(0xF00, "DICTIONARY", &[(100, "AcDbDictionary"), (3, "ACAD_ENHANCEDBLOCK"), (360, "F01")]));
|
|
f.objects.push(obj(0xF01, "ACAD_EVALUATION_GRAPH", &[(100, "AcDbEvalGraph"), (91, "0"), (95, "1"), (360, "F02")]));
|
|
f.objects.push(obj(
|
|
0xF02,
|
|
"BLOCKVISIBILITYPARAMETER",
|
|
&[
|
|
(100, "AcDbEvalExpr"),
|
|
(90, "1"),
|
|
(100, "AcDbBlockElement"),
|
|
(300, "Visibility State"),
|
|
(100, "AcDbBlockParameter"),
|
|
(280, "1"),
|
|
(100, "AcDbBlock1PtParameter"),
|
|
(1010, "2"),
|
|
(1020, "3"),
|
|
(100, "AcDbBlockVisibilityParameter"),
|
|
(301, "Visibility1"),
|
|
(302, ""),
|
|
(93, "1"),
|
|
(331, &line),
|
|
(92, "2"),
|
|
(303, "Open"),
|
|
(94, "1"),
|
|
(332, &line),
|
|
(95, "0"),
|
|
(303, "Closed"),
|
|
(94, "0"),
|
|
(95, "0"),
|
|
],
|
|
));
|
|
let mut d = s.doc().unwrap().clone();
|
|
d.foreign = Arc::new(f);
|
|
s.open_drawing(d, "dynamic", None);
|
|
s
|
|
}
|
|
|
|
#[test]
|
|
fn dynamic_block_data_is_reported() {
|
|
let mut s = session();
|
|
let list = s.execute("blocks.list", &json!({})).unwrap();
|
|
let flags: Vec<_> = list["blocks"].as_array().unwrap().iter().map(|b| (b["name"].as_str().unwrap().to_string(), b["dynamic"].clone())).collect();
|
|
assert!(flags.contains(&("Door".into(), json!(true))), "{flags:?}");
|
|
|
|
let m = s.execute("blocks.dynamic", &json!({ "name": "DOOR" })).unwrap();
|
|
let p = &m["parameters"][0];
|
|
assert_eq!((p["node"].clone(), p["element"].clone(), p["showProperties"].clone()), (json!(1), json!("Visibility State"), json!(true)));
|
|
assert_eq!(p["kind"]["type"], "visibility");
|
|
assert_eq!(p["kind"]["label"], "Visibility1");
|
|
assert_eq!(p["kind"]["at"], json!({ "x": 2.0, "y": 3.0, "z": 0.0 }));
|
|
let states: Vec<_> =
|
|
p["kind"]["states"].as_array().unwrap().iter().map(|st| (st["name"].clone(), st["visible"].as_array().unwrap().len())).collect();
|
|
assert_eq!(states, [(json!("Open"), 1), (json!("Closed"), 0)]);
|
|
assert!(p["kind"]["controlled"][0].is_string(), "handles are hex strings");
|
|
assert_eq!(m["actions"], json!([]));
|
|
}
|
|
|
|
#[test]
|
|
fn blocks_without_dynamic_data_and_bad_names() {
|
|
let mut s = session();
|
|
let l = s.execute("line", &json!({ "points": [[0, 0], [1, 1]] })).unwrap()["handles"][0].clone();
|
|
s.execute("block", &json!({ "name": "Window", "base": [0, 0], "handles": [l] })).unwrap();
|
|
assert_eq!(s.execute("blocks.dynamic", &json!({ "name": "Window" })).unwrap(), json!(null));
|
|
assert_eq!(s.execute("blocks.dynamic", &json!({ "name": "*Model_Space" })).unwrap(), json!(null));
|
|
let list = s.execute("blocks.list", &json!({})).unwrap();
|
|
let flags: Vec<_> = list["blocks"].as_array().unwrap().iter().map(|b| (b["name"].as_str().unwrap().to_string(), b["dynamic"].clone())).collect();
|
|
assert!(flags.contains(&("Window".into(), json!(false))), "{flags:?}");
|
|
assert!(flags.contains(&("Door".into(), json!(true))), "{flags:?}");
|
|
for p in [json!({}), json!({ "name": 3 }), json!({ "name": "Nope" }), json!(null)] {
|
|
assert!(s.execute("blocks.dynamic", &p).is_err(), "{p}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_reference_to_a_dynamic_block_reports_its_custom_properties() {
|
|
let mut s = session();
|
|
// BLOCK made the line a reference to Door, which has Door's own values.
|
|
let insert = s.doc().unwrap().model.handles()[0].hex();
|
|
let r = s.execute("blocks.reference", &json!({ "handle": insert })).unwrap();
|
|
assert_eq!(r["definition"], "Door");
|
|
assert_eq!(r["values"], json!({}));
|
|
assert_eq!(r["properties"], json!([{ "name": "Visibility1", "value": "Open", "choices": ["Open", "Closed"], "editable": true }]));
|
|
|
|
// A plain block's reference, objects that aren't references and bad parameters.
|
|
let l = s.execute("line", &json!({ "points": [[0, 0], [1, 1]] })).unwrap()["handles"][0].clone();
|
|
let plain = s.execute("block", &json!({ "name": "Window", "base": [0, 0], "handles": [l.clone()] })).unwrap()["insert"].clone();
|
|
assert_eq!(s.execute("blocks.reference", &json!({ "handle": plain })).unwrap(), json!(null));
|
|
let l = s.execute("line", &json!({ "points": [[0, 0], [2, 2]] })).unwrap()["handles"][0].clone();
|
|
assert!(s.execute("blocks.reference", &json!({ "handle": l })).is_err());
|
|
for p in [json!({}), json!({ "handle": "zz" }), json!({ "handle": "FFFFFF" }), json!(null)] {
|
|
assert!(s.execute("blocks.reference", &p).is_err(), "{p}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_visibility_state_is_changed_in_one_undo_step() {
|
|
let mut s = session();
|
|
let insert = s.doc().unwrap().model.handles()[0].hex();
|
|
let r = s.execute("blocks.reference.set", &json!({ "handle": insert, "name": "Visibility1", "value": "Closed" })).unwrap();
|
|
assert_eq!(r["properties"][0]["value"], "Closed");
|
|
let block = |s: &Session| match s.doc().unwrap().model.iter().next().map(|e| e.kind.clone()) {
|
|
Some(cadcraft_engine::doc::EntityKind::Insert(i)) => i.block,
|
|
other => panic!("{other:?}"),
|
|
};
|
|
assert!(block(&s).starts_with("*U"));
|
|
// Still a reference to Door, in its new state.
|
|
let r = s.execute("blocks.reference", &json!({ "handle": insert })).unwrap();
|
|
assert_eq!((r["definition"].clone(), r["properties"][0]["value"].clone()), (json!("Door"), json!("Closed")));
|
|
|
|
s.execute("undo", &json!({})).unwrap();
|
|
assert_eq!(block(&s), "Door");
|
|
let r = s.execute("blocks.reference", &json!({ "handle": insert })).unwrap();
|
|
assert_eq!(r["properties"][0]["value"], "Open");
|
|
|
|
for p in [
|
|
json!({ "handle": insert, "name": "Visibility1", "value": "Ajar" }),
|
|
json!({ "handle": insert, "name": "Visibility1" }),
|
|
json!({ "name": "Visibility1", "value": "Open" }),
|
|
json!({ "handle": insert, "value": "Open" }),
|
|
] {
|
|
assert!(s.execute("blocks.reference.set", &p).is_err(), "{p}");
|
|
}
|
|
}
|