mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-25 19:36:49 -06:00
-BLOCK was an alias of BLOCK, so the selected objects were always converted to a block reference; a script doing -BLOCK then INSERT ended up with an extra reference. -BLOCK is now its own command: the command line erases the selection, and its JSON form defaults to keep "delete". BLOCK (menu Make..., typed BLOCK) and the "block" JSON form still convert by default. Both forms ask 'Block "<name>" already exists. Redefine it? [Yes/No] <N>:' when the name is in use; No asks for the name again. Session::start resolves a dashed name to a command with that id (the same change as #280's -ATTEDIT). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.9 KiB
Rust
59 lines
2.9 KiB
Rust
//! BLOCK redefinition: no self-referencing blocks, names are case-insensitive (#250).
|
|
|
|
use cadcraft_engine::Session;
|
|
use cadcraft_engine::doc::EntityKind;
|
|
use serde_json::{Value, json};
|
|
|
|
fn line(s: &mut Session, a: [f64; 2], b: [f64; 2]) -> String {
|
|
s.execute("line", &json!({ "points": [a, b] })).unwrap()["handles"][0].as_str().unwrap().to_string()
|
|
}
|
|
|
|
/// Kinds of the entities in block `name`, as "line"/"insert:<block>".
|
|
fn contents(s: &Session, name: &str) -> Vec<String> {
|
|
let b = s.doc().unwrap().block(name).unwrap().clone();
|
|
b.entities
|
|
.iter()
|
|
.map(|e| match &e.kind {
|
|
EntityKind::Insert(i) => format!("insert:{}", i.block),
|
|
EntityKind::Line(_) => "line".to_string(),
|
|
_ => "other".to_string(),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn a_block_cannot_be_redefined_to_reference_itself() {
|
|
let mut s = Session::new();
|
|
// Command line: -BLOCK redefines B (Yes) from the reference BLOCK converted plus a new line.
|
|
s.script("LINE 0,0 10,0\n\nBLOCK B 0,0 L\n\nLINE 0,0 0,10\n\n-BLOCK B Y 0,0 ALL\n\n").unwrap();
|
|
assert!(s.running.is_none());
|
|
assert!(s.log.iter().any(|l| l.contains("references itself")), "{:?}", s.log);
|
|
assert_eq!(contents(&s, "B"), ["line"], "B keeps its original definition");
|
|
assert_eq!(s.doc().unwrap().model.len(), 2, "the reference and the new line stay in the drawing");
|
|
|
|
// Through a nested block: A holds B's reference, B holds A's.
|
|
let mut s = Session::new();
|
|
let l = line(&mut s, [0.0, 0.0], [10.0, 0.0]);
|
|
let a = s.execute("block", &json!({ "name": "A", "base": [0, 0], "handles": [l] })).unwrap()["insert"].clone();
|
|
let l = line(&mut s, [0.0, 0.0], [0.0, 10.0]);
|
|
let b = s.execute("block", &json!({ "name": "B", "base": [0, 0], "handles": [a, l] })).unwrap()["insert"].clone();
|
|
let r = s.execute("block", &json!({ "name": "a", "base": [0, 0], "handles": [b] }));
|
|
assert!(r.is_err_and(|e| e.to_string().contains("references itself")));
|
|
assert_eq!(contents(&s, "A"), ["line"]);
|
|
}
|
|
|
|
#[test]
|
|
fn redefining_with_a_different_case_replaces_the_block() {
|
|
let mut s = Session::new();
|
|
let l = line(&mut s, [0.0, 0.0], [10.0, 0.0]);
|
|
s.execute("block", &json!({ "name": "Door", "base": [0, 0], "handles": [l] })).unwrap();
|
|
let l = line(&mut s, [0.0, 0.0], [0.0, 10.0]);
|
|
s.execute("block", &json!({ "name": "DOOR", "base": [0, 0], "handles": [l], "keep": "delete" })).unwrap();
|
|
let list = s.execute("blocks.list", &json!({})).unwrap();
|
|
let names: Vec<&str> = list["blocks"].as_array().unwrap().iter().filter_map(|b| b["name"].as_str()).collect();
|
|
assert_eq!(names, ["Door"]);
|
|
assert_eq!(s.doc().unwrap().blocks.values().map(|b| b.entities.len()).collect::<Vec<_>>(), [1]);
|
|
let v: Vec<Value> = s.doc().unwrap().block("Door").unwrap().entities.iter().map(|e| serde_json::to_value(&e.kind).unwrap()).collect();
|
|
assert_eq!(v[0]["b"]["y"], json!(10.0), "the new definition replaced the old one");
|
|
}
|