Selection: rebuild the spatial index when a layer used inside blocks is hidden or shown (fixes #716) (#775)

This commit is contained in:
Marco Arnulfo 2026-10-13 10:35:08 +02:00 committed by GitHub
parent 266ee7e6ea
commit 5de2324973
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 52 additions and 1 deletions

View File

@ -73,6 +73,8 @@ pub struct SpatialIndex {
dimscale: f64,
/// Annotation scale factor and ANNOALLVISIBLE: annotative objects measure at that scale.
anno: (u64, bool),
/// Layers turned off or frozen: block contents on them take no room in their inserts.
hidden_layers: Vec<String>,
/// (draw index of the chunk's first entity, chunk), in draw order.
chunks: Vec<(u32, Arc<ChunkIx>)>,
/// Chunk serial → draw offset.
@ -88,6 +90,10 @@ fn anno_key(d: &Drawing) -> (u64, bool) {
(d.current_anno_scale().factor().to_bits(), d.anno_all_visible())
}
fn hidden_layers(d: &Drawing) -> impl Iterator<Item = &str> {
d.layers.iter().filter(|l| !l.visible()).map(|l| l.name.as_str())
}
impl SpatialIndex {
/// Build an index over a store (no caching).
pub fn build(d: &Drawing, store: &EntityStore) -> SpatialIndex {
@ -95,7 +101,7 @@ impl SpatialIndex {
}
/// Build, reusing `prev` for chunks the two stores share (only when the bounds context —
/// blocks, dimension styles, DIMSCALE — is the same).
/// blocks, dimension styles, DIMSCALE, hidden layers — is the same).
fn build_from(d: &Drawing, store: &EntityStore, prev: Option<&SpatialIndex>) -> SpatialIndex {
let prev = prev.filter(|p| p.same_context(d));
let old: std::collections::HashMap<usize, Arc<ChunkIx>> =
@ -146,6 +152,7 @@ impl SpatialIndex {
dim_styles: d.dim_styles.clone(),
dimscale: d.header.f64("DIMSCALE", 1.0),
anno: anno_key(d),
hidden_layers: hidden_layers(d).map(str::to_owned).collect(),
chunks,
offsets,
tree,
@ -160,6 +167,7 @@ impl SpatialIndex {
&& self.dimscale.to_bits() == d.header.f64("DIMSCALE", 1.0).to_bits()
&& self.dim_styles == d.dim_styles
&& self.anno == anno_key(d)
&& self.hidden_layers.iter().map(String::as_str).eq(hidden_layers(d))
}
fn matches(&self, d: &Drawing, store: &EntityStore) -> bool {
@ -464,6 +472,49 @@ mod tests {
assert!(!Arc::ptr_eq(&before, &index(&d, &Space::Model).unwrap()));
}
/// Hiding a layer used inside a block shrinks its inserts' bounds without touching the
/// space or the block table: the index must not be reused (issue #716).
#[test]
fn index_follows_layer_visibility_inside_blocks() {
let mut d = Drawing::new_imperial();
let mut blk = (**drawing(1, 0).blocks.get("B").unwrap()).clone();
let big = Common { layer: "HID".into(), ..Common::default() };
blk.entities.push(Entity {
handle: cadcraft_doc::Handle(2_000_000),
common: big,
kind: EntityKind::Circle(cadcraft_doc::Circle { center: v3(0.0, 0.0), radius: 50.0 }),
});
d.blocks.insert("B".into(), Arc::new(blk));
d.ensure_layer("HID");
for i in 0..MIN_INDEXED {
let x = 1000.0 + i as f64;
d.add(&Space::Model, Common::default(), EntityKind::Line(cadcraft_doc::Line { a: v3(x, 0.0), b: v3(x, 1.0) })).unwrap();
}
let insert = Insert {
block: "B".into(),
insert: v3(0.0, 0.0),
scale: Vec3::new(1.0, 1.0, 1.0),
rotation: 0.0,
attribs: Vec::new(),
cols: 1,
rows: 1,
col_spacing: 0.0,
row_spacing: 0.0,
};
let h = d.add(&Space::Model, Common::default(), EntityKind::Insert(insert)).unwrap();
let small = Bounds2::new(Vec2::new(-2.0, -2.0), Vec2::new(2.0, 2.0));
let shown = SpatialIndex::build(&d, &d.model);
assert!(select::select_window(&d, &Space::Model, small, false).is_empty());
d.layer_mut("HID").unwrap().on = false;
assert!(!shown.matches(&d, &d.model));
let reused = SpatialIndex::build_from(&d, &d.model, Some(&shown));
let fresh = SpatialIndex::build(&d, &d.model);
let last = u32::try_from(MIN_INDEXED).unwrap();
assert_eq!(reused.bounds(last), fresh.bounds(last));
assert_eq!(select::select_window(&d, &Space::Model, small, false), vec![h]);
}
#[test]
fn small_spaces_are_scanned() {
let d = drawing(1, MIN_INDEXED - 1);