Extents: count the visible attributes of a reference to a missing block (fixes #707) (#746)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 05:12:00 -04:00
1 parent 7b767ddab9
commit 63b448bee9
2 files changed
+70 -16

No files matched your search

+19 -16
View File
@@ -108,24 +108,27 @@ fn bounds_in<'a>(d: &'a Drawing, e: &Entity, depth: usize, blocks: &mut HashMap<
// A reference to a missing block (an unloaded xref, a purged definition) or to an empty
// one draws nothing, so it contributes nothing: ZOOM Extents must not fly off to its
// insertion point. Attributes below still count.
let Some(blk) = d.block(&ins.block) else { return Bounds2::EMPTY };
let m = ins.transform(blk.base.xy());
let inner = match blocks.get(&(blk.name.as_str(), depth)) {
Some(b) => *b,
None => {
let mut inner = Bounds2::EMPTY;
for be in blk.entities.iter().filter(|be| in_block_visible(d, be)) {
inner = inner.union(&bounds_in(d, be, depth + 1, blocks));
let mut b = Bounds2::EMPTY;
if let Some(blk) = d.block(&ins.block) {
let m = ins.transform(blk.base.xy());
let inner = match blocks.get(&(blk.name.as_str(), depth)) {
Some(b) => *b,
None => {
let mut inner = Bounds2::EMPTY;
for be in blk.entities.iter().filter(|be| in_block_visible(d, be)) {
inner = inner.union(&bounds_in(d, be, depth + 1, blocks));
}
blocks.insert((blk.name.as_str(), depth), inner);
inner
}
blocks.insert((blk.name.as_str(), depth), inner);
inner
};
b = if inner.is_empty() { Bounds2::EMPTY } else { Bounds2::from_points(inner.corners().map(|c| m.apply(c))) };
if ins.cols > 1 || ins.rows > 1 {
let off =
Vec2::new(ins.col_spacing * f64::from(ins.cols.saturating_sub(1)), ins.row_spacing * f64::from(ins.rows.saturating_sub(1)))
.rotate(ins.rotation);
b = b.union(&Bounds2::from_points(b.corners().map(|c| c + off)));
}
};
let mut b = if inner.is_empty() { Bounds2::EMPTY } else { Bounds2::from_points(inner.corners().map(|c| m.apply(c))) };
if ins.cols > 1 || ins.rows > 1 {
let off = Vec2::new(ins.col_spacing * f64::from(ins.cols.saturating_sub(1)), ins.row_spacing * f64::from(ins.rows.saturating_sub(1)))
.rotate(ins.rotation);
b = b.union(&Bounds2::from_points(b.corners().map(|c| c + off)));
}
for a in &ins.attribs {
if !a.invisible {
+51
View File
@@ -79,6 +79,57 @@ fn inserts_of_missing_or_empty_blocks_do_not_stretch_extents() {
assert_eq!(d.extents(&Space::Model).max, Vec2::new(12.0, 12.0));
}
#[test]
fn visible_attributes_of_a_missing_block_still_count() {
// The renderer draws the attributes of a reference whose definition is missing, so they are
// extents and selection targets, as with an empty definition. The insertion point still isn't.
let attrib = |invisible: bool| Attrib {
tag: "TAG".into(),
text: Text {
insert: Vec3::new(1000.0, 1000.0, 0.0),
align_pt: None,
height: 10.0,
value: "hello".into(),
rotation: 0.0,
width_factor: 1.0,
oblique: 0.0,
style: "Standard".into(),
halign: HAlign::Left,
valign: VAlign::Baseline,
},
invisible,
constant: false,
prompt: String::new(),
props: Default::default(),
};
let ins = |block: &str, attribs: Vec<Attrib>| {
Entity::new(
Handle(1),
EntityKind::Insert(Insert {
block: block.into(),
insert: Vec3::ZERO,
scale: Vec3::new(1.0, 1.0, 1.0),
rotation: 0.0,
attribs,
cols: 1,
rows: 1,
col_spacing: 0.0,
row_spacing: 0.0,
}),
)
};
let mut d = Drawing::new_imperial();
d.blocks.insert("Empty".into(), std::sync::Arc::new(Block::new("Empty")));
let missing = entity_bounds(&d, &ins("Missing", vec![attrib(false)]), 0);
let empty = entity_bounds(&d, &ins("Empty", vec![attrib(false)]), 0);
assert!(!missing.is_empty());
assert_eq!((missing.min, missing.max), (empty.min, empty.max));
assert_eq!(missing.min, Vec2::new(1000.0, 1000.0));
// Without a visible attribute there is nothing to measure.
assert!(entity_bounds(&d, &ins("Missing", vec![]), 0).is_empty());
assert!(entity_bounds(&d, &ins("Missing", vec![attrib(true)]), 0).is_empty());
}
#[test]
fn transform_arc_mirror_keeps_shape() {
let mut k = EntityKind::Arc(Arc { center: Vec3::ZERO, radius: 1.0, start: 0.0, end: std::f64::consts::FRAC_PI_2 });